@quolu/lattice 0.69.5 → 0.71.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 (605) hide show
  1. package/README.ja.md +5 -1
  2. package/package.json +1 -1
  3. package/sensor/dist/bin/lattice-sensor.js +746 -323
  4. package/sensor/dist/bin/viewer-gate.d.ts +17 -0
  5. package/sensor/dist/bin/viewer-gate.d.ts.map +1 -0
  6. package/sensor/dist/bin/viewer-gate.js +31 -0
  7. package/sensor/dist/bin/viewer-gate.js.map +1 -0
  8. package/sensor/dist/context/formatter.d.ts +7 -1
  9. package/sensor/dist/context/formatter.d.ts.map +1 -1
  10. package/sensor/dist/context/formatter.js +12 -6
  11. package/sensor/dist/context/formatter.js.map +1 -1
  12. package/sensor/dist/context/index.d.ts +12 -0
  13. package/sensor/dist/context/index.d.ts.map +1 -1
  14. package/sensor/dist/context/index.js +103 -16
  15. package/sensor/dist/context/index.js.map +1 -1
  16. package/sensor/dist/db/index.d.ts +23 -2
  17. package/sensor/dist/db/index.d.ts.map +1 -1
  18. package/sensor/dist/db/index.js +109 -31
  19. package/sensor/dist/db/index.js.map +1 -1
  20. package/sensor/dist/db/migrations.d.ts +1 -1
  21. package/sensor/dist/db/migrations.d.ts.map +1 -1
  22. package/sensor/dist/db/migrations.js +68 -1
  23. package/sensor/dist/db/migrations.js.map +1 -1
  24. package/sensor/dist/db/queries.d.ts +583 -6
  25. package/sensor/dist/db/queries.d.ts.map +1 -1
  26. package/sensor/dist/db/queries.js +1425 -57
  27. package/sensor/dist/db/queries.js.map +1 -1
  28. package/sensor/dist/db/schema.sql +27 -4
  29. package/sensor/dist/db/sqlite-adapter.d.ts +2 -0
  30. package/sensor/dist/db/sqlite-adapter.d.ts.map +1 -1
  31. package/sensor/dist/db/sqlite-adapter.js +56 -7
  32. package/sensor/dist/db/sqlite-adapter.js.map +1 -1
  33. package/sensor/dist/db/synthesis-stage.d.ts +12 -0
  34. package/sensor/dist/db/synthesis-stage.d.ts.map +1 -0
  35. package/sensor/dist/db/synthesis-stage.js +93 -0
  36. package/sensor/dist/db/synthesis-stage.js.map +1 -0
  37. package/sensor/dist/db/wal-valve.d.ts +32 -12
  38. package/sensor/dist/db/wal-valve.d.ts.map +1 -1
  39. package/sensor/dist/db/wal-valve.js +65 -22
  40. package/sensor/dist/db/wal-valve.js.map +1 -1
  41. package/sensor/dist/db/wsl-shared-index.d.ts +53 -0
  42. package/sensor/dist/db/wsl-shared-index.d.ts.map +1 -0
  43. package/sensor/dist/db/wsl-shared-index.js +121 -0
  44. package/sensor/dist/db/wsl-shared-index.js.map +1 -0
  45. package/sensor/dist/directory.d.ts +126 -2
  46. package/sensor/dist/directory.d.ts.map +1 -1
  47. package/sensor/dist/directory.js +285 -16
  48. package/sensor/dist/directory.js.map +1 -1
  49. package/sensor/dist/errors.d.ts +14 -0
  50. package/sensor/dist/errors.d.ts.map +1 -1
  51. package/sensor/dist/errors.js +16 -1
  52. package/sensor/dist/errors.js.map +1 -1
  53. package/sensor/dist/extraction/cfml-extractor.d.ts +1 -0
  54. package/sensor/dist/extraction/cfml-extractor.d.ts.map +1 -1
  55. package/sensor/dist/extraction/cfml-extractor.js +15 -3
  56. package/sensor/dist/extraction/cfml-extractor.js.map +1 -1
  57. package/sensor/dist/extraction/dfm-extractor.d.ts.map +1 -1
  58. package/sensor/dist/extraction/dfm-extractor.js +14 -10
  59. package/sensor/dist/extraction/dfm-extractor.js.map +1 -1
  60. package/sensor/dist/extraction/extraction-version.d.ts +1 -1
  61. package/sensor/dist/extraction/extraction-version.d.ts.map +1 -1
  62. package/sensor/dist/extraction/extraction-version.js +3 -1
  63. package/sensor/dist/extraction/extraction-version.js.map +1 -1
  64. package/sensor/dist/extraction/function-ref.d.ts +6 -4
  65. package/sensor/dist/extraction/function-ref.d.ts.map +1 -1
  66. package/sensor/dist/extraction/function-ref.js +35 -14
  67. package/sensor/dist/extraction/function-ref.js.map +1 -1
  68. package/sensor/dist/extraction/generated-detection.d.ts +48 -12
  69. package/sensor/dist/extraction/generated-detection.d.ts.map +1 -1
  70. package/sensor/dist/extraction/generated-detection.js +176 -11
  71. package/sensor/dist/extraction/generated-detection.js.map +1 -1
  72. package/sensor/dist/extraction/grammars.d.ts +38 -0
  73. package/sensor/dist/extraction/grammars.d.ts.map +1 -1
  74. package/sensor/dist/extraction/grammars.js +115 -3
  75. package/sensor/dist/extraction/grammars.js.map +1 -1
  76. package/sensor/dist/extraction/index.d.ts +218 -14
  77. package/sensor/dist/extraction/index.d.ts.map +1 -1
  78. package/sensor/dist/extraction/index.js +887 -175
  79. package/sensor/dist/extraction/index.js.map +1 -1
  80. package/sensor/dist/extraction/languages/c-cpp.d.ts +58 -0
  81. package/sensor/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  82. package/sensor/dist/extraction/languages/c-cpp.js +310 -19
  83. package/sensor/dist/extraction/languages/c-cpp.js.map +1 -1
  84. package/sensor/dist/extraction/languages/dart.d.ts.map +1 -1
  85. package/sensor/dist/extraction/languages/dart.js +8 -3
  86. package/sensor/dist/extraction/languages/dart.js.map +1 -1
  87. package/sensor/dist/extraction/languages/erlang.d.ts.map +1 -1
  88. package/sensor/dist/extraction/languages/erlang.js +58 -22
  89. package/sensor/dist/extraction/languages/erlang.js.map +1 -1
  90. package/sensor/dist/extraction/languages/javascript.js +1 -1
  91. package/sensor/dist/extraction/languages/javascript.js.map +1 -1
  92. package/sensor/dist/extraction/languages/kotlin.d.ts.map +1 -1
  93. package/sensor/dist/extraction/languages/kotlin.js +169 -36
  94. package/sensor/dist/extraction/languages/kotlin.js.map +1 -1
  95. package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
  96. package/sensor/dist/extraction/languages/lua.js +4 -1
  97. package/sensor/dist/extraction/languages/lua.js.map +1 -1
  98. package/sensor/dist/extraction/languages/objc.d.ts.map +1 -1
  99. package/sensor/dist/extraction/languages/objc.js +4 -0
  100. package/sensor/dist/extraction/languages/objc.js.map +1 -1
  101. package/sensor/dist/extraction/languages/python.d.ts.map +1 -1
  102. package/sensor/dist/extraction/languages/python.js +71 -0
  103. package/sensor/dist/extraction/languages/python.js.map +1 -1
  104. package/sensor/dist/extraction/languages/rust.d.ts +20 -0
  105. package/sensor/dist/extraction/languages/rust.d.ts.map +1 -1
  106. package/sensor/dist/extraction/languages/rust.js +50 -20
  107. package/sensor/dist/extraction/languages/rust.js.map +1 -1
  108. package/sensor/dist/extraction/languages/scala.d.ts.map +1 -1
  109. package/sensor/dist/extraction/languages/scala.js +18 -3
  110. package/sensor/dist/extraction/languages/scala.js.map +1 -1
  111. package/sensor/dist/extraction/languages/swift.d.ts +15 -0
  112. package/sensor/dist/extraction/languages/swift.d.ts.map +1 -1
  113. package/sensor/dist/extraction/languages/swift.js +50 -0
  114. package/sensor/dist/extraction/languages/swift.js.map +1 -1
  115. package/sensor/dist/extraction/languages/typescript.d.ts.map +1 -1
  116. package/sensor/dist/extraction/languages/typescript.js +9 -2
  117. package/sensor/dist/extraction/languages/typescript.js.map +1 -1
  118. package/sensor/dist/extraction/liquid-extractor.d.ts +18 -0
  119. package/sensor/dist/extraction/liquid-extractor.d.ts.map +1 -1
  120. package/sensor/dist/extraction/liquid-extractor.js +98 -22
  121. package/sensor/dist/extraction/liquid-extractor.js.map +1 -1
  122. package/sensor/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  123. package/sensor/dist/extraction/mybatis-extractor.js +6 -4
  124. package/sensor/dist/extraction/mybatis-extractor.js.map +1 -1
  125. package/sensor/dist/extraction/parse-pool.d.ts +7 -0
  126. package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
  127. package/sensor/dist/extraction/parse-pool.js +20 -1
  128. package/sensor/dist/extraction/parse-pool.js.map +1 -1
  129. package/sensor/dist/extraction/syntax-tokens.d.ts +103 -0
  130. package/sensor/dist/extraction/syntax-tokens.d.ts.map +1 -0
  131. package/sensor/dist/extraction/syntax-tokens.js +381 -0
  132. package/sensor/dist/extraction/syntax-tokens.js.map +1 -0
  133. package/sensor/dist/extraction/tree-sitter-helpers.d.ts +5 -0
  134. package/sensor/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  135. package/sensor/dist/extraction/tree-sitter-helpers.js +17 -0
  136. package/sensor/dist/extraction/tree-sitter-helpers.js.map +1 -1
  137. package/sensor/dist/extraction/tree-sitter-types.d.ts +31 -3
  138. package/sensor/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  139. package/sensor/dist/extraction/tree-sitter.d.ts +101 -18
  140. package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
  141. package/sensor/dist/extraction/tree-sitter.js +928 -134
  142. package/sensor/dist/extraction/tree-sitter.js.map +1 -1
  143. package/sensor/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  144. package/sensor/dist/file-limits.d.ts +40 -0
  145. package/sensor/dist/file-limits.d.ts.map +1 -0
  146. package/sensor/dist/file-limits.js +147 -0
  147. package/sensor/dist/file-limits.js.map +1 -0
  148. package/sensor/dist/graph/branch-guards.d.ts +265 -0
  149. package/sensor/dist/graph/branch-guards.d.ts.map +1 -0
  150. package/sensor/dist/graph/branch-guards.js +2325 -0
  151. package/sensor/dist/graph/branch-guards.js.map +1 -0
  152. package/sensor/dist/graph/dead-code.d.ts +251 -0
  153. package/sensor/dist/graph/dead-code.d.ts.map +1 -0
  154. package/sensor/dist/graph/dead-code.js +776 -0
  155. package/sensor/dist/graph/dead-code.js.map +1 -0
  156. package/sensor/dist/graph/dynamic-boundary-report.d.ts +121 -0
  157. package/sensor/dist/graph/dynamic-boundary-report.d.ts.map +1 -0
  158. package/sensor/dist/graph/dynamic-boundary-report.js +283 -0
  159. package/sensor/dist/graph/dynamic-boundary-report.js.map +1 -0
  160. package/sensor/dist/graph/index.d.ts +4 -0
  161. package/sensor/dist/graph/index.d.ts.map +1 -1
  162. package/sensor/dist/graph/index.js +16 -1
  163. package/sensor/dist/graph/index.js.map +1 -1
  164. package/sensor/dist/graph/named-symbol-flow.d.ts +125 -0
  165. package/sensor/dist/graph/named-symbol-flow.d.ts.map +1 -0
  166. package/sensor/dist/graph/named-symbol-flow.js +552 -0
  167. package/sensor/dist/graph/named-symbol-flow.js.map +1 -0
  168. package/sensor/dist/graph/queries.d.ts.map +1 -1
  169. package/sensor/dist/graph/queries.js +2 -0
  170. package/sensor/dist/graph/queries.js.map +1 -1
  171. package/sensor/dist/graph/symbol-lookup.d.ts +83 -0
  172. package/sensor/dist/graph/symbol-lookup.d.ts.map +1 -0
  173. package/sensor/dist/graph/symbol-lookup.js +181 -0
  174. package/sensor/dist/graph/symbol-lookup.js.map +1 -0
  175. package/sensor/dist/graph/traversal.d.ts +9 -0
  176. package/sensor/dist/graph/traversal.d.ts.map +1 -1
  177. package/sensor/dist/graph/traversal.js +62 -36
  178. package/sensor/dist/graph/traversal.js.map +1 -1
  179. package/sensor/dist/graph/type-hierarchy.d.ts +155 -0
  180. package/sensor/dist/graph/type-hierarchy.d.ts.map +1 -0
  181. package/sensor/dist/graph/type-hierarchy.js +382 -0
  182. package/sensor/dist/graph/type-hierarchy.js.map +1 -0
  183. package/sensor/dist/index.d.ts +272 -10
  184. package/sensor/dist/index.d.ts.map +1 -1
  185. package/sensor/dist/index.js +510 -34
  186. package/sensor/dist/index.js.map +1 -1
  187. package/sensor/dist/mcp/answer-freshness.d.ts +10 -0
  188. package/sensor/dist/mcp/answer-freshness.d.ts.map +1 -0
  189. package/sensor/dist/mcp/answer-freshness.js +68 -0
  190. package/sensor/dist/mcp/answer-freshness.js.map +1 -0
  191. package/sensor/dist/mcp/daemon-manager.d.ts +1 -1
  192. package/sensor/dist/mcp/daemon-manager.d.ts.map +1 -1
  193. package/sensor/dist/mcp/daemon-manager.js +17 -1
  194. package/sensor/dist/mcp/daemon-manager.js.map +1 -1
  195. package/sensor/dist/mcp/daemon-paths.d.ts +8 -0
  196. package/sensor/dist/mcp/daemon-paths.d.ts.map +1 -1
  197. package/sensor/dist/mcp/daemon-paths.js +65 -2
  198. package/sensor/dist/mcp/daemon-paths.js.map +1 -1
  199. package/sensor/dist/mcp/daemon-registry.d.ts +17 -4
  200. package/sensor/dist/mcp/daemon-registry.d.ts.map +1 -1
  201. package/sensor/dist/mcp/daemon-registry.js +133 -23
  202. package/sensor/dist/mcp/daemon-registry.js.map +1 -1
  203. package/sensor/dist/mcp/daemon.d.ts +26 -13
  204. package/sensor/dist/mcp/daemon.d.ts.map +1 -1
  205. package/sensor/dist/mcp/daemon.js +126 -32
  206. package/sensor/dist/mcp/daemon.js.map +1 -1
  207. package/sensor/dist/mcp/dynamic-boundaries.d.ts +1 -10
  208. package/sensor/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  209. package/sensor/dist/mcp/dynamic-boundaries.js +46 -42
  210. package/sensor/dist/mcp/dynamic-boundaries.js.map +1 -1
  211. package/sensor/dist/mcp/engine.d.ts +31 -9
  212. package/sensor/dist/mcp/engine.d.ts.map +1 -1
  213. package/sensor/dist/mcp/engine.js +190 -79
  214. package/sensor/dist/mcp/engine.js.map +1 -1
  215. package/sensor/dist/mcp/explore-dedup.d.ts +140 -0
  216. package/sensor/dist/mcp/explore-dedup.d.ts.map +1 -0
  217. package/sensor/dist/mcp/explore-dedup.js +239 -0
  218. package/sensor/dist/mcp/explore-dedup.js.map +1 -0
  219. package/sensor/dist/mcp/explore-diagnostics.d.ts +289 -0
  220. package/sensor/dist/mcp/explore-diagnostics.d.ts.map +1 -0
  221. package/sensor/dist/mcp/explore-diagnostics.js +558 -0
  222. package/sensor/dist/mcp/explore-diagnostics.js.map +1 -0
  223. package/sensor/dist/mcp/explore-session-state.d.ts +217 -0
  224. package/sensor/dist/mcp/explore-session-state.d.ts.map +1 -0
  225. package/sensor/dist/mcp/explore-session-state.js +322 -0
  226. package/sensor/dist/mcp/explore-session-state.js.map +1 -0
  227. package/sensor/dist/mcp/index-freshness-worker.d.ts +2 -0
  228. package/sensor/dist/mcp/index-freshness-worker.d.ts.map +1 -0
  229. package/sensor/dist/mcp/index-freshness-worker.js +30 -0
  230. package/sensor/dist/mcp/index-freshness-worker.js.map +1 -0
  231. package/sensor/dist/mcp/index-freshness.d.ts +7 -0
  232. package/sensor/dist/mcp/index-freshness.d.ts.map +1 -0
  233. package/sensor/dist/mcp/index-freshness.js +94 -0
  234. package/sensor/dist/mcp/index-freshness.js.map +1 -0
  235. package/sensor/dist/mcp/index.d.ts +3 -1
  236. package/sensor/dist/mcp/index.d.ts.map +1 -1
  237. package/sensor/dist/mcp/index.js +109 -12
  238. package/sensor/dist/mcp/index.js.map +1 -1
  239. package/sensor/dist/mcp/liveness-watchdog.d.ts +4 -1
  240. package/sensor/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  241. package/sensor/dist/mcp/liveness-watchdog.js +11 -2
  242. package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
  243. package/sensor/dist/mcp/project-lifecycle.d.ts +9 -0
  244. package/sensor/dist/mcp/project-lifecycle.d.ts.map +1 -0
  245. package/sensor/dist/mcp/project-lifecycle.js +182 -0
  246. package/sensor/dist/mcp/project-lifecycle.js.map +1 -0
  247. package/sensor/dist/mcp/proxy.d.ts.map +1 -1
  248. package/sensor/dist/mcp/proxy.js +15 -1
  249. package/sensor/dist/mcp/proxy.js.map +1 -1
  250. package/sensor/dist/mcp/query-pool.d.ts +2 -2
  251. package/sensor/dist/mcp/query-pool.d.ts.map +1 -1
  252. package/sensor/dist/mcp/query-pool.js +9 -7
  253. package/sensor/dist/mcp/query-pool.js.map +1 -1
  254. package/sensor/dist/mcp/query-worker.js +2 -2
  255. package/sensor/dist/mcp/query-worker.js.map +1 -1
  256. package/sensor/dist/mcp/server-instructions.d.ts +2 -2
  257. package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
  258. package/sensor/dist/mcp/server-instructions.js +13 -6
  259. package/sensor/dist/mcp/server-instructions.js.map +1 -1
  260. package/sensor/dist/mcp/session.d.ts +16 -0
  261. package/sensor/dist/mcp/session.d.ts.map +1 -1
  262. package/sensor/dist/mcp/session.js +36 -13
  263. package/sensor/dist/mcp/session.js.map +1 -1
  264. package/sensor/dist/mcp/startup-handshake.d.ts +3 -1
  265. package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
  266. package/sensor/dist/mcp/startup-handshake.js +10 -2
  267. package/sensor/dist/mcp/startup-handshake.js.map +1 -1
  268. package/sensor/dist/mcp/tools.d.ts +496 -38
  269. package/sensor/dist/mcp/tools.d.ts.map +1 -1
  270. package/sensor/dist/mcp/tools.js +4021 -772
  271. package/sensor/dist/mcp/tools.js.map +1 -1
  272. package/sensor/dist/mcp/writer-lock.d.ts +61 -0
  273. package/sensor/dist/mcp/writer-lock.d.ts.map +1 -0
  274. package/sensor/dist/mcp/writer-lock.js +260 -0
  275. package/sensor/dist/mcp/writer-lock.js.map +1 -0
  276. package/sensor/dist/project-config.d.ts +23 -0
  277. package/sensor/dist/project-config.d.ts.map +1 -1
  278. package/sensor/dist/project-config.js +39 -2
  279. package/sensor/dist/project-config.js.map +1 -1
  280. package/sensor/dist/resolution/alias-binding.d.ts +56 -0
  281. package/sensor/dist/resolution/alias-binding.d.ts.map +1 -0
  282. package/sensor/dist/resolution/alias-binding.js +127 -0
  283. package/sensor/dist/resolution/alias-binding.js.map +1 -0
  284. package/sensor/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  285. package/sensor/dist/resolution/c-fnptr-synthesizer.js +21 -19
  286. package/sensor/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  287. package/sensor/dist/resolution/callback-synthesizer.d.ts +23 -1
  288. package/sensor/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  289. package/sensor/dist/resolution/callback-synthesizer.js +356 -155
  290. package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
  291. package/sensor/dist/resolution/cpp-constructor.d.ts +4 -0
  292. package/sensor/dist/resolution/cpp-constructor.d.ts.map +1 -0
  293. package/sensor/dist/resolution/cpp-constructor.js +123 -0
  294. package/sensor/dist/resolution/cpp-constructor.js.map +1 -0
  295. package/sensor/dist/resolution/cpp-macro-visibility.d.ts +12 -0
  296. package/sensor/dist/resolution/cpp-macro-visibility.d.ts.map +1 -0
  297. package/sensor/dist/resolution/cpp-macro-visibility.js +342 -0
  298. package/sensor/dist/resolution/cpp-macro-visibility.js.map +1 -0
  299. package/sensor/dist/resolution/expo-router-synthesizer.d.ts +32 -0
  300. package/sensor/dist/resolution/expo-router-synthesizer.d.ts.map +1 -0
  301. package/sensor/dist/resolution/expo-router-synthesizer.js +189 -0
  302. package/sensor/dist/resolution/expo-router-synthesizer.js.map +1 -0
  303. package/sensor/dist/resolution/frameworks/csharp.d.ts.map +1 -1
  304. package/sensor/dist/resolution/frameworks/csharp.js +96 -2
  305. package/sensor/dist/resolution/frameworks/csharp.js.map +1 -1
  306. package/sensor/dist/resolution/frameworks/expo-router.d.ts +189 -0
  307. package/sensor/dist/resolution/frameworks/expo-router.d.ts.map +1 -0
  308. package/sensor/dist/resolution/frameworks/expo-router.js +785 -0
  309. package/sensor/dist/resolution/frameworks/expo-router.js.map +1 -0
  310. package/sensor/dist/resolution/frameworks/express.d.ts.map +1 -1
  311. package/sensor/dist/resolution/frameworks/express.js +251 -16
  312. package/sensor/dist/resolution/frameworks/express.js.map +1 -1
  313. package/sensor/dist/resolution/frameworks/index.d.ts +5 -0
  314. package/sensor/dist/resolution/frameworks/index.d.ts.map +1 -1
  315. package/sensor/dist/resolution/frameworks/index.js +29 -1
  316. package/sensor/dist/resolution/frameworks/index.js.map +1 -1
  317. package/sensor/dist/resolution/frameworks/java.d.ts.map +1 -1
  318. package/sensor/dist/resolution/frameworks/java.js +79 -47
  319. package/sensor/dist/resolution/frameworks/java.js.map +1 -1
  320. package/sensor/dist/resolution/frameworks/laravel.d.ts.map +1 -1
  321. package/sensor/dist/resolution/frameworks/laravel.js +294 -4
  322. package/sensor/dist/resolution/frameworks/laravel.js.map +1 -1
  323. package/sensor/dist/resolution/frameworks/nestjs.d.ts.map +1 -1
  324. package/sensor/dist/resolution/frameworks/nestjs.js +6 -14
  325. package/sensor/dist/resolution/frameworks/nestjs.js.map +1 -1
  326. package/sensor/dist/resolution/frameworks/nextjs.d.ts +76 -0
  327. package/sensor/dist/resolution/frameworks/nextjs.d.ts.map +1 -0
  328. package/sensor/dist/resolution/frameworks/nextjs.js +328 -0
  329. package/sensor/dist/resolution/frameworks/nextjs.js.map +1 -0
  330. package/sensor/dist/resolution/frameworks/object-literal.d.ts +32 -0
  331. package/sensor/dist/resolution/frameworks/object-literal.d.ts.map +1 -0
  332. package/sensor/dist/resolution/frameworks/object-literal.js +139 -0
  333. package/sensor/dist/resolution/frameworks/object-literal.js.map +1 -0
  334. package/sensor/dist/resolution/frameworks/package-deps.d.ts +14 -0
  335. package/sensor/dist/resolution/frameworks/package-deps.d.ts.map +1 -0
  336. package/sensor/dist/resolution/frameworks/package-deps.js +78 -0
  337. package/sensor/dist/resolution/frameworks/package-deps.js.map +1 -0
  338. package/sensor/dist/resolution/frameworks/python.d.ts.map +1 -1
  339. package/sensor/dist/resolution/frameworks/python.js +166 -0
  340. package/sensor/dist/resolution/frameworks/python.js.map +1 -1
  341. package/sensor/dist/resolution/frameworks/react-native.d.ts +26 -0
  342. package/sensor/dist/resolution/frameworks/react-native.d.ts.map +1 -1
  343. package/sensor/dist/resolution/frameworks/react-native.js +153 -14
  344. package/sensor/dist/resolution/frameworks/react-native.js.map +1 -1
  345. package/sensor/dist/resolution/frameworks/react-router.d.ts +46 -0
  346. package/sensor/dist/resolution/frameworks/react-router.d.ts.map +1 -0
  347. package/sensor/dist/resolution/frameworks/react-router.js +187 -0
  348. package/sensor/dist/resolution/frameworks/react-router.js.map +1 -0
  349. package/sensor/dist/resolution/frameworks/react.d.ts +2 -1
  350. package/sensor/dist/resolution/frameworks/react.d.ts.map +1 -1
  351. package/sensor/dist/resolution/frameworks/react.js +188 -139
  352. package/sensor/dist/resolution/frameworks/react.js.map +1 -1
  353. package/sensor/dist/resolution/frameworks/svelte.d.ts.map +1 -1
  354. package/sensor/dist/resolution/frameworks/svelte.js +6 -1
  355. package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
  356. package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts +36 -0
  357. package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts.map +1 -0
  358. package/sensor/dist/resolution/frameworks/sveltekit-router.js +156 -0
  359. package/sensor/dist/resolution/frameworks/sveltekit-router.js.map +1 -0
  360. package/sensor/dist/resolution/frameworks/swift.d.ts.map +1 -1
  361. package/sensor/dist/resolution/frameworks/swift.js +140 -99
  362. package/sensor/dist/resolution/frameworks/swift.js.map +1 -1
  363. package/sensor/dist/resolution/frameworks/tanstack-router.d.ts +84 -0
  364. package/sensor/dist/resolution/frameworks/tanstack-router.d.ts.map +1 -0
  365. package/sensor/dist/resolution/frameworks/tanstack-router.js +437 -0
  366. package/sensor/dist/resolution/frameworks/tanstack-router.js.map +1 -0
  367. package/sensor/dist/resolution/frameworks/vue-router.d.ts +82 -0
  368. package/sensor/dist/resolution/frameworks/vue-router.d.ts.map +1 -0
  369. package/sensor/dist/resolution/frameworks/vue-router.js +377 -0
  370. package/sensor/dist/resolution/frameworks/vue-router.js.map +1 -0
  371. package/sensor/dist/resolution/import-resolver.d.ts +38 -0
  372. package/sensor/dist/resolution/import-resolver.d.ts.map +1 -1
  373. package/sensor/dist/resolution/import-resolver.js +394 -28
  374. package/sensor/dist/resolution/import-resolver.js.map +1 -1
  375. package/sensor/dist/resolution/index.d.ts +89 -25
  376. package/sensor/dist/resolution/index.d.ts.map +1 -1
  377. package/sensor/dist/resolution/index.js +573 -148
  378. package/sensor/dist/resolution/index.js.map +1 -1
  379. package/sensor/dist/resolution/js-builtins.d.ts +13 -0
  380. package/sensor/dist/resolution/js-builtins.d.ts.map +1 -0
  381. package/sensor/dist/resolution/js-builtins.js +46 -0
  382. package/sensor/dist/resolution/js-builtins.js.map +1 -0
  383. package/sensor/dist/resolution/name-matcher.d.ts +99 -22
  384. package/sensor/dist/resolution/name-matcher.d.ts.map +1 -1
  385. package/sensor/dist/resolution/name-matcher.js +2220 -86
  386. package/sensor/dist/resolution/name-matcher.js.map +1 -1
  387. package/sensor/dist/resolution/next-router-synthesizer.d.ts +27 -0
  388. package/sensor/dist/resolution/next-router-synthesizer.d.ts.map +1 -0
  389. package/sensor/dist/resolution/next-router-synthesizer.js +113 -0
  390. package/sensor/dist/resolution/next-router-synthesizer.js.map +1 -0
  391. package/sensor/dist/resolution/path-aliases.d.ts +6 -4
  392. package/sensor/dist/resolution/path-aliases.d.ts.map +1 -1
  393. package/sensor/dist/resolution/path-aliases.js +125 -17
  394. package/sensor/dist/resolution/path-aliases.js.map +1 -1
  395. package/sensor/dist/resolution/react-router-synthesizer.d.ts +33 -0
  396. package/sensor/dist/resolution/react-router-synthesizer.d.ts.map +1 -0
  397. package/sensor/dist/resolution/react-router-synthesizer.js +126 -0
  398. package/sensor/dist/resolution/react-router-synthesizer.js.map +1 -0
  399. package/sensor/dist/resolution/resolver-pool.d.ts +2 -0
  400. package/sensor/dist/resolution/resolver-pool.d.ts.map +1 -1
  401. package/sensor/dist/resolution/resolver-pool.js +4 -0
  402. package/sensor/dist/resolution/resolver-pool.js.map +1 -1
  403. package/sensor/dist/resolution/strip-comments.d.ts +6 -0
  404. package/sensor/dist/resolution/strip-comments.d.ts.map +1 -1
  405. package/sensor/dist/resolution/strip-comments.js +58 -0
  406. package/sensor/dist/resolution/strip-comments.js.map +1 -1
  407. package/sensor/dist/resolution/sveltekit-synthesizer.d.ts +58 -0
  408. package/sensor/dist/resolution/sveltekit-synthesizer.d.ts.map +1 -0
  409. package/sensor/dist/resolution/sveltekit-synthesizer.js +173 -0
  410. package/sensor/dist/resolution/sveltekit-synthesizer.js.map +1 -0
  411. package/sensor/dist/resolution/swift-type-visibility.d.ts +52 -0
  412. package/sensor/dist/resolution/swift-type-visibility.d.ts.map +1 -0
  413. package/sensor/dist/resolution/swift-type-visibility.js +328 -0
  414. package/sensor/dist/resolution/swift-type-visibility.js.map +1 -0
  415. package/sensor/dist/resolution/synth-utils.d.ts +26 -0
  416. package/sensor/dist/resolution/synth-utils.d.ts.map +1 -0
  417. package/sensor/dist/resolution/synth-utils.js +75 -0
  418. package/sensor/dist/resolution/synth-utils.js.map +1 -0
  419. package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts +31 -0
  420. package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts.map +1 -0
  421. package/sensor/dist/resolution/tanstack-router-synthesizer.js +114 -0
  422. package/sensor/dist/resolution/tanstack-router-synthesizer.js.map +1 -0
  423. package/sensor/dist/resolution/tier-synthesizer.d.ts +54 -0
  424. package/sensor/dist/resolution/tier-synthesizer.d.ts.map +1 -0
  425. package/sensor/dist/resolution/tier-synthesizer.js +1294 -0
  426. package/sensor/dist/resolution/tier-synthesizer.js.map +1 -0
  427. package/sensor/dist/resolution/types.d.ts +78 -2
  428. package/sensor/dist/resolution/types.d.ts.map +1 -1
  429. package/sensor/dist/resolution/types.js +64 -0
  430. package/sensor/dist/resolution/types.js.map +1 -1
  431. package/sensor/dist/resolution/vue-router-synthesizer.d.ts +30 -0
  432. package/sensor/dist/resolution/vue-router-synthesizer.d.ts.map +1 -0
  433. package/sensor/dist/resolution/vue-router-synthesizer.js +115 -0
  434. package/sensor/dist/resolution/vue-router-synthesizer.js.map +1 -0
  435. package/sensor/dist/search/identifier-segments.d.ts +10 -0
  436. package/sensor/dist/search/identifier-segments.d.ts.map +1 -1
  437. package/sensor/dist/search/identifier-segments.js +29 -0
  438. package/sensor/dist/search/identifier-segments.js.map +1 -1
  439. package/sensor/dist/search/query-paths.d.ts +106 -0
  440. package/sensor/dist/search/query-paths.d.ts.map +1 -0
  441. package/sensor/dist/search/query-paths.js +513 -0
  442. package/sensor/dist/search/query-paths.js.map +1 -0
  443. package/sensor/dist/search/query-utils.d.ts +22 -2
  444. package/sensor/dist/search/query-utils.d.ts.map +1 -1
  445. package/sensor/dist/search/query-utils.js +56 -16
  446. package/sensor/dist/search/query-utils.js.map +1 -1
  447. package/sensor/dist/sync/watch-policy.d.ts +14 -0
  448. package/sensor/dist/sync/watch-policy.d.ts.map +1 -1
  449. package/sensor/dist/sync/watch-policy.js +19 -0
  450. package/sensor/dist/sync/watch-policy.js.map +1 -1
  451. package/sensor/dist/sync/watcher.d.ts +74 -35
  452. package/sensor/dist/sync/watcher.d.ts.map +1 -1
  453. package/sensor/dist/sync/watcher.js +281 -99
  454. package/sensor/dist/sync/watcher.js.map +1 -1
  455. package/sensor/dist/telemetry/index.d.ts +4 -2
  456. package/sensor/dist/telemetry/index.d.ts.map +1 -1
  457. package/sensor/dist/telemetry/index.js +92 -41
  458. package/sensor/dist/telemetry/index.js.map +1 -1
  459. package/sensor/dist/types.d.ts +18 -2
  460. package/sensor/dist/types.d.ts.map +1 -1
  461. package/sensor/dist/types.js +2 -0
  462. package/sensor/dist/types.js.map +1 -1
  463. package/sensor/dist/ui-server/api/deadcode.d.ts +84 -0
  464. package/sensor/dist/ui-server/api/deadcode.d.ts.map +1 -0
  465. package/sensor/dist/ui-server/api/deadcode.js +136 -0
  466. package/sensor/dist/ui-server/api/deadcode.js.map +1 -0
  467. package/sensor/dist/ui-server/api/effects.d.ts +94 -0
  468. package/sensor/dist/ui-server/api/effects.d.ts.map +1 -0
  469. package/sensor/dist/ui-server/api/effects.js +527 -0
  470. package/sensor/dist/ui-server/api/effects.js.map +1 -0
  471. package/sensor/dist/ui-server/api/entrypoints.d.ts +92 -0
  472. package/sensor/dist/ui-server/api/entrypoints.d.ts.map +1 -0
  473. package/sensor/dist/ui-server/api/entrypoints.js +361 -0
  474. package/sensor/dist/ui-server/api/entrypoints.js.map +1 -0
  475. package/sensor/dist/ui-server/api/events.d.ts +183 -0
  476. package/sensor/dist/ui-server/api/events.d.ts.map +1 -0
  477. package/sensor/dist/ui-server/api/events.js +430 -0
  478. package/sensor/dist/ui-server/api/events.js.map +1 -0
  479. package/sensor/dist/ui-server/api/file.d.ts +66 -0
  480. package/sensor/dist/ui-server/api/file.d.ts.map +1 -0
  481. package/sensor/dist/ui-server/api/file.js +235 -0
  482. package/sensor/dist/ui-server/api/file.js.map +1 -0
  483. package/sensor/dist/ui-server/api/filecode.d.ts +126 -0
  484. package/sensor/dist/ui-server/api/filecode.d.ts.map +1 -0
  485. package/sensor/dist/ui-server/api/filecode.js +205 -0
  486. package/sensor/dist/ui-server/api/filecode.js.map +1 -0
  487. package/sensor/dist/ui-server/api/flow.d.ts +226 -0
  488. package/sensor/dist/ui-server/api/flow.d.ts.map +1 -0
  489. package/sensor/dist/ui-server/api/flow.js +612 -0
  490. package/sensor/dist/ui-server/api/flow.js.map +1 -0
  491. package/sensor/dist/ui-server/api/hierarchy.d.ts +81 -0
  492. package/sensor/dist/ui-server/api/hierarchy.d.ts.map +1 -0
  493. package/sensor/dist/ui-server/api/hierarchy.js +92 -0
  494. package/sensor/dist/ui-server/api/hierarchy.js.map +1 -0
  495. package/sensor/dist/ui-server/api/index.d.ts +90 -0
  496. package/sensor/dist/ui-server/api/index.d.ts.map +1 -0
  497. package/sensor/dist/ui-server/api/index.js +332 -0
  498. package/sensor/dist/ui-server/api/index.js.map +1 -0
  499. package/sensor/dist/ui-server/api/map.d.ts +256 -0
  500. package/sensor/dist/ui-server/api/map.d.ts.map +1 -0
  501. package/sensor/dist/ui-server/api/map.js +760 -0
  502. package/sensor/dist/ui-server/api/map.js.map +1 -0
  503. package/sensor/dist/ui-server/api/node.d.ts +81 -0
  504. package/sensor/dist/ui-server/api/node.d.ts.map +1 -0
  505. package/sensor/dist/ui-server/api/node.js +388 -0
  506. package/sensor/dist/ui-server/api/node.js.map +1 -0
  507. package/sensor/dist/ui-server/api/nodes.d.ts +25 -0
  508. package/sensor/dist/ui-server/api/nodes.d.ts.map +1 -0
  509. package/sensor/dist/ui-server/api/nodes.js +45 -0
  510. package/sensor/dist/ui-server/api/nodes.js.map +1 -0
  511. package/sensor/dist/ui-server/api/program.d.ts +139 -0
  512. package/sensor/dist/ui-server/api/program.d.ts.map +1 -0
  513. package/sensor/dist/ui-server/api/program.js +315 -0
  514. package/sensor/dist/ui-server/api/program.js.map +1 -0
  515. package/sensor/dist/ui-server/api/respond.d.ts +76 -0
  516. package/sensor/dist/ui-server/api/respond.d.ts.map +1 -0
  517. package/sensor/dist/ui-server/api/respond.js +186 -0
  518. package/sensor/dist/ui-server/api/respond.js.map +1 -0
  519. package/sensor/dist/ui-server/api/route-roots.d.ts +39 -0
  520. package/sensor/dist/ui-server/api/route-roots.d.ts.map +1 -0
  521. package/sensor/dist/ui-server/api/route-roots.js +96 -0
  522. package/sensor/dist/ui-server/api/route-roots.js.map +1 -0
  523. package/sensor/dist/ui-server/api/routes.d.ts +59 -0
  524. package/sensor/dist/ui-server/api/routes.d.ts.map +1 -0
  525. package/sensor/dist/ui-server/api/routes.js +108 -0
  526. package/sensor/dist/ui-server/api/routes.js.map +1 -0
  527. package/sensor/dist/ui-server/api/screens.d.ts +117 -0
  528. package/sensor/dist/ui-server/api/screens.d.ts.map +1 -0
  529. package/sensor/dist/ui-server/api/screens.js +611 -0
  530. package/sensor/dist/ui-server/api/screens.js.map +1 -0
  531. package/sensor/dist/ui-server/api/search.d.ts +29 -0
  532. package/sensor/dist/ui-server/api/search.d.ts.map +1 -0
  533. package/sensor/dist/ui-server/api/search.js +232 -0
  534. package/sensor/dist/ui-server/api/search.js.map +1 -0
  535. package/sensor/dist/ui-server/api/session.d.ts +52 -0
  536. package/sensor/dist/ui-server/api/session.d.ts.map +1 -0
  537. package/sensor/dist/ui-server/api/session.js +196 -0
  538. package/sensor/dist/ui-server/api/session.js.map +1 -0
  539. package/sensor/dist/ui-server/api/source.d.ts +200 -0
  540. package/sensor/dist/ui-server/api/source.d.ts.map +1 -0
  541. package/sensor/dist/ui-server/api/source.js +422 -0
  542. package/sensor/dist/ui-server/api/source.js.map +1 -0
  543. package/sensor/dist/ui-server/api/stats.d.ts +27 -0
  544. package/sensor/dist/ui-server/api/stats.d.ts.map +1 -0
  545. package/sensor/dist/ui-server/api/stats.js +164 -0
  546. package/sensor/dist/ui-server/api/stats.js.map +1 -0
  547. package/sensor/dist/ui-server/api/steps.d.ts +274 -0
  548. package/sensor/dist/ui-server/api/steps.d.ts.map +1 -0
  549. package/sensor/dist/ui-server/api/steps.js +1544 -0
  550. package/sensor/dist/ui-server/api/steps.js.map +1 -0
  551. package/sensor/dist/ui-server/api/trail-store.d.ts +142 -0
  552. package/sensor/dist/ui-server/api/trail-store.d.ts.map +1 -0
  553. package/sensor/dist/ui-server/api/trail-store.js +325 -0
  554. package/sensor/dist/ui-server/api/trail-store.js.map +1 -0
  555. package/sensor/dist/ui-server/api/trails.d.ts +163 -0
  556. package/sensor/dist/ui-server/api/trails.d.ts.map +1 -0
  557. package/sensor/dist/ui-server/api/trails.js +369 -0
  558. package/sensor/dist/ui-server/api/trails.js.map +1 -0
  559. package/sensor/dist/ui-server/api/when.d.ts +133 -0
  560. package/sensor/dist/ui-server/api/when.d.ts.map +1 -0
  561. package/sensor/dist/ui-server/api/when.js +275 -0
  562. package/sensor/dist/ui-server/api/when.js.map +1 -0
  563. package/sensor/dist/ui-server/api/wire.d.ts +179 -0
  564. package/sensor/dist/ui-server/api/wire.d.ts.map +1 -0
  565. package/sensor/dist/ui-server/api/wire.js +231 -0
  566. package/sensor/dist/ui-server/api/wire.js.map +1 -0
  567. package/sensor/dist/ui-server/assets.d.ts +40 -0
  568. package/sensor/dist/ui-server/assets.d.ts.map +1 -0
  569. package/sensor/dist/ui-server/assets.js +110 -0
  570. package/sensor/dist/ui-server/assets.js.map +1 -0
  571. package/sensor/dist/ui-server/constants.d.ts +32 -0
  572. package/sensor/dist/ui-server/constants.d.ts.map +1 -0
  573. package/sensor/dist/ui-server/constants.js +35 -0
  574. package/sensor/dist/ui-server/constants.js.map +1 -0
  575. package/sensor/dist/ui-server/highlight/index.d.ts +113 -0
  576. package/sensor/dist/ui-server/highlight/index.d.ts.map +1 -0
  577. package/sensor/dist/ui-server/highlight/index.js +307 -0
  578. package/sensor/dist/ui-server/highlight/index.js.map +1 -0
  579. package/sensor/dist/ui-server/highlight/languages.d.ts +36 -0
  580. package/sensor/dist/ui-server/highlight/languages.d.ts.map +1 -0
  581. package/sensor/dist/ui-server/highlight/languages.js +50 -0
  582. package/sensor/dist/ui-server/highlight/languages.js.map +1 -0
  583. package/sensor/dist/ui-server/index.d.ts +92 -0
  584. package/sensor/dist/ui-server/index.d.ts.map +1 -0
  585. package/sensor/dist/ui-server/index.js +399 -0
  586. package/sensor/dist/ui-server/index.js.map +1 -0
  587. package/sensor/dist/ui-server/open-browser.d.ts +31 -0
  588. package/sensor/dist/ui-server/open-browser.d.ts.map +1 -0
  589. package/sensor/dist/ui-server/open-browser.js +79 -0
  590. package/sensor/dist/ui-server/open-browser.js.map +1 -0
  591. package/sensor/dist/ui-server/security.d.ts +127 -0
  592. package/sensor/dist/ui-server/security.d.ts.map +1 -0
  593. package/sensor/dist/ui-server/security.js +332 -0
  594. package/sensor/dist/ui-server/security.js.map +1 -0
  595. package/sensor/dist/ui-server/static.d.ts +45 -0
  596. package/sensor/dist/ui-server/static.d.ts.map +1 -0
  597. package/sensor/dist/ui-server/static.js +165 -0
  598. package/sensor/dist/ui-server/static.js.map +1 -0
  599. package/sensor/dist/utils.d.ts +14 -2
  600. package/sensor/dist/utils.d.ts.map +1 -1
  601. package/sensor/dist/utils.js +25 -10
  602. package/sensor/dist/utils.js.map +1 -1
  603. package/src/cli-help.mjs +4 -2
  604. package/src/sensor-adapter.mjs +5 -1
  605. package/src/todo-cli.mjs +88 -11
@@ -61,6 +61,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
61
61
  // launcher is (almost certainly) still alive. A launcher killed mid-startup
62
62
  // otherwise blinds the PPID watchdog forever (#1185) — see early-ppid.ts.
63
63
  require("../mcp/early-ppid");
64
+ // The browser viewer is not part of a release yet (see viewer-gate). Refuse
65
+ // `ui` / `web` — also as `help ui` or `ui --help` — before any startup work,
66
+ // unless LATTICE_SENSOR_UI=1 opts in.
67
+ const viewer_gate_1 = require("./viewer-gate");
68
+ {
69
+ const viewerCommand = (0, viewer_gate_1.requestedViewerCommand)(process.argv.slice(2));
70
+ if (viewerCommand && !(0, viewer_gate_1.viewerEnabled)()) {
71
+ process.stderr.write(`error: 'lattice sensor ${viewerCommand}' is not in this release yet. The browser viewer is coming in an upcoming release.\n`);
72
+ process.exit(1);
73
+ }
74
+ }
64
75
  // Persist V8 compile artifacts across runs (Node ≥22.8). Every invocation —
65
76
  // and every worker thread, which re-requires the whole extraction module
66
77
  // graph — skips recompiling unchanged sources. Worth hundreds of ms of
@@ -87,6 +98,11 @@ const command_supervision_1 = require("./command-supervision");
87
98
  const exact_traversal_1 = require("./exact-traversal");
88
99
  const extraction_version_1 = require("../extraction/extraction-version");
89
100
  const telemetry_1 = require("../telemetry");
101
+ // Value import, but dependency-free by design so `--help` text can name the
102
+ // default port without dragging node:http into every other subcommand; the
103
+ // server itself is loaded lazily inside the `ui` action. See ui-server/constants.
104
+ const constants_1 = require("../ui-server/constants");
105
+ const symbol_lookup_1 = require("../graph/symbol-lookup");
90
106
  // Decided once, before `--color`/`--no-color` are stripped from argv below
91
107
  // (#1281). Piped/redirected stdout, NO_COLOR, or --no-color -> plain output.
92
108
  const COLORS_ENABLED = (0, color_1.ansiColorsEnabled)();
@@ -355,11 +371,31 @@ function main() {
355
371
  function warn(message) {
356
372
  console.log(chalk.yellow((0, glyphs_1.getGlyphs)().warn) + ' ' + message);
357
373
  }
374
+ /** "not found" (+ optional did-you-mean) when no exact symbol matches. */
375
+ function formatSymbolNotFound(symbol, fuzzyNames) {
376
+ const suggestions = [...new Set(fuzzyNames.filter((n) => n !== symbol))].slice(0, 3);
377
+ if (suggestions.length === 0)
378
+ return `Symbol "${symbol}" not found`;
379
+ return `Symbol "${symbol}" not found — did you mean: ${suggestions.join(', ')}?`;
380
+ }
381
+ /** Compact node shape retained by the CLI's existing JSON lists. */
382
+ function cliNode(node) {
383
+ return { name: node.name, kind: node.kind, filePath: node.filePath, startLine: node.startLine };
384
+ }
385
+ /** Attribute a group's edges to every overload of this definition. */
386
+ function cliDefinition(group) {
387
+ const head = group[0];
388
+ return {
389
+ definition: { ...cliNode(head), id: head.id, qualifiedName: head.qualifiedName, language: head.language },
390
+ roots: group.map((node) => node.id),
391
+ };
392
+ }
358
393
  /**
359
394
  * Print indexing results using clack log methods
360
395
  */
361
396
  function printIndexResult(clack, result, projectPath) {
362
397
  const hasErrors = result.filesErrored > 0;
398
+ const parseWarnings = result.errors.filter((e) => e.code === 'parse_error' && e.severity === 'warning');
363
399
  // Surface non-file-level failures (e.g. lock-acquisition failure
364
400
  // when another indexer is running) before the file-count branches.
365
401
  // Without this the CLI falls through to "No files found to index",
@@ -384,6 +420,10 @@ function main() {
384
420
  clack.log.success(`Indexed ${formatNumber(result.filesIndexed)} files`);
385
421
  }
386
422
  clack.log.info(`${formatNumber(result.nodesCreated)} nodes, ${formatNumber(result.edgesCreated)} edges in ${formatDuration(result.durationMs)}`);
423
+ // Warning-only parse failures keep indexing successful, but must be visible.
424
+ for (const warning of parseWarnings) {
425
+ clack.log.warn(warning.message);
426
+ }
387
427
  // A PARTIAL index (files silently dropped mid-pipeline) must not pass
388
428
  // as a clean run — it's the difference between "indexed the repo" and
389
429
  // "indexed most of the repo, quietly". Only the completeness
@@ -392,10 +432,33 @@ function main() {
392
432
  for (const w of result.errors.filter((e) => e.code === 'index_partial')) {
393
433
  clack.log.warn(w.message);
394
434
  }
435
+ // Files salvaged from comment-stripped source after repeated parser
436
+ // failures are indexed but possibly incomplete — say so here, or the run
437
+ // reads as fully clean and the index quietly disagrees with a later
438
+ // re-parse of the same bytes (#1565).
439
+ const salvaged = result.errors.filter((e) => e.code === 'salvaged_stripped');
440
+ if (salvaged.length > 0) {
441
+ const sample = salvaged.slice(0, 3).map((e) => e.filePath).filter(Boolean).join(', ');
442
+ const more = salvaged.length > 3 ? ', ...' : '';
443
+ clack.log.warn(`${formatNumber(salvaged.length)} file(s) indexed from comment-stripped source after repeated parse failures ${(0, glyphs_1.getGlyphs)().dash} symbols may be incomplete (${sample}${more})`);
444
+ }
395
445
  }
396
446
  else if (hasErrors) {
397
447
  clack.log.error(`Indexing failed ${(0, glyphs_1.getGlyphs)().dash} all ${formatNumber(result.filesErrored)} files had errors`);
398
448
  }
449
+ else if (result.filesSkippedUnsupported) {
450
+ // A project LatticeSensor has no grammar for used to be indistinguishable from
451
+ // an empty one: same message, same `complete` state, same exit 0. Say which
452
+ // files were there and that the graph is empty on purpose, so nobody — and
453
+ // no agent trusting the graph — reads silence as "this code doesn't exist"
454
+ // (#1502).
455
+ const top = (result.topUnsupportedExtensions ?? [])
456
+ .map(e => `${e.ext} (${formatNumber(e.count)})`)
457
+ .join(', ');
458
+ clack.log.warn(`No supported source files found ${(0, glyphs_1.getGlyphs)().dash} ${formatNumber(result.filesSkippedUnsupported)} file(s) present, none in a language LatticeSensor indexes`
459
+ + (top ? `: ${top}` : ''));
460
+ clack.log.info('LatticeSensor is inactive for this workspace — searches will return nothing. Use your own file tools here.');
461
+ }
399
462
  else {
400
463
  clack.log.warn('No files found to index');
401
464
  }
@@ -428,9 +491,17 @@ function main() {
428
491
  }
429
492
  }
430
493
  else if (projectPath) {
431
- const logPath = path.join((0, directory_1.getLatticeSensorDir)(projectPath), 'errors.log');
432
- if (fs.existsSync(logPath)) {
433
- fs.unlinkSync(logPath);
494
+ // No hard errors. Salvaged-file warnings still belong in the log — it
495
+ // carries the per-file detail behind the one-line summary above.
496
+ if (result.errors.some((e) => e.code === 'salvaged_stripped')) {
497
+ writeErrorLog(projectPath, result.errors);
498
+ clack.log.info('See .lattice/sensor/errors.log for details');
499
+ }
500
+ else {
501
+ const logPath = path.join((0, directory_1.getLatticeSensorDir)(projectPath), 'errors.log');
502
+ if (fs.existsSync(logPath)) {
503
+ fs.unlinkSync(logPath);
504
+ }
434
505
  }
435
506
  }
436
507
  }
@@ -550,16 +621,14 @@ function main() {
550
621
  // Commands
551
622
  // =============================================================================
552
623
  /**
553
- * lattice sensor init [path]
624
+ * The `init` flow — shared by `lattice sensor init` and `lattice sensor install --init`
625
+ * (#1578): refuse an unsafe root, create `.lattice/sensor/`, build the initial
626
+ * index under supervision, then the post-index offers. `yes` makes every
627
+ * offer non-interactive (defaults only), so a container / CI bootstrap never
628
+ * blocks on a prompt. An unsafe root sets `process.exitCode = 1` and returns
629
+ * (no `--force` is implied by any caller); an index failure exits 1.
554
630
  */
555
- program
556
- .command('init [path]')
557
- .description('Initialize LatticeSensor in a project directory and build the initial index')
558
- .option('-i, --index', 'Deprecated: indexing now runs by default; flag accepted for backward compatibility')
559
- .option('-f, --force', 'Initialize even if the path looks like your home directory or a filesystem root')
560
- .option('-v, --verbose', 'Show detailed worker lifecycle and memory info')
561
- .action(async (pathArg, options) => {
562
- const projectPath = path.resolve(pathArg || process.cwd());
631
+ async function runInit(projectPath, options) {
563
632
  const clack = await importESM('@clack/prompts');
564
633
  clack.intro('Initializing LatticeSensor');
565
634
  try {
@@ -580,9 +649,27 @@ function main() {
580
649
  clack.outro('');
581
650
  return;
582
651
  }
652
+ if ((0, directory_1.hasForeignDbFile)(projectPath)) {
653
+ const dbFile = path.join((0, directory_1.getLatticeSensorDir)(projectPath), 'sensor.db');
654
+ clack.log.error(`${dbFile} is not a SQLite database, so it cannot be rebuilt in place.`);
655
+ clack.log.info('Move or delete that file, then run "lattice sensor init" again.');
656
+ clack.outro('');
657
+ process.exitCode = 1;
658
+ return;
659
+ }
660
+ if ((0, directory_1.hasSchemalessDb)(projectPath)) {
661
+ clack.log.warn(`Found a sensor.db without the lattice sensor schema in ${(0, directory_1.getLatticeSensorDir)(projectPath)} (left by an interrupted init?) — rebuilding it.`);
662
+ }
583
663
  const { default: LatticeSensor, getDatabasePath } = await loadLatticeSensor();
584
664
  const cg = await LatticeSensor.init(projectPath, { index: false });
585
665
  clack.log.success(`Initialized in ${projectPath}`);
666
+ // A fresh index on a Windows drive under WSL gets its own directory (#995).
667
+ // It isn't the documented name, so say where it went and why.
668
+ const dataDir = path.basename((0, directory_1.getLatticeSensorDir)(projectPath));
669
+ if (dataDir !== (0, directory_1.latticeSensorRelativeDir)()) {
670
+ clack.log.info(`The index is in ${dataDir}/: this project is on a Windows drive, so WSL keeps its own index ` +
671
+ `rather than share ${directory_1.DEFAULT_LATTICE_SENSOR_DIR}/ with LatticeSensor on Windows. Set LATTICE_SENSOR_DIR to choose the name yourself.`);
672
+ }
586
673
  // Indexing runs by default now. The legacy -i/--index flag is still
587
674
  // accepted (so existing muscle memory and scripts don't break) but is a
588
675
  // no-op — initializing always builds the initial index.
@@ -614,8 +701,10 @@ function main() {
614
701
  // An empty graph at a git super-repo usually means `.gitignore` excludes
615
702
  // the child repos that hold the code — surface them and offer to opt in
616
703
  // rather than leaving the user with a silent 0-node "Done". (#1156)
704
+ // Under --yes the offer prints its one-line opt-in snippet instead of
705
+ // prompting (same as a non-TTY run).
617
706
  if (result.nodesCreated === 0) {
618
- await offerIndexIgnoredRepos(clack, projectPath, runIndex, { interactive: true });
707
+ await offerIndexIgnoredRepos(clack, projectPath, runIndex, { interactive: !options.yes });
619
708
  }
620
709
  clack.outro('Done');
621
710
  cg.destroy();
@@ -624,6 +713,19 @@ function main() {
624
713
  clack.log.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
625
714
  process.exit(1);
626
715
  }
716
+ }
717
+ /**
718
+ * lattice sensor init [path]
719
+ */
720
+ program
721
+ .command('init [path]')
722
+ .description('Initialize LatticeSensor in a project directory and build the initial index')
723
+ .option('-i, --index', 'Deprecated: indexing now runs by default; flag accepted for backward compatibility')
724
+ .option('-f, --force', 'Initialize even if the path looks like your home directory or a filesystem root')
725
+ .option('-v, --verbose', 'Show detailed worker lifecycle and memory info')
726
+ .option('-y, --yes', 'Non-interactive: skip every prompt and take the defaults (for scripts / CI / container bootstraps)')
727
+ .action(async (pathArg, options) => {
728
+ await runInit(path.resolve(pathArg || process.cwd()), options);
627
729
  });
628
730
  /**
629
731
  * lattice sensor uninit [path]
@@ -688,7 +790,13 @@ function main() {
688
790
  .option('-q, --quiet', 'Suppress progress output')
689
791
  .option('-v, --verbose', 'Show detailed worker lifecycle and memory info')
690
792
  .action(async (pathArg, options) => {
691
- const projectPath = resolveProjectPath(pathArg);
793
+ // An EXPLICIT path names the project to rebuild — it is never a hint to go
794
+ // looking for one. resolveProjectPath walks up to the nearest initialized
795
+ // ancestor, which is right for `lattice sensor query` run from a subdirectory,
796
+ // but for a full re-index it silently rebuilt the parent's graph under a
797
+ // normal "Done" when <path> had no index of its own (#1524). Only a bare
798
+ // `lattice sensor index` (cwd) may resolve upward.
799
+ const projectPath = pathArg ? path.resolve(pathArg) : resolveProjectPath();
692
800
  try {
693
801
  // Don't (re)index your home directory / a filesystem root (#845). --force
694
802
  // doubles as the override.
@@ -699,66 +807,97 @@ function main() {
699
807
  }
700
808
  if (!(0, directory_1.isInitialized)(projectPath)) {
701
809
  error(`LatticeSensor not initialized in ${projectPath}`);
702
- info('Run "lattice sensor init" first');
810
+ const ancestor = pathArg ? resolveProjectPath(pathArg) : projectPath;
811
+ if (ancestor !== projectPath) {
812
+ info(`The nearest initialized project is ${ancestor} — pass that path to rebuild it, or run "lattice sensor init" in ${projectPath} to index it on its own.`);
813
+ }
814
+ else {
815
+ info('Run "lattice sensor init" first');
816
+ }
703
817
  process.exit(1);
704
818
  }
705
- const { default: LatticeSensor, getDatabasePath } = await loadLatticeSensor();
706
- // `index` is a FULL re-index — identical to a fresh `init`. RECREATE the
707
- // database from scratch (discard .lattice/sensor/sensor.db + its WAL) rather
708
- // than opening the old graph and DELETE-ing every row. The clear-then-index
709
- // approach reported "0 nodes" without the clear (#874); the recreate keeps
710
- // that fixed AND avoids the failure mode where, on a large or pre-fix
711
- // poisoned index, the per-row FTS delete churn wedged the main thread long
712
- // enough to trip the liveness watchdog before scanning even began (#1067).
713
- // recreate() hands back a fresh, empty instance — no clear() needed. For
714
- // fast incremental updates use `sync`.
715
- const cg = await LatticeSensor.recreate(projectPath);
716
- // Supervise the indexer: self-terminate if orphaned (parent shim killed)
717
- // or if the main thread wedges — neither was guarded on this path (#999).
718
- // The DB + WAL paths let the liveness watchdog tell a slow store on
719
- // degraded storage from a true wedge (#1231).
720
- const dbPath = getDatabasePath(projectPath);
721
- const supervision = (0, command_supervision_1.installCommandSupervision)('index', { progressPaths: [dbPath, `${dbPath}-wal`] });
819
+ const { tryAcquireWriterLock, releaseWriterLock, writerLockHeldMessage } = await Promise.resolve().then(() => __importStar(require('../mcp/writer-lock')));
820
+ const rebuild = tryAcquireWriterLock(projectPath, 'rebuild', 'rebuild.pid');
821
+ if (rebuild.kind === 'taken')
822
+ throw new Error('Another index rebuild is already in progress.');
722
823
  try {
723
- if (options.quiet) {
724
- // Quiet mode: no UI, just run against the freshly-recreated graph.
725
- const result = await cg.indexAll();
726
- if (!result.success)
727
- process.exit(1);
728
- cg.destroy();
729
- return;
824
+ // A live MCP daemon keeps SQLite handles open. Verify it by its socket
825
+ // before stopping it so a stale pidfile can never signal another process.
826
+ const { stopDaemonAt } = await Promise.resolve().then(() => __importStar(require('../mcp/daemon-registry')));
827
+ const daemonStop = await stopDaemonAt(fs.realpathSync(projectPath), { preserveUnverified: true });
828
+ if (daemonStop.outcome === 'unverified' || daemonStop.outcome === 'still-running') {
829
+ throw new Error('Could not verify that the active LatticeSensor daemon has stopped. Run `lattice sensor daemon stop` to stop it, then retry `lattice sensor index`.');
730
830
  }
731
- const clack = await importESM('@clack/prompts');
732
- clack.intro('Indexing project');
733
- // A closure so a re-index (after opting gitignored child repos in, #1156)
734
- // renders identically. Supervision already wraps the whole command.
735
- const renderIndex = async () => {
736
- if (options.verbose) {
737
- return await cg.indexAll({ onProgress: createVerboseProgress(), verbose: true });
831
+ // Keep the writer slot through recreation AND indexing. A reconnecting
832
+ // proxy/daemon must not open the replacement database halfway through.
833
+ const writer = tryAcquireWriterLock(projectPath, 'rebuild');
834
+ if (writer.kind === 'taken')
835
+ throw new Error(writerLockHeldMessage(writer.existing, writer.pidPath));
836
+ try {
837
+ const { default: LatticeSensor, getDatabasePath } = await loadLatticeSensor();
838
+ // `index` is a FULL re-index — identical to a fresh `init`. RECREATE the
839
+ // database from scratch (discard .lattice/sensor/sensor.db + its WAL) rather
840
+ // than opening the old graph and DELETE-ing every row. The clear-then-index
841
+ // approach reported "0 nodes" without the clear (#874); the recreate keeps
842
+ // that fixed AND avoids the failure mode where, on a large or pre-fix
843
+ // poisoned index, the per-row FTS delete churn wedged the main thread long
844
+ // enough to trip the liveness watchdog before scanning even began (#1067).
845
+ // recreate() hands back a fresh, empty instance — no clear() needed. For
846
+ // fast incremental updates use `sync`.
847
+ const cg = await LatticeSensor.recreate(projectPath);
848
+ // Supervise the indexer: self-terminate if orphaned (parent shim killed)
849
+ // or if the main thread wedges — neither was guarded on this path (#999).
850
+ // The DB + WAL paths let the liveness watchdog tell a slow store on
851
+ // degraded storage from a true wedge (#1231).
852
+ const dbPath = getDatabasePath(projectPath);
853
+ const supervision = (0, command_supervision_1.installCommandSupervision)('index', { progressPaths: [dbPath, `${dbPath}-wal`] });
854
+ try {
855
+ if (options.quiet) {
856
+ // Quiet mode: no UI, just run against the freshly-recreated graph.
857
+ const result = await cg.indexAll();
858
+ if (!result.success)
859
+ process.exit(1);
860
+ return;
861
+ }
862
+ const clack = await importESM('@clack/prompts');
863
+ clack.intro('Indexing project');
864
+ // A closure so a re-index (after opting gitignored child repos in, #1156)
865
+ // renders identically. Supervision already wraps the whole command.
866
+ const renderIndex = async () => {
867
+ if (options.verbose) {
868
+ return await cg.indexAll({ onProgress: createVerboseProgress(), verbose: true });
869
+ }
870
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
871
+ const progress = (0, shimmer_progress_1.createShimmerProgress)();
872
+ const r = await cg.indexAll({ onProgress: progress.onProgress });
873
+ await progress.stop();
874
+ return r;
875
+ };
876
+ const result = await renderIndex();
877
+ printIndexResult(clack, result, projectPath);
878
+ await recordIndexTelemetry(cg, result);
879
+ // Empty graph at a git super-repo → likely `.gitignore`d child repos;
880
+ // name them and offer to opt in instead of a silent 0-node result (#1156).
881
+ let finalResult = result;
882
+ if (result.nodesCreated === 0) {
883
+ finalResult = (await offerIndexIgnoredRepos(clack, projectPath, renderIndex, { interactive: true })) ?? result;
884
+ }
885
+ if (!finalResult.success) {
886
+ process.exit(1);
887
+ }
888
+ clack.outro('Done');
889
+ }
890
+ finally {
891
+ supervision.stop();
892
+ cg.destroy();
738
893
  }
739
- process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
740
- const progress = (0, shimmer_progress_1.createShimmerProgress)();
741
- const r = await cg.indexAll({ onProgress: progress.onProgress });
742
- await progress.stop();
743
- return r;
744
- };
745
- const result = await renderIndex();
746
- printIndexResult(clack, result, projectPath);
747
- await recordIndexTelemetry(cg, result);
748
- // Empty graph at a git super-repo → likely `.gitignore`d child repos;
749
- // name them and offer to opt in instead of a silent 0-node result (#1156).
750
- let finalResult = result;
751
- if (result.nodesCreated === 0) {
752
- finalResult = (await offerIndexIgnoredRepos(clack, projectPath, renderIndex, { interactive: true })) ?? result;
753
894
  }
754
- if (!finalResult.success) {
755
- process.exit(1);
895
+ finally {
896
+ releaseWriterLock(projectPath);
756
897
  }
757
- clack.outro('Done');
758
- cg.destroy();
759
898
  }
760
899
  finally {
761
- supervision.stop();
900
+ releaseWriterLock(projectPath, 'rebuild.pid');
762
901
  }
763
902
  }
764
903
  catch (err) {
@@ -784,36 +923,43 @@ function main() {
784
923
  }
785
924
  const { default: LatticeSensor } = await loadLatticeSensor();
786
925
  const cg = await LatticeSensor.open(projectPath);
787
- if (options.quiet) {
788
- await cg.sync();
789
- cg.destroy();
790
- return;
791
- }
792
- const clack = await importESM('@clack/prompts');
793
- clack.intro('Syncing LatticeSensor');
794
- process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
795
- const progress = (0, shimmer_progress_1.createShimmerProgress)();
796
- const result = await cg.sync({
797
- onProgress: progress.onProgress,
798
- });
799
- await progress.stop();
800
- const totalChanges = result.filesAdded + result.filesModified + result.filesRemoved;
801
- if (totalChanges === 0) {
802
- clack.log.info('Already up to date');
926
+ try {
927
+ if (options.quiet) {
928
+ await cg.sync();
929
+ return;
930
+ }
931
+ const clack = await importESM('@clack/prompts');
932
+ clack.intro('Syncing LatticeSensor');
933
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
934
+ const progress = (0, shimmer_progress_1.createShimmerProgress)();
935
+ const result = await cg.sync({
936
+ onProgress: progress.onProgress,
937
+ }).finally(() => progress.stop());
938
+ const totalChanges = result.filesAdded + result.filesModified + result.filesRemoved;
939
+ if (totalChanges === 0 && !result.pendingRefsProcessed) {
940
+ clack.log.info('Already up to date');
941
+ }
942
+ else if (totalChanges > 0) {
943
+ clack.log.success(`Synced ${formatNumber(totalChanges)} changed files`);
944
+ const details = [];
945
+ if (result.filesAdded > 0)
946
+ details.push(`Added: ${result.filesAdded}`);
947
+ if (result.filesModified > 0)
948
+ details.push(`Modified: ${result.filesModified}`);
949
+ if (result.filesRemoved > 0)
950
+ details.push(`Removed: ${result.filesRemoved}`);
951
+ clack.log.info(`${details.join(', ')} ${(0, glyphs_1.getGlyphs)().dash} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
952
+ }
953
+ if (result.pendingRefsProcessed) {
954
+ const unresolved = result.pendingRefsUnresolved
955
+ ? ` (${formatNumber(result.pendingRefsUnresolved)} unresolved)` : '';
956
+ clack.log.info(`Resolved ${formatNumber(result.pendingRefsResolved ?? 0)} pending references${unresolved}`);
957
+ }
958
+ clack.outro('Done');
803
959
  }
804
- else {
805
- clack.log.success(`Synced ${formatNumber(totalChanges)} changed files`);
806
- const details = [];
807
- if (result.filesAdded > 0)
808
- details.push(`Added: ${result.filesAdded}`);
809
- if (result.filesModified > 0)
810
- details.push(`Modified: ${result.filesModified}`);
811
- if (result.filesRemoved > 0)
812
- details.push(`Removed: ${result.filesRemoved}`);
813
- clack.log.info(`${details.join(', ')} ${(0, glyphs_1.getGlyphs)().dash} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
960
+ finally {
961
+ cg.destroy();
814
962
  }
815
- clack.outro('Done');
816
- cg.destroy();
817
963
  }
818
964
  catch (err) {
819
965
  if (!options.quiet) {
@@ -1051,20 +1197,27 @@ function main() {
1051
1197
  const cg = await LatticeSensor.open(projectPath);
1052
1198
  const limit = parseInt(options.limit || '10', 10);
1053
1199
  const rawResults = cg.searchNodes(search, {
1054
- limit,
1200
+ // Fetch one extra row so the CLI can report a cut without changing the
1201
+ // long-standing bare-array contract of `query --json` (#1639).
1202
+ limit: limit + 1,
1055
1203
  kinds: options.kind ? [options.kind] : undefined,
1056
1204
  });
1057
1205
  // Mirror the MCP search down-rank so the CLI also surfaces the
1058
1206
  // hand-written implementation before protobuf/gRPC scaffolding
1059
1207
  // when both share a name. See extraction/generated-detection.ts.
1060
- const { isGeneratedFile } = await Promise.resolve().then(() => __importStar(require('../extraction/generated-detection')));
1061
- const results = [...rawResults].sort((a, b) => {
1062
- const aGen = isGeneratedFile(a.node.filePath) ? 1 : 0;
1063
- const bGen = isGeneratedFile(b.node.filePath) ? 1 : 0;
1208
+ const isGen = cg.generatedFilePredicate(rawResults.map((r) => r.node.filePath));
1209
+ const rankedResults = [...rawResults].sort((a, b) => {
1210
+ const aGen = isGen(a.node.filePath) ? 1 : 0;
1211
+ const bGen = isGen(b.node.filePath) ? 1 : 0;
1064
1212
  return aGen - bGen;
1065
1213
  });
1214
+ const truncated = rankedResults.length > limit;
1215
+ const results = rankedResults.slice(0, limit);
1216
+ const truncationMessage = `Results truncated at ${limit}; pass --limit to widen.`;
1066
1217
  if (options.json) {
1067
1218
  console.log(JSON.stringify(results, null, 2));
1219
+ if (truncated)
1220
+ console.error(truncationMessage);
1068
1221
  }
1069
1222
  else {
1070
1223
  if (results.length === 0) {
@@ -1088,6 +1241,8 @@ function main() {
1088
1241
  }
1089
1242
  console.log();
1090
1243
  }
1244
+ if (truncated)
1245
+ console.log(chalk.dim(truncationMessage));
1091
1246
  }
1092
1247
  }
1093
1248
  cg.destroy();
@@ -1136,6 +1291,60 @@ function main() {
1136
1291
  process.exit(1);
1137
1292
  }
1138
1293
  });
1294
+ /**
1295
+ * lattice sensor context <task...>
1296
+ *
1297
+ * The CLI face of the public `buildContext` API (ContextBuilder): FTS entry
1298
+ * points + graph expansion + code blocks, formatted as markdown or JSON.
1299
+ * Advertised in the usage header since the first release but never actually
1300
+ * registered (#1611); external integrations (e.g. Memorix) invoke it as
1301
+ * `lattice sensor context --path <root> --format json --max-nodes 8 --no-code <task>`.
1302
+ */
1303
+ program
1304
+ .command('context <task...>')
1305
+ .description('Build context for a task: relevant symbols, relationships, and code blocks')
1306
+ .option('-p, --path <path>', 'Project path')
1307
+ .option('-f, --format <format>', 'Output format: markdown or json', 'markdown')
1308
+ .option('-n, --max-nodes <number>', 'Maximum number of symbols to include')
1309
+ .option('--no-code', 'Omit code blocks (structure only)')
1310
+ .action(async (taskParts, options) => {
1311
+ const projectPath = resolveProjectPath(options.path);
1312
+ const format = options.format ?? 'markdown';
1313
+ if (format !== 'markdown' && format !== 'json') {
1314
+ error(`Unknown format "${options.format}" — use "markdown" or "json".`);
1315
+ process.exit(1);
1316
+ }
1317
+ let maxNodes;
1318
+ if (options.maxNodes !== undefined) {
1319
+ maxNodes = parseInt(options.maxNodes, 10);
1320
+ if (Number.isNaN(maxNodes) || maxNodes < 1) {
1321
+ error(`--max-nodes expects a positive integer, got "${options.maxNodes}".`);
1322
+ process.exit(1);
1323
+ }
1324
+ }
1325
+ try {
1326
+ if (!(0, directory_1.isInitialized)(projectPath)) {
1327
+ error(`LatticeSensor not initialized in ${projectPath}`);
1328
+ process.exit(1);
1329
+ }
1330
+ const { default: LatticeSensor } = await loadLatticeSensor();
1331
+ const cg = await LatticeSensor.open(projectPath);
1332
+ const result = await cg.buildContext(taskParts.join(' '), {
1333
+ format,
1334
+ includeCode: options.code !== false,
1335
+ ...(maxNodes !== undefined ? { maxNodes } : {}),
1336
+ });
1337
+ // Both supported formats return a formatted string; print it verbatim so
1338
+ // `--format json` stays machine-parseable on stdout (error()/warnings go
1339
+ // to stderr only).
1340
+ console.log(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
1341
+ cg.destroy();
1342
+ }
1343
+ catch (err) {
1344
+ error(`Context build failed: ${err instanceof Error ? err.message : String(err)}`);
1345
+ process.exit(1);
1346
+ }
1347
+ });
1139
1348
  /**
1140
1349
  * lattice sensor prompt-hook (hidden)
1141
1350
  *
@@ -1176,6 +1385,10 @@ function main() {
1176
1385
  return;
1177
1386
  }
1178
1387
  const prompt = String(input.prompt || '');
1388
+ // System-injected task notifications are not user prompts: exit before
1389
+ // any project lookup or explore work (#1832).
1390
+ if ((0, directory_1.isTaskNotification)(prompt))
1391
+ return;
1179
1392
  // Gate telemetry: how often each tier fires vs. no-ops — counter names
1180
1393
  // only, NEVER prompt content (see TELEMETRY.md). This is the data that
1181
1394
  // turns "is the gate any good" from vibes into a measured recall rate.
@@ -1240,8 +1453,11 @@ function main() {
1240
1453
  const text = result.content[0]?.text ?? '';
1241
1454
  if (!result.isError && text.trim()) {
1242
1455
  // Cap the injection so a large-repo explore can't flood the prompt.
1243
- const MAX = 16000;
1244
- const body = text.length > MAX ? `${text.slice(0, MAX)}\n…(truncated; call lattice_sensor_explore for the rest)` : text;
1456
+ // Claude Code shows hook stdout inline only up to 10,000 characters;
1457
+ // above that it persists the output to a file and the model sees a
1458
+ // 2 KB preview (#1694). PROMPT_HOOK_INJECTION_MAX (9,000) leaves
1459
+ // room for the wrapper and the projectPath nudge lines below.
1460
+ const body = (0, directory_1.capPromptHookInjection)(text);
1245
1461
  // For a front-loaded SUB-project, a follow-up explore needs its path.
1246
1462
  const more = plan.viaSubScan
1247
1463
  ? `call lattice_sensor_explore with projectPath: "${plan.exploreRoot}" for more`
@@ -1651,9 +1867,9 @@ function main() {
1651
1867
  .aliases(['daemons'])
1652
1868
  .description('Manage running LatticeSensor background daemons — pick one and press enter to stop it')
1653
1869
  .action(async () => {
1654
- const { listDaemons, stopDaemonAt, stopAllDaemons } = await Promise.resolve().then(() => __importStar(require('../mcp/daemon-registry')));
1870
+ const { listVerifiedDaemons, stopDaemonAt, stopAllDaemons } = await Promise.resolve().then(() => __importStar(require('../mcp/daemon-registry')));
1655
1871
  const { runDaemonPicker } = await Promise.resolve().then(() => __importStar(require('../mcp/daemon-manager')));
1656
- const daemons = listDaemons();
1872
+ const daemons = await listVerifiedDaemons();
1657
1873
  if (daemons.length === 0) {
1658
1874
  info('No LatticeSensor daemons running.');
1659
1875
  return;
@@ -1680,7 +1896,7 @@ function main() {
1680
1896
  const clack = await importESM('@clack/prompts');
1681
1897
  clack.intro('LatticeSensor daemons');
1682
1898
  await runDaemonPicker({
1683
- list: listDaemons,
1899
+ list: listVerifiedDaemons,
1684
1900
  stop: stopDaemonAt,
1685
1901
  stopAll: stopAllDaemons,
1686
1902
  cwdRoot,
@@ -1691,6 +1907,172 @@ function main() {
1691
1907
  done: (m) => clack.outro(m),
1692
1908
  });
1693
1909
  });
1910
+ /**
1911
+ * Print the "no index here" guidance.
1912
+ *
1913
+ * The viewer READS an index; it never builds one — indexing stays the user's
1914
+ * decision, exactly as it is for the MCP tools. So a missing index is normal
1915
+ * input, not a failure to apologize for: say what is missing, say the one
1916
+ * command that fixes it, and never print a stack trace.
1917
+ */
1918
+ function printNoIndexGuidance(projectPath) {
1919
+ error(`No LatticeSensor index found for ${projectPath}`);
1920
+ console.error('');
1921
+ // getGlyphs() (not a literal em dash): a legacy Windows console decodes raw
1922
+ // UTF-8 with its OEM codepage and renders one as mojibake (#168).
1923
+ console.error(` The viewer reads an index that already exists ${(0, glyphs_1.getGlyphs)().dash} it never creates one.`);
1924
+ console.error(' To index this project:');
1925
+ console.error('');
1926
+ console.error(` ${chalk.cyan('lattice sensor init')}`);
1927
+ console.error('');
1928
+ console.error(' Already indexed somewhere else? Point the viewer at it:');
1929
+ console.error('');
1930
+ console.error(` ${chalk.cyan('lattice sensor ui /path/to/indexed/project')}`);
1931
+ console.error('');
1932
+ }
1933
+ /**
1934
+ * lattice sensor ui [path] (alias: web)
1935
+ *
1936
+ * The browser reader: serves the built viewer (`dist/viewer/`) over loopback
1937
+ * and opens it. It opens the index for reading and never writes to it, never
1938
+ * indexes, and never changes a line of the project's code. The single thing it
1939
+ * writes is a trail the reader saved, as JSON under `.lattice/sensor/ui/trails/`;
1940
+ * `--read-only` turns even that off.
1941
+ *
1942
+ * Deliberately absent from TELEMETRY_FLUSH_COMMANDS above: the command's own
1943
+ * banner tells the user nothing leaves their machine, so it must not be the
1944
+ * thing that triggers a telemetry send. The usage count still buffers locally
1945
+ * like every other quick command.
1946
+ */
1947
+ program
1948
+ .command('ui [path]', { hidden: !(0, viewer_gate_1.viewerEnabled)() })
1949
+ .alias('web')
1950
+ .description('Open the LatticeSensor viewer in your browser — read your indexed project as a graph')
1951
+ .option('--port <number>', `Port to listen on (default: ${constants_1.DEFAULT_UI_PORT}, or the next free one)`)
1952
+ .option('--no-open', 'Print the URL instead of opening a browser')
1953
+ .option('--read-only', 'Refuse every write — saved trails can be opened but not saved or deleted')
1954
+ .addHelpText('after', `
1955
+ Examples:
1956
+ $ lattice sensor ui Read the project you're standing in
1957
+ $ lattice sensor ui ~/code/my-app Read a specific indexed project
1958
+ $ lattice sensor ui --port 8080 Use one specific port (fails if it's taken)
1959
+ $ lattice sensor ui --no-open Just print the URL (headless boxes, SSH)
1960
+ $ lattice sensor web Same command under its alias
1961
+
1962
+ Pick a symbol and you see who calls it on the left, its source in the middle,
1963
+ and what it calls on the right at the height of the line that calls it. Search
1964
+ with / (or Cmd-K), click a file path for the file's outline and its imports.
1965
+
1966
+ Ask "how does execute reach getFile" (or "execute -> getFile") in the search
1967
+ box for the flow between two symbols: one card per hop, opened at the line that
1968
+ makes the next call, with dynamic-dispatch hops drawn dashed and named. The Map
1969
+ tab draws the whole project by module, with dependencies pointing down.
1970
+
1971
+ Never opened this codebase before? The Entry points tab lists the routes with
1972
+ the symbols that serve them, the files that run something when they load, the
1973
+ tests, and what the most code depends on — and starts a flow from any of them.
1974
+
1975
+ The page keeps up with the project while it is open: save a file and it says so
1976
+ within about a third of a second, and whatever is on screen re-reads the graph
1977
+ when something re-indexes it. It watches for that; it never polls.
1978
+
1979
+ Save a walk you want to keep: name the trail and it is written to
1980
+ .lattice/sensor/ui/trails/ (already gitignored) as plain JSON, listed on the empty
1981
+ screen, and reopened at the symbol you left. Hops are remembered by name rather
1982
+ than by position, so a saved trail survives re-indexing and says which hop moved
1983
+ when one does. Pass --read-only to refuse every write.
1984
+
1985
+ The viewer listens on 127.0.0.1 only, so nothing on your network can reach it.
1986
+ It opens an index that already exists, never indexes, and never changes a line
1987
+ of your code — the one thing it writes is a trail you asked it to save.
1988
+ Requests from any other host are refused, and nothing is sent anywhere: no code,
1989
+ no paths, no analytics.
1990
+
1991
+ Without --port it takes ${constants_1.DEFAULT_UI_PORT}, or the next free port if that one is busy.
1992
+
1993
+ Set ${constants_1.BROWSER_ENV}=<command> to choose which browser opens, or
1994
+ ${constants_1.BROWSER_ENV}=none to never open one.
1995
+ `)
1996
+ .action(async (pathArg, options) => {
1997
+ // An explicit --port stays explicit: a scripted `--port 8080` that quietly
1998
+ // lands on 8081 is worse than one that says the port is busy. The default
1999
+ // port is the only one we're free to walk away from.
2000
+ let requestedPort;
2001
+ if (options.port !== undefined) {
2002
+ requestedPort = Number(options.port);
2003
+ if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
2004
+ error(`--port must be a whole number between 0 and 65535 (got "${options.port}").`);
2005
+ process.exit(1);
2006
+ }
2007
+ }
2008
+ const projectPath = resolveProjectPath(pathArg);
2009
+ // Sensitive-directory refusal before anything opens: the same guard the MCP
2010
+ // entry points use, so `lattice sensor ui /etc` is turned away here rather than
2011
+ // becoming a browsable view of the system.
2012
+ const { validateProjectPath } = await Promise.resolve().then(() => __importStar(require('../utils')));
2013
+ const rootError = validateProjectPath(projectPath);
2014
+ if (rootError) {
2015
+ error(rootError);
2016
+ process.exit(1);
2017
+ }
2018
+ if (!(0, directory_1.isInitialized)(projectPath)) {
2019
+ printNoIndexGuidance(projectPath);
2020
+ process.exit(1);
2021
+ }
2022
+ const { startUiServer, openBrowser, createGraphApi, ViewerMissingError } = await Promise.resolve().then(() => __importStar(require('../ui-server')));
2023
+ // The JSON API the viewer reads its screens from. It opens the index lazily
2024
+ // on the first request, so a slow first paint is the only cost of mounting
2025
+ // it here rather than after the browser connects.
2026
+ const readOnly = options.readOnly === true;
2027
+ const api = createGraphApi({
2028
+ projectRoot: projectPath,
2029
+ readOnly,
2030
+ readOnlyReason: readOnly
2031
+ ? 'This viewer was started with --read-only, so trails cannot be saved.'
2032
+ : undefined,
2033
+ });
2034
+ let handle;
2035
+ try {
2036
+ handle = await startUiServer({
2037
+ projectRoot: projectPath,
2038
+ port: requestedPort,
2039
+ portFallback: requestedPort === undefined,
2040
+ api: api.handler,
2041
+ });
2042
+ }
2043
+ catch (err) {
2044
+ api.close();
2045
+ // Both failure modes here (viewer assets missing, no port available) carry
2046
+ // their own remediation — print it plainly, never a stack trace.
2047
+ error(err instanceof ViewerMissingError || err instanceof Error ? err.message : String(err));
2048
+ process.exit(1);
2049
+ }
2050
+ console.log('');
2051
+ console.log(chalk.bold('LatticeSensor viewer'));
2052
+ console.log('');
2053
+ console.log(` ${chalk.dim('Reading')} ${projectPath}`);
2054
+ console.log(` ${chalk.dim('URL')} ${chalk.cyan(handle.url)}`);
2055
+ console.log(` ${chalk.dim('Access')} this machine only ${(0, glyphs_1.getGlyphs)().dash} ` +
2056
+ (readOnly
2057
+ ? 'read-only, nothing leaves your computer'
2058
+ : 'nothing leaves your computer; saved trails are the only thing written'));
2059
+ console.log('');
2060
+ const opened = options.open === false ? false : openBrowser(handle.url);
2061
+ console.log(opened
2062
+ ? chalk.dim(' Opening your browser... press Ctrl+C to stop.')
2063
+ : chalk.dim(' Open that URL in a browser. Press Ctrl+C to stop.'));
2064
+ console.log('');
2065
+ // The http server keeps the event loop alive on its own; these just make
2066
+ // Ctrl-C hang up live sockets instead of waiting on browser keep-alives.
2067
+ const shutdown = () => {
2068
+ // Release the SQLite handle before the socket: the process should never
2069
+ // exit with a live connection to the user's index.
2070
+ api.close();
2071
+ void handle.close().then(() => process.exit(0));
2072
+ };
2073
+ process.once('SIGINT', shutdown);
2074
+ process.once('SIGTERM', shutdown);
2075
+ });
1694
2076
  /**
1695
2077
  * lattice sensor serve
1696
2078
  */
@@ -1782,12 +2164,17 @@ function main() {
1782
2164
  return;
1783
2165
  }
1784
2166
  const lockPath = path.join((0, directory_1.getLatticeSensorDir)(projectPath), 'sensor.lock');
1785
- if (!fs.existsSync(lockPath)) {
1786
- info(`No lock file found ${(0, glyphs_1.getGlyphs)().dash} nothing to do`);
1787
- return;
1788
- }
1789
- fs.unlinkSync(lockPath);
1790
- success('Removed lock file. You can now run indexing again.');
2167
+ let removed = false;
2168
+ if (fs.existsSync(lockPath)) {
2169
+ fs.unlinkSync(lockPath);
2170
+ removed = true;
2171
+ }
2172
+ const { clearStaleDaemonArtifacts } = await Promise.resolve().then(() => __importStar(require('../mcp/daemon-registry')));
2173
+ removed = await clearStaleDaemonArtifacts(projectPath) || removed;
2174
+ if (removed)
2175
+ success('Removed stale lock artifacts. You can now run indexing again.');
2176
+ else
2177
+ info(`No stale lock files found ${(0, glyphs_1.getGlyphs)().dash} nothing to do`);
1791
2178
  }
1792
2179
  catch (err) {
1793
2180
  error(`Failed to remove lock: ${err instanceof Error ? err.message : String(err)}`);
@@ -1795,175 +2182,183 @@ function main() {
1795
2182
  }
1796
2183
  });
1797
2184
  /**
1798
- * lattice sensor callers <symbol>
2185
+ * CLI parity with MCP callers/callees: resolve once, then collect and limit
2186
+ * within each definition. The legacy JSON list remains an explicitly labeled
2187
+ * union, with its original total/limit/truncated contract (#1674).
1799
2188
  *
1800
- * CLI parity with the MCP graph tools (lattice_sensor_callers/callees/impact) so the
1801
- * traversal queries work in scripts, CI, and git hooks without a running MCP
1802
- * server.
2189
+ * Lattice contract on top (src/sensor-adapter.mjs, todo-structure-source-adapter,
2190
+ * seam-cost): `--exact-path` traverses exactly the definition of `symbol` in one
2191
+ * repo-relative file and reports `exactResolution`; every listed node carries
2192
+ * `edgeKind`/`valueRef`/`valueWrite` from the edges that connect it.
1803
2193
  */
1804
- program
1805
- .command('callers <symbol>')
1806
- .description('Find all functions/methods that call a specific symbol')
1807
- .option('-p, --path <path>', 'Project path')
1808
- .option('-l, --limit <number>', 'Maximum results', '20')
1809
- .option('--exact-path <repo-relative-path>', 'Traverse one exact symbol in this repo-relative file')
1810
- .option('-j, --json', 'Output as JSON')
1811
- .action(async (symbol, options) => {
1812
- const projectPath = resolveProjectPath(options.path);
1813
- try {
1814
- if (!(0, directory_1.isInitialized)(projectPath)) {
1815
- error(`LatticeSensor not initialized in ${projectPath}`);
1816
- process.exit(1);
1817
- }
1818
- const { default: LatticeSensor } = await loadLatticeSensor();
1819
- const cg = await LatticeSensor.open(projectPath);
1820
- const limit = parseInt(options.limit || '20', 10);
1821
- const matches = cg.searchNodes(symbol, { limit: 50 });
1822
- if (matches.length === 0) {
1823
- info(`Symbol "${symbol}" not found`);
1824
- cg.destroy();
1825
- return;
1826
- }
1827
- const seen = new Set();
1828
- const allCallers = [];
1829
- const strict = options.exactPath === undefined
1830
- ? null : (0, exact_traversal_1.selectExactTraversalCandidate)(matches, symbol, options.exactPath);
1831
- const traversalMatches = strict === null
1832
- ? matches : strict.outcome === 'ready' ? [strict.candidate] : [];
1833
- for (const match of traversalMatches) {
1834
- const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1835
- if (strict === null && !exactMatch && matches.length > 1)
1836
- continue;
1837
- for (const c of cg.getCallers(match.node.id)) {
1838
- if (!seen.has(c.node.id)) {
1839
- seen.add(c.node.id);
1840
- allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, edgeKind: c.edge.kind, valueRef: c.edge.metadata?.valueRef === true, valueWrite: c.edge.metadata?.write === true });
1841
- }
2194
+ for (const direction of ['callers', 'callees']) {
2195
+ const title = direction === 'callers' ? 'Callers' : 'Callees';
2196
+ program
2197
+ .command(`${direction} <symbol>`)
2198
+ .description(direction === 'callers'
2199
+ ? 'Find all functions/methods that call a specific symbol'
2200
+ : 'Find all functions/methods called by a specific symbol')
2201
+ .option('-p, --path <path>', 'Project path')
2202
+ .option('-f, --file <path>', 'Narrow definitions by file path or suffix (no match: show all with a note)')
2203
+ .option('-l, --limit <number>', 'Maximum results per definition (also caps the JSON union)', '20')
2204
+ .option('--exact-path <repo-relative-path>', 'Traverse one exact symbol in this repo-relative file')
2205
+ .option('-j, --json', 'Output as JSON')
2206
+ .action(async (symbol, options) => {
2207
+ const projectPath = resolveProjectPath(options.path);
2208
+ try {
2209
+ if (!(0, directory_1.isInitialized)(projectPath)) {
2210
+ error(`LatticeSensor not initialized in ${projectPath}`);
2211
+ process.exit(1);
1842
2212
  }
1843
- }
1844
- // Fallback: if exact filter removed everything, use the top match
1845
- if (strict === null && allCallers.length === 0 && matches[0]) {
1846
- for (const c of cg.getCallers(matches[0].node.id)) {
1847
- if (!seen.has(c.node.id)) {
1848
- seen.add(c.node.id);
1849
- allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, edgeKind: c.edge.kind, valueRef: c.edge.metadata?.valueRef === true, valueWrite: c.edge.metadata?.write === true });
2213
+ const { default: LatticeSensor } = await loadLatticeSensor();
2214
+ const cg = await LatticeSensor.open(projectPath);
2215
+ try {
2216
+ const limit = parseInt(options.limit || '20', 10);
2217
+ const { nodes: targets } = (0, symbol_lookup_1.lookupSymbolNodes)(cg, symbol);
2218
+ if (targets.length === 0) {
2219
+ info(formatSymbolNotFound(symbol, cg.searchNodes(symbol, { limit: 5 }).map((m) => m.node.name)));
2220
+ return;
1850
2221
  }
1851
- }
1852
- }
1853
- const limited = allCallers.slice(0, limit);
1854
- if (options.json) {
1855
- console.log(JSON.stringify({
1856
- symbol,
1857
- ...(strict === null ? {} : { exactPath: options.exactPath, exactResolution: strict.outcome }),
1858
- callers: limited,
1859
- }, null, 2));
1860
- }
1861
- else if (limited.length === 0) {
1862
- info(`No callers found for "${symbol}"`);
1863
- }
1864
- else {
1865
- console.log(chalk.bold(`\nCallers of "${symbol}" (${limited.length}):\n`));
1866
- for (const node of limited) {
1867
- const loc = node.startLine ? `:${node.startLine}` : '';
1868
- console.log(chalk.cyan(node.kind.padEnd(12)) +
1869
- chalk.white(node.name));
1870
- console.log(chalk.dim(` ${node.filePath}${loc}`));
1871
- console.log();
1872
- }
1873
- }
1874
- cg.destroy();
1875
- }
1876
- catch (err) {
1877
- error(`callers failed: ${err instanceof Error ? err.message : String(err)}`);
1878
- process.exit(1);
1879
- }
1880
- });
1881
- /**
1882
- * lattice sensor callees <symbol>
1883
- */
1884
- program
1885
- .command('callees <symbol>')
1886
- .description('Find all functions/methods that a specific symbol calls')
1887
- .option('-p, --path <path>', 'Project path')
1888
- .option('-l, --limit <number>', 'Maximum results', '20')
1889
- .option('--exact-path <repo-relative-path>', 'Traverse one exact symbol in this repo-relative file')
1890
- .option('-j, --json', 'Output as JSON')
1891
- .action(async (symbol, options) => {
1892
- const projectPath = resolveProjectPath(options.path);
1893
- try {
1894
- if (!(0, directory_1.isInitialized)(projectPath)) {
1895
- error(`LatticeSensor not initialized in ${projectPath}`);
1896
- process.exit(1);
1897
- }
1898
- const { default: LatticeSensor } = await loadLatticeSensor();
1899
- const cg = await LatticeSensor.open(projectPath);
1900
- const limit = parseInt(options.limit || '20', 10);
1901
- const matches = cg.searchNodes(symbol, { limit: 50 });
1902
- if (matches.length === 0) {
1903
- info(`Symbol "${symbol}" not found`);
1904
- cg.destroy();
1905
- return;
1906
- }
1907
- const seen = new Set();
1908
- const allCallees = [];
1909
- const strict = options.exactPath === undefined
1910
- ? null : (0, exact_traversal_1.selectExactTraversalCandidate)(matches, symbol, options.exactPath);
1911
- const traversalMatches = strict === null
1912
- ? matches : strict.outcome === 'ready' ? [strict.candidate] : [];
1913
- for (const match of traversalMatches) {
1914
- const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1915
- if (strict === null && !exactMatch && matches.length > 1)
1916
- continue;
1917
- for (const c of cg.getCallees(match.node.id)) {
1918
- if (!seen.has(c.node.id)) {
1919
- seen.add(c.node.id);
1920
- allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, edgeKind: c.edge.kind, valueRef: c.edge.metadata?.valueRef === true, valueWrite: c.edge.metadata?.write === true });
2222
+ const strict = options.exactPath === undefined
2223
+ ? null : (0, exact_traversal_1.selectExactTraversalCandidate)(targets.map((node) => ({ node })), symbol, options.exactPath);
2224
+ if (strict !== null && strict.outcome !== 'ready') {
2225
+ if (options.json) {
2226
+ console.log(JSON.stringify({
2227
+ symbol, exactPath: options.exactPath, exactResolution: strict.outcome,
2228
+ [direction]: [], total: 0, limit, truncated: false,
2229
+ }, null, 2));
2230
+ }
2231
+ else {
2232
+ info(`No single definition of "${symbol}" in ${options.exactPath} (${strict.outcome})`);
2233
+ }
2234
+ return;
1921
2235
  }
1922
- }
1923
- }
1924
- if (strict === null && allCallees.length === 0 && matches[0]) {
1925
- for (const c of cg.getCallees(matches[0].node.id)) {
1926
- if (!seen.has(c.node.id)) {
1927
- seen.add(c.node.id);
1928
- allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, edgeKind: c.edge.kind, valueRef: c.edge.metadata?.valueRef === true, valueWrite: c.edge.metadata?.write === true });
2236
+ const exact = strict === null
2237
+ ? {} : { exactPath: options.exactPath, exactResolution: strict.outcome };
2238
+ const { groups, filteredOut } = (0, symbol_lookup_1.groupDefinitions)(strict === null ? targets : [strict.candidate.node], options.file);
2239
+ const ambiguous = groups.length > 1;
2240
+ const note = filteredOut
2241
+ ? `no definition of "${symbol}" matches file "${options.file}" — showing all definitions instead.`
2242
+ : undefined;
2243
+ const collected = groups.map((group) => {
2244
+ const nodes = new Map();
2245
+ const edges = new Map();
2246
+ for (const target of group) {
2247
+ const connections = direction === 'callers' ? cg.getCallers(target.id) : cg.getCallees(target.id);
2248
+ for (const { node, edge } of connections) {
2249
+ nodes.set(node.id, node);
2250
+ edges.set(`${edge.source}->${edge.target}:${edge.kind}`, edge);
2251
+ }
2252
+ }
2253
+ return { group, nodes: [...nodes.values()], edges: [...edges.values()] };
2254
+ });
2255
+ const relationships = (node, edges) => [...new Set(edges
2256
+ .filter((edge) => (direction === 'callers' ? edge.source : edge.target) === node.id)
2257
+ .map((edge) => edge.kind))];
2258
+ // Lattice: the edge facts behind each listed node. A value write on ANY
2259
+ // connecting edge counts — dropping one would hide a writer from seam cost.
2260
+ const edgeFacts = (node, edges) => {
2261
+ const own = edges.filter((edge) => (direction === 'callers' ? edge.source : edge.target) === node.id);
2262
+ return {
2263
+ edgeKind: own[0]?.kind ?? 'calls',
2264
+ valueRef: own.some((edge) => edge.metadata?.valueRef === true),
2265
+ valueWrite: own.some((edge) => edge.metadata?.valueWrite === true),
2266
+ };
2267
+ };
2268
+ if (options.json) {
2269
+ const definitions = collected.map(({ group, nodes, edges }) => {
2270
+ const limited = nodes.slice(0, limit);
2271
+ const shown = new Set(limited.map((node) => node.id));
2272
+ return {
2273
+ ...cliDefinition(group),
2274
+ [direction]: limited.map((node) => ({ id: node.id, ...cliNode(node),
2275
+ relationships: relationships(node, edges), ...edgeFacts(node, edges) })),
2276
+ edges: edges.filter((edge) => shown.has(direction === 'callers' ? edge.source : edge.target)),
2277
+ total: nodes.length,
2278
+ limit,
2279
+ truncated: nodes.length > limit,
2280
+ };
2281
+ });
2282
+ const union = new Map();
2283
+ for (const { nodes } of collected) {
2284
+ for (const node of nodes)
2285
+ union.set(node.id, node);
2286
+ }
2287
+ const total = union.size;
2288
+ console.log(JSON.stringify({
2289
+ symbol,
2290
+ ...exact,
2291
+ targets: groups.flat().map((node) => cliDefinition([node]).definition),
2292
+ ambiguous,
2293
+ aggregation: ambiguous ? 'union' : 'definition',
2294
+ file: options.file,
2295
+ filteredOut,
2296
+ note,
2297
+ definitions,
2298
+ [direction]: [...union.values()].slice(0, limit).map((node) => {
2299
+ const all = collected.flatMap((entry) => entry.edges);
2300
+ return { ...cliNode(node), relationships: relationships(node, all), ...edgeFacts(node, all) };
2301
+ }),
2302
+ total,
2303
+ limit,
2304
+ truncated: total > limit,
2305
+ }, null, 2));
2306
+ }
2307
+ else {
2308
+ if (note)
2309
+ warn(note);
2310
+ if (ambiguous) {
2311
+ console.log(chalk.bold(`\n${title} of "${symbol}" — ${groups.length} distinct definitions (narrow with --file):`));
2312
+ }
2313
+ for (const { group, nodes, edges } of collected) {
2314
+ const limited = nodes.slice(0, limit);
2315
+ const total = nodes.length;
2316
+ const truncated = total > limit;
2317
+ const count = truncated ? `${limited.length} of ${total}` : String(total);
2318
+ if (ambiguous) {
2319
+ console.log(chalk.bold(`\n${(0, symbol_lookup_1.describeSymbolNode)(group[0])} (${count}):\n`));
2320
+ }
2321
+ else {
2322
+ console.log(chalk.bold(`\n${title} of "${symbol}" (${count}):\n`));
2323
+ console.log(chalk.dim((0, symbol_lookup_1.describeSymbolNode)(group[0])));
2324
+ }
2325
+ if (total === 0) {
2326
+ if (ambiguous)
2327
+ console.log(chalk.dim(` (no ${direction})`));
2328
+ else
2329
+ info(`No ${direction} found for "${symbol}"`);
2330
+ }
2331
+ for (const node of limited) {
2332
+ const loc = node.startLine ? `:${node.startLine}` : '';
2333
+ const kinds = relationships(node, edges).filter((kind) => kind !== 'calls');
2334
+ const relation = kinds.length ? ` [${kinds.join(', ')}]` : '';
2335
+ console.log(chalk.cyan(node.kind.padEnd(12)) + chalk.white(node.name) + chalk.dim(relation));
2336
+ console.log(chalk.dim(` ${node.filePath}${loc}`));
2337
+ console.log();
2338
+ }
2339
+ if (truncated)
2340
+ console.log(chalk.dim(`Showing ${limited.length} of ${total}; pass --limit to widen.`));
2341
+ }
1929
2342
  }
1930
2343
  }
1931
- }
1932
- const limited = allCallees.slice(0, limit);
1933
- if (options.json) {
1934
- console.log(JSON.stringify({
1935
- symbol,
1936
- ...(strict === null ? {} : { exactPath: options.exactPath, exactResolution: strict.outcome }),
1937
- callees: limited,
1938
- }, null, 2));
1939
- }
1940
- else if (limited.length === 0) {
1941
- info(`No callees found for "${symbol}"`);
1942
- }
1943
- else {
1944
- console.log(chalk.bold(`\nCallees of "${symbol}" (${limited.length}):\n`));
1945
- for (const node of limited) {
1946
- const loc = node.startLine ? `:${node.startLine}` : '';
1947
- console.log(chalk.cyan(node.kind.padEnd(12)) +
1948
- chalk.white(node.name));
1949
- console.log(chalk.dim(` ${node.filePath}${loc}`));
1950
- console.log();
2344
+ finally {
2345
+ cg.destroy();
1951
2346
  }
1952
2347
  }
1953
- cg.destroy();
1954
- }
1955
- catch (err) {
1956
- error(`callees failed: ${err instanceof Error ? err.message : String(err)}`);
1957
- process.exit(1);
1958
- }
1959
- });
2348
+ catch (err) {
2349
+ error(`${direction} failed: ${err instanceof Error ? err.message : String(err)}`);
2350
+ process.exit(1);
2351
+ }
2352
+ });
2353
+ }
1960
2354
  /**
1961
- * lattice sensor impact <symbol>
2355
+ * lattice sensor impact <symbol> — one blast radius per distinct definition.
1962
2356
  */
1963
2357
  program
1964
2358
  .command('impact <symbol>')
1965
2359
  .description('Analyze what code is affected by changing a symbol')
1966
2360
  .option('-p, --path <path>', 'Project path')
2361
+ .option('-f, --file <path>', 'Narrow definitions by file path or suffix (no match: show all with a note)')
1967
2362
  .option('-d, --depth <number>', 'Traversal depth', '2')
1968
2363
  .option('-j, --json', 'Output as JSON')
1969
2364
  .action(async (symbol, options) => {
@@ -1975,72 +2370,95 @@ function main() {
1975
2370
  }
1976
2371
  const { default: LatticeSensor } = await loadLatticeSensor();
1977
2372
  const cg = await LatticeSensor.open(projectPath);
1978
- const depth = Math.min(Math.max(parseInt(options.depth || '2', 10), 1), 10);
1979
- const matches = cg.searchNodes(symbol, { limit: 50 });
1980
- if (matches.length === 0) {
1981
- info(`Symbol "${symbol}" not found`);
1982
- cg.destroy();
1983
- return;
1984
- }
1985
- // Merge impact subgraphs across all exact-matching symbols
1986
- const mergedNodes = new Map();
1987
- const seenEdges = new Set();
1988
- let edgeCount = 0;
1989
- for (const match of matches) {
1990
- const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1991
- if (!exactMatch && matches.length > 1)
1992
- continue;
1993
- const impact = cg.getImpactRadius(match.node.id, depth);
1994
- for (const [id, n] of impact.nodes) {
1995
- mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine });
2373
+ try {
2374
+ const depth = Math.min(Math.max(parseInt(options.depth || '2', 10), 1), 10);
2375
+ const { nodes: targets } = (0, symbol_lookup_1.lookupSymbolNodes)(cg, symbol);
2376
+ if (targets.length === 0) {
2377
+ info(formatSymbolNotFound(symbol, cg.searchNodes(symbol, { limit: 5 }).map((m) => m.node.name)));
2378
+ return;
1996
2379
  }
1997
- for (const e of impact.edges) {
1998
- const key = `${e.source}->${e.target}:${e.kind}`;
1999
- if (!seenEdges.has(key)) {
2000
- seenEdges.add(key);
2001
- edgeCount++;
2380
+ const { groups, filteredOut } = (0, symbol_lookup_1.groupDefinitions)(targets, options.file);
2381
+ const ambiguous = groups.length > 1;
2382
+ const note = filteredOut
2383
+ ? `no definition of "${symbol}" matches file "${options.file}" — showing all definitions instead.`
2384
+ : undefined;
2385
+ const collected = groups.map((group) => {
2386
+ const nodes = new Map();
2387
+ const edges = new Map();
2388
+ for (const target of group) {
2389
+ const impact = cg.getImpactRadius(target.id, depth);
2390
+ for (const [id, node] of impact.nodes)
2391
+ nodes.set(id, node);
2392
+ for (const edge of impact.edges)
2393
+ edges.set(`${edge.source}->${edge.target}:${edge.kind}`, edge);
2002
2394
  }
2395
+ return { group, nodes, edges };
2396
+ });
2397
+ if (options.json) {
2398
+ const unionNodes = new Map();
2399
+ const unionEdges = new Map();
2400
+ const definitions = collected.map(({ group, nodes, edges }) => {
2401
+ for (const [id, node] of nodes)
2402
+ unionNodes.set(id, node);
2403
+ for (const [key, edge] of edges)
2404
+ unionEdges.set(key, edge);
2405
+ return {
2406
+ ...cliDefinition(group),
2407
+ nodeCount: nodes.size,
2408
+ edgeCount: edges.size,
2409
+ affected: [...nodes.values()].map((node) => ({ id: node.id, ...cliNode(node) })),
2410
+ edges: [...edges.values()],
2411
+ };
2412
+ });
2413
+ console.log(JSON.stringify({
2414
+ symbol,
2415
+ depth,
2416
+ targets: groups.flat().map((node) => cliDefinition([node]).definition),
2417
+ ambiguous,
2418
+ aggregation: ambiguous ? 'union' : 'definition',
2419
+ file: options.file,
2420
+ filteredOut,
2421
+ note,
2422
+ definitions,
2423
+ nodeCount: unionNodes.size,
2424
+ edgeCount: unionEdges.size,
2425
+ affected: [...unionNodes.values()].map(cliNode),
2426
+ }, null, 2));
2003
2427
  }
2004
- }
2005
- // Fallback to top match if exact filter removed everything
2006
- if (mergedNodes.size === 0 && matches[0]) {
2007
- const impact = cg.getImpactRadius(matches[0].node.id, depth);
2008
- for (const [id, n] of impact.nodes) {
2009
- mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine });
2010
- }
2011
- edgeCount = impact.edges.length;
2012
- }
2013
- if (options.json) {
2014
- console.log(JSON.stringify({
2015
- symbol,
2016
- depth,
2017
- nodeCount: mergedNodes.size,
2018
- edgeCount,
2019
- affected: Array.from(mergedNodes.values()),
2020
- }, null, 2));
2021
- }
2022
- else if (mergedNodes.size === 0) {
2023
- info(`No affected symbols found for "${symbol}"`);
2024
- }
2025
- else {
2026
- console.log(chalk.bold(`\nImpact of changing "${symbol}" — ${mergedNodes.size} affected symbols:\n`));
2027
- // Group by file
2028
- const byFile = new Map();
2029
- for (const node of mergedNodes.values()) {
2030
- const list = byFile.get(node.filePath) || [];
2031
- list.push({ name: node.name, kind: node.kind, startLine: node.startLine });
2032
- byFile.set(node.filePath, list);
2033
- }
2034
- for (const [file, nodes] of byFile) {
2035
- console.log(chalk.cyan(file));
2036
- for (const node of nodes) {
2037
- const loc = node.startLine ? `:${node.startLine}` : '';
2038
- console.log(` ${chalk.dim(node.kind.padEnd(12))}${node.name}${chalk.dim(loc)}`);
2428
+ else {
2429
+ if (note)
2430
+ warn(note);
2431
+ if (ambiguous) {
2432
+ console.log(chalk.bold(`\nImpact of changing "${symbol}" — ${groups.length} distinct definitions (each with its own blast radius; narrow with --file):`));
2433
+ }
2434
+ for (const { group, nodes } of collected) {
2435
+ if (ambiguous) {
2436
+ console.log(chalk.bold(`\n${(0, symbol_lookup_1.describeSymbolNode)(group[0])} — ${nodes.size} affected symbols:\n`));
2437
+ }
2438
+ else {
2439
+ console.log(chalk.bold(`\nImpact of changing "${symbol}" — ${nodes.size} affected symbols:\n`));
2440
+ console.log(chalk.dim((0, symbol_lookup_1.describeSymbolNode)(group[0])));
2441
+ }
2442
+ const byFile = new Map();
2443
+ for (const node of nodes.values()) {
2444
+ const list = byFile.get(node.filePath) || [];
2445
+ list.push(node);
2446
+ byFile.set(node.filePath, list);
2447
+ }
2448
+ for (const [file, affected] of byFile) {
2449
+ console.log(chalk.cyan(file));
2450
+ for (const node of affected) {
2451
+ const loc = node.startLine ? `:${node.startLine}` : '';
2452
+ console.log(` ${chalk.dim(node.kind.padEnd(12))}${node.name}${chalk.dim(loc)}`);
2453
+ }
2454
+ console.log();
2455
+ }
2039
2456
  }
2040
- console.log();
2041
2457
  }
2042
2458
  }
2043
- cg.destroy();
2459
+ finally {
2460
+ cg.destroy();
2461
+ }
2044
2462
  }
2045
2463
  catch (err) {
2046
2464
  error(`impact failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -2106,6 +2524,11 @@ function main() {
2106
2524
  .replace(/\*/g, '[^/]*');
2107
2525
  customFilter = new RegExp(regex);
2108
2526
  }
2527
+ // One notion of "a test" for the whole tool (#1507): the CLI used to keep
2528
+ // its own six regexes here, which knew `.test.` and `/tests/` but not Go's
2529
+ // `_test.go`, Python's `test_x.py` or the JVM's `FooTest.kt` — so
2530
+ // `affected` reported "no tests" for whole ecosystems while `search` and
2531
+ // the MCP tools counted those very files as tests.
2109
2532
  function matchesTestFile(filePath) {
2110
2533
  if (customFilter)
2111
2534
  return customFilter.test(filePath);