@plebeiantech/lat.md 0.12.3-fork.13 → 0.12.3-fork.14

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 (274) hide show
  1. package/README.md +24 -0
  2. package/dist/src/cli/check-context.d.ts +56 -0
  3. package/dist/src/cli/check-context.js +215 -0
  4. package/dist/src/cli/check-coverage.js +9 -5
  5. package/dist/src/cli/check-frontmatter.js +1 -1
  6. package/dist/src/cli/check-mode.js +1 -1
  7. package/dist/src/cli/check-status.js +1 -1
  8. package/dist/src/cli/check.d.ts +19 -12
  9. package/dist/src/cli/check.js +259 -206
  10. package/dist/src/cli/context.js +1 -1
  11. package/dist/src/cli/expand.d.ts +1 -2
  12. package/dist/src/cli/expand.js +52 -5
  13. package/dist/src/cli/external.d.ts +11 -0
  14. package/dist/src/cli/external.js +185 -0
  15. package/dist/src/cli/fork-instructions.d.ts +1 -0
  16. package/dist/src/cli/fork-instructions.js +3 -2
  17. package/dist/src/cli/hook.d.ts +26 -0
  18. package/dist/src/cli/hook.js +112 -40
  19. package/dist/src/cli/index.js +55 -3
  20. package/dist/src/cli/init.d.ts +1 -0
  21. package/dist/src/cli/init.js +40 -2
  22. package/dist/src/cli/locate.js +4 -3
  23. package/dist/src/cli/refs.d.ts +2 -1
  24. package/dist/src/cli/refs.js +88 -43
  25. package/dist/src/cli/reindex.js +2 -1
  26. package/dist/src/cli/search.d.ts +12 -3
  27. package/dist/src/cli/search.js +45 -49
  28. package/dist/src/cli/section.d.ts +3 -1
  29. package/dist/src/cli/section.js +114 -38
  30. package/dist/src/code-refs.d.ts +17 -5
  31. package/dist/src/code-refs.js +246 -65
  32. package/dist/src/context.d.ts +3 -0
  33. package/dist/src/document-formats.d.ts +7 -0
  34. package/dist/src/document-formats.js +39 -0
  35. package/dist/src/extensions/alert-marker.d.ts +25 -0
  36. package/dist/src/extensions/alert-marker.js +55 -0
  37. package/dist/src/external-documents.d.ts +56 -0
  38. package/dist/src/external-documents.js +404 -0
  39. package/dist/src/external-sources.d.ts +136 -0
  40. package/dist/src/external-sources.js +1098 -0
  41. package/dist/src/fork/antigravity-hook.d.ts +47 -0
  42. package/dist/src/fork/antigravity-hook.js +424 -0
  43. package/dist/src/fork/antigravity-init.d.ts +17 -0
  44. package/dist/src/fork/antigravity-init.js +124 -0
  45. package/dist/src/fork/frontmatter-fields.d.ts +36 -0
  46. package/dist/src/fork/frontmatter-fields.js +23 -0
  47. package/dist/src/format.d.ts +5 -2
  48. package/dist/src/format.js +8 -3
  49. package/dist/src/graph-export.js +1 -1
  50. package/dist/src/lattice-model.d.ts +77 -0
  51. package/dist/src/lattice-model.js +464 -0
  52. package/dist/src/lattice.d.ts +9 -104
  53. package/dist/src/lattice.js +69 -516
  54. package/dist/src/markdown-analysis-cache.d.ts +20 -0
  55. package/dist/src/markdown-analysis-cache.js +139 -0
  56. package/dist/src/markdown-analysis-worker.d.ts +16 -0
  57. package/dist/src/markdown-analysis-worker.js +33 -0
  58. package/dist/src/markdown-analysis.d.ts +50 -0
  59. package/dist/src/markdown-analysis.js +139 -0
  60. package/dist/src/markdown-analyzer-loader.d.ts +4 -0
  61. package/dist/src/markdown-analyzer-loader.js +13 -0
  62. package/dist/src/markdown-validation.d.ts +25 -0
  63. package/dist/src/markdown-validation.js +104 -0
  64. package/dist/src/mcp/server.js +27 -11
  65. package/dist/src/parser-cache.d.ts +17 -0
  66. package/dist/src/parser-cache.js +65 -0
  67. package/dist/src/parser-import.d.ts +8 -0
  68. package/dist/src/parser-import.js +1 -0
  69. package/dist/src/parser.js +80 -5
  70. package/dist/src/path.d.ts +2 -0
  71. package/dist/src/path.js +4 -0
  72. package/dist/src/profiler.d.ts +16 -0
  73. package/dist/src/profiler.js +81 -0
  74. package/dist/src/project-analysis.d.ts +59 -0
  75. package/dist/src/project-analysis.js +233 -0
  76. package/dist/src/project-discovery.d.ts +6 -0
  77. package/dist/src/project-discovery.js +30 -0
  78. package/dist/src/repository-path.d.ts +11 -0
  79. package/dist/src/repository-path.js +52 -0
  80. package/dist/src/search/db.d.ts +1 -1
  81. package/dist/src/search/db.js +2 -2
  82. package/dist/src/search/embedder.d.ts +2 -1
  83. package/dist/src/search/embedder.js +6 -5
  84. package/dist/src/search/index.d.ts +2 -1
  85. package/dist/src/search/index.js +10 -8
  86. package/dist/src/search/query.d.ts +20 -0
  87. package/dist/src/search/query.js +67 -0
  88. package/dist/src/search/search.d.ts +3 -1
  89. package/dist/src/search/search.js +5 -2
  90. package/dist/src/source-formats.d.ts +7 -0
  91. package/dist/src/source-formats.js +26 -0
  92. package/dist/src/source-parser.d.ts +44 -4
  93. package/dist/src/source-parser.js +494 -57
  94. package/dist/src/view/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  95. package/dist/src/view/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  96. package/dist/src/view/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  97. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  98. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  99. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  100. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  101. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  102. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  103. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  104. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  105. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  106. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  107. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  108. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  109. package/dist/src/view/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  110. package/dist/src/view/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  111. package/dist/src/view/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  112. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  113. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  114. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  115. package/dist/src/view/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  116. package/dist/src/view/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  117. package/dist/src/view/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  118. package/dist/src/view/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  119. package/dist/src/view/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  120. package/dist/src/view/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  121. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  122. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  123. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  124. package/dist/src/view/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  125. package/dist/src/view/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  126. package/dist/src/view/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  127. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  128. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  129. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  130. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  131. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  132. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  133. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  134. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  135. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  136. package/dist/src/view/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  137. package/dist/src/view/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  138. package/dist/src/view/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  139. package/dist/src/view/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  140. package/dist/src/view/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  141. package/dist/src/view/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  142. package/dist/src/view/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  143. package/dist/src/view/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  144. package/dist/src/view/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  145. package/dist/src/view/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  146. package/dist/src/view/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  147. package/dist/src/view/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  148. package/dist/src/view/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  149. package/dist/src/view/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  150. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  151. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  152. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  153. package/dist/src/view/client/assets/MarkdownEditor-B7quqPb8.js +28 -0
  154. package/dist/src/view/client/assets/OrbitControls-DwcpQNja.js +1 -0
  155. package/dist/src/view/client/assets/STLLoader-CLWTKt5i.js +1 -0
  156. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-V7-T6qsl.js +1 -0
  157. package/dist/src/view/client/assets/arc-BnwR8Bu7.js +1 -0
  158. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +36 -0
  159. package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-DETl8e_H.js +129 -0
  160. package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-DJRAT1Ve.js +38 -0
  161. package/dist/src/view/client/assets/channel-BwQbnWCN.js +1 -0
  162. package/dist/src/view/client/assets/chunk-2Q5K7J3B-C0TmOC1Q.js +1 -0
  163. package/dist/src/view/client/assets/chunk-5VM5RSS4-dfBOrYE6.js +15 -0
  164. package/dist/src/view/client/assets/chunk-F27PBJKO-DAnks99K.js +1 -0
  165. package/dist/src/view/client/assets/chunk-IMKFNOWR-CPjB07if.js +231 -0
  166. package/dist/src/view/client/assets/chunk-JWPE2WC7-VvU_gZI6.js +1 -0
  167. package/dist/src/view/client/assets/chunk-POPQ4Y6H-DDUbVF55.js +1 -0
  168. package/dist/src/view/client/assets/chunk-SVP7TREG-BHROkr6D.js +88 -0
  169. package/dist/src/view/client/assets/chunk-TICWLB2K-95PzGIOg.js +206 -0
  170. package/dist/src/view/client/assets/chunk-XXDRQBXY-MdmuLhIs.js +1 -0
  171. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +1 -0
  172. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +1 -0
  173. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +1 -0
  174. package/dist/src/view/client/assets/cynefin-OW5HDTMX-CeA8IKWo.js +166 -0
  175. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +62 -0
  176. package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
  177. package/dist/src/view/client/assets/dagre-GXQ25YYZ-DLQwTa3H.js +4 -0
  178. package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
  179. package/dist/src/view/client/assets/diagram-S7CK7UJ4-kbKdIyXG.js +30 -0
  180. package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +41 -0
  181. package/dist/src/view/client/assets/diagram-VSXAHHWV-EXJTU8qq.js +3 -0
  182. package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +24 -0
  183. package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +24 -0
  184. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +1 -0
  185. package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-C925J5A3.js +99 -0
  186. package/dist/src/view/client/assets/flowDiagram-HODETNUW-DzoCa-Nq.js +168 -0
  187. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +292 -0
  188. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +106 -0
  189. package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
  190. package/dist/src/view/client/assets/index-D0ajr_Ja.css +1 -0
  191. package/dist/src/view/client/assets/index-KZcnpXJH.js +321 -0
  192. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +2 -0
  193. package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
  194. package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-CW4mto2d.js +70 -0
  195. package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-8R4eacFW.js +139 -0
  196. package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-Cy9ioXEJ.js +89 -0
  197. package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
  198. package/dist/src/view/client/assets/layout-C0MFuhth.js +1 -0
  199. package/dist/src/view/client/assets/linear-Dv3N4dQR.js +1 -0
  200. package/dist/src/view/client/assets/logo-small-RR_KBOAT.svg +149 -0
  201. package/dist/src/view/client/assets/maplibre-gl-DnqZgfUS.js +800 -0
  202. package/dist/src/view/client/assets/mermaid.core-bds9A7e2.js +310 -0
  203. package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-DjmGW355.js +96 -0
  204. package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
  205. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +1 -0
  206. package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-CCH2gGR-.js +39 -0
  207. package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC-CjocPJFz.js +7 -0
  208. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +1 -0
  209. package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-Ct6AoXRr.js +84 -0
  210. package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-CIpgGN_w.js +40 -0
  211. package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-DrmLFj6w.js +162 -0
  212. package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +1 -0
  213. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +1 -0
  214. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +1 -0
  215. package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +1 -0
  216. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +8 -0
  217. package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
  218. package/dist/src/view/client/assets/timeline-definition-24CTP7MA-BExPu6X5.js +120 -0
  219. package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-PC1MU3si.js +34 -0
  220. package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-DNWqU3fa.js +78 -0
  221. package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-E1uA28_D.js +7 -0
  222. package/dist/src/view/client/index.html +2 -2
  223. package/dist/src/view/client-shell.d.ts +2 -0
  224. package/dist/src/view/client-shell.js +5 -0
  225. package/dist/src/view/diagnostics.d.ts +3 -2
  226. package/dist/src/view/diagnostics.js +49 -58
  227. package/dist/src/view/document-edit.d.ts +8 -0
  228. package/dist/src/view/document-edit.js +25 -0
  229. package/dist/src/view/document-route.d.ts +12 -0
  230. package/dist/src/view/document-route.js +93 -0
  231. package/dist/src/view/document-tree.d.ts +18 -0
  232. package/dist/src/view/document-tree.js +139 -0
  233. package/dist/src/view/external-document-tree.d.ts +3 -0
  234. package/dist/src/view/external-document-tree.js +674 -0
  235. package/dist/src/view/git-diff.js +187 -0
  236. package/dist/src/view/git.js +1 -1
  237. package/dist/src/view/graph.d.ts +3 -2
  238. package/dist/src/view/graph.js +94 -7
  239. package/dist/src/view/highlight.d.ts +10 -2
  240. package/dist/src/view/highlight.js +4540 -1360
  241. package/dist/src/view/markdown.d.ts +3 -2
  242. package/dist/src/view/markdown.js +214 -53
  243. package/dist/src/view/protocol.d.ts +59 -6
  244. package/dist/src/view/references.d.ts +10 -25
  245. package/dist/src/view/references.js +117 -107
  246. package/dist/src/view/repository.d.ts +13 -3
  247. package/dist/src/view/repository.js +234 -15
  248. package/dist/src/view/search.d.ts +1 -1
  249. package/dist/src/view/search.js +3 -7
  250. package/dist/src/view/server.d.ts +11 -2
  251. package/dist/src/view/server.js +212 -70
  252. package/dist/src/view/source-target.js +5 -8
  253. package/dist/src/view/static-build.d.ts +1 -0
  254. package/dist/src/view/static-build.js +245 -42
  255. package/dist/src/view/static-protocol.d.ts +19 -2
  256. package/dist/src/view/store.d.ts +16 -3
  257. package/dist/src/view/store.js +228 -36
  258. package/dist/src/view/table-of-contents.d.ts +2 -2
  259. package/dist/src/view/table-of-contents.js +2 -22
  260. package/dist/src/walk.d.ts +5 -3
  261. package/dist/src/walk.js +102 -8
  262. package/package.json +46 -8
  263. package/plugins/lat-md/README.md +61 -0
  264. package/plugins/lat-md/hooks.json +38 -0
  265. package/plugins/lat-md/plugin.json +9 -0
  266. package/plugins/lat-md/rules/AGENTS.md +118 -0
  267. package/plugins/lat-md/skills/lat-md/SKILL.md +178 -0
  268. package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +182 -0
  269. package/templates/AGENTS.md +15 -3
  270. package/templates/cursor-rules.md +12 -3
  271. package/templates/skill/SKILL.md +22 -1
  272. package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
  273. package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
  274. package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
@@ -0,0 +1,139 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { relative } from 'node:path';
3
+ import { performance } from 'node:perf_hooks';
4
+ import { PARSER_CACHE_VERSION, hashParserContent, parsedCachePath, parserCacheIdentity, readParsedCache, writeParsedCache, } from './parser-cache.js';
5
+ import { toPosix } from './path.js';
6
+ import { loadMarkdownAnalyzer } from './markdown-analyzer-loader.js';
7
+ export { PARSER_CACHE_VERSION } from './parser-cache.js';
8
+ /** Return the collision-safe, sharded cache path for one Markdown file. */
9
+ export function markdownAnalysisCachePath(latDir, projectRoot, absolutePath) {
10
+ return parsedCachePath(latDir, parserCacheIdentity(absolutePath, projectRoot));
11
+ }
12
+ function emptyTimings(values) {
13
+ return {
14
+ ...values,
15
+ cacheWriteMs: 0,
16
+ parseMs: 0,
17
+ sectionsMs: 0,
18
+ refsMs: 0,
19
+ linksMs: 0,
20
+ paragraphsMs: 0,
21
+ frontmatterMs: 0,
22
+ indexEntriesMs: 0,
23
+ diagnosticsMs: 0,
24
+ };
25
+ }
26
+ function isRecord(value) {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ }
29
+ function cachedAnalysis(entry, contentHash, content, absolutePath, latDir, projectRoot) {
30
+ if (!entry ||
31
+ entry.version !== PARSER_CACHE_VERSION ||
32
+ entry.contentHash !== contentHash) {
33
+ return null;
34
+ }
35
+ try {
36
+ const analysis = entry.value;
37
+ const expectedPath = toPosix(relative(latDir, absolutePath));
38
+ const expectedProjectPath = toPosix(relative(projectRoot, absolutePath));
39
+ if (!isRecord(analysis) ||
40
+ analysis.content !== content ||
41
+ analysis.path !== expectedPath ||
42
+ analysis.projectPath !== expectedProjectPath ||
43
+ !isRecord(analysis.frontmatter) ||
44
+ !Array.isArray(analysis.sections) ||
45
+ !Array.isArray(analysis.headingTitles) ||
46
+ !Array.isArray(analysis.wikiRefs) ||
47
+ !Array.isArray(analysis.paragraphs) ||
48
+ !Array.isArray(analysis.markdownLinks) ||
49
+ !Array.isArray(analysis.validationLinks) ||
50
+ !Array.isArray(analysis.indexEntries) ||
51
+ !Array.isArray(analysis.diagnostics)) {
52
+ return null;
53
+ }
54
+ return { ...analysis, absolutePath };
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ /** Read, hash, and attempt to hydrate one Markdown file from persistent cache. */
61
+ export async function prepareMarkdownAnalysis(absolutePath, latDir, projectRoot, cache = true) {
62
+ const cachePath = markdownAnalysisCachePath(latDir, projectRoot, absolutePath);
63
+ const readStarted = performance.now();
64
+ const contentPromise = readFile(absolutePath, 'utf8').then((content) => ({
65
+ content,
66
+ readMs: performance.now() - readStarted,
67
+ }));
68
+ const cacheStarted = performance.now();
69
+ const cachePromise = cache
70
+ ? readParsedCache(cachePath).then((entry) => ({
71
+ entry,
72
+ cacheReadMs: performance.now() - cacheStarted,
73
+ }))
74
+ : Promise.resolve({ entry: null, cacheReadMs: 0 });
75
+ const [{ content, readMs }, { entry, cacheReadMs }] = await Promise.all([
76
+ contentPromise,
77
+ cachePromise,
78
+ ]);
79
+ const hashStarted = performance.now();
80
+ const contentHash = hashParserContent(content);
81
+ const hashMs = performance.now() - hashStarted;
82
+ const analysis = cache
83
+ ? cachedAnalysis(entry, contentHash, content, absolutePath, latDir, projectRoot)
84
+ : null;
85
+ const timings = {
86
+ readMs,
87
+ hashMs,
88
+ cacheReadMs,
89
+ cacheStatus: cache ? (analysis ? 'hit' : 'miss') : 'disabled',
90
+ };
91
+ return {
92
+ absolutePath,
93
+ cachePath,
94
+ content,
95
+ contentHash,
96
+ analysis: analysis
97
+ ? { ...analysis, timings: emptyTimings(timings) }
98
+ : undefined,
99
+ timings,
100
+ };
101
+ }
102
+ /** Attach I/O timings and best-effort publish one newly parsed analysis. */
103
+ export async function publishMarkdownAnalysis(prepared, analysis) {
104
+ const base = {
105
+ ...analysis,
106
+ timings: {
107
+ ...analysis.timings,
108
+ ...prepared.timings,
109
+ cacheWriteMs: 0,
110
+ },
111
+ };
112
+ if (prepared.timings.cacheStatus === 'disabled')
113
+ return base;
114
+ const started = performance.now();
115
+ try {
116
+ await writeParsedCache(prepared.cachePath, prepared.contentHash, base);
117
+ }
118
+ catch {
119
+ // The cache is a disposable optimization; analysis must work read-only.
120
+ }
121
+ base.timings.cacheWriteMs = performance.now() - started;
122
+ return base;
123
+ }
124
+ /** Analyze one path through the same persistent cache used by project runs. */
125
+ export async function analyzeMarkdownPath(absolutePath, latDir, projectRoot, cache = true, onParserImport) {
126
+ const prepared = await prepareMarkdownAnalysis(absolutePath, latDir, projectRoot, cache);
127
+ const detail = toPosix(relative(latDir, absolutePath));
128
+ if (prepared.analysis) {
129
+ onParserImport?.({
130
+ parser: 'Markdown analyzer',
131
+ imported: false,
132
+ durationMs: 0,
133
+ detail: `${detail} cached`,
134
+ });
135
+ return prepared.analysis;
136
+ }
137
+ const analyzeMarkdownFile = await loadMarkdownAnalyzer(onParserImport, detail);
138
+ return publishMarkdownAnalysis(prepared, analyzeMarkdownFile(absolutePath, prepared.content, latDir, projectRoot));
139
+ }
@@ -0,0 +1,16 @@
1
+ import type { MarkdownFileAnalysis } from './markdown-analysis.js';
2
+ export type MarkdownWorkerTask = {
3
+ id: number;
4
+ absolutePath: string;
5
+ content: string;
6
+ latDir: string;
7
+ projectRoot: string;
8
+ };
9
+ export type MarkdownWorkerResponse = {
10
+ id: number;
11
+ analysis: MarkdownFileAnalysis;
12
+ importMs?: number;
13
+ } | {
14
+ id: number;
15
+ error: string;
16
+ };
@@ -0,0 +1,33 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import { performance } from 'node:perf_hooks';
3
+ if (!parentPort)
4
+ throw new Error('Markdown analysis worker needs a parent port');
5
+ async function loadAnalyzer() {
6
+ const started = performance.now();
7
+ const sourceRuntime = import.meta.url.endsWith('.ts');
8
+ const moduleUrl = new URL(sourceRuntime ? './markdown-analysis.ts' : './markdown-analysis.js', import.meta.url).href;
9
+ const module = sourceRuntime
10
+ ? await import('tsx/esm/api').then(({ tsImport }) => tsImport(moduleUrl, import.meta.url))
11
+ : await import(moduleUrl);
12
+ return {
13
+ analyzeMarkdownFile: module.analyzeMarkdownFile,
14
+ importMs: performance.now() - started,
15
+ };
16
+ }
17
+ const analyzerPromise = loadAnalyzer();
18
+ let importReported = false;
19
+ parentPort.on('message', async (task) => {
20
+ try {
21
+ const loaded = await analyzerPromise;
22
+ const importMs = importReported ? undefined : loaded.importMs;
23
+ importReported = true;
24
+ const analysis = loaded.analyzeMarkdownFile(task.absolutePath, task.content, task.latDir, task.projectRoot);
25
+ parentPort.postMessage({ id: task.id, analysis, importMs });
26
+ }
27
+ catch (error) {
28
+ parentPort.postMessage({
29
+ id: task.id,
30
+ error: error instanceof Error ? error.message : String(error),
31
+ });
32
+ }
33
+ });
@@ -0,0 +1,50 @@
1
+ import type { LatFrontmatter, MdLink, Ref, Section } from './lattice-model.js';
2
+ import { type LocalMarkdownDiagnostic } from './markdown-validation.js';
3
+ export type MarkdownParagraph = {
4
+ markdown: string;
5
+ startLine: number;
6
+ endLine: number;
7
+ text: string;
8
+ };
9
+ export type MarkdownDestinationLink = {
10
+ kind: 'image' | 'link';
11
+ line: number;
12
+ url: string;
13
+ };
14
+ export type MarkdownAnalysisTimings = {
15
+ readMs: number;
16
+ hashMs: number;
17
+ cacheReadMs: number;
18
+ cacheWriteMs: number;
19
+ cacheStatus: 'disabled' | 'hit' | 'miss';
20
+ parseMs: number;
21
+ sectionsMs: number;
22
+ refsMs: number;
23
+ linksMs: number;
24
+ paragraphsMs: number;
25
+ frontmatterMs: number;
26
+ indexEntriesMs: number;
27
+ diagnosticsMs: number;
28
+ };
29
+ /** Serializable semantic facts extracted from one Markdown file. */
30
+ export type MarkdownFileAnalysis = {
31
+ absolutePath: string;
32
+ content: string;
33
+ path: string;
34
+ projectPath: string;
35
+ frontmatter: LatFrontmatter;
36
+ sections: Section[];
37
+ headingTitles: string[];
38
+ wikiRefs: Ref[];
39
+ paragraphs: MarkdownParagraph[];
40
+ markdownLinks: MarkdownDestinationLink[];
41
+ validationLinks: MdLink[];
42
+ indexEntries: string[];
43
+ diagnostics: LocalMarkdownDiagnostic[];
44
+ timings: MarkdownAnalysisTimings;
45
+ };
46
+ /**
47
+ * Parse and analyze one Markdown document. The syntax tree is deliberately
48
+ * confined to this call so callers and workers only exchange serializable facts.
49
+ */
50
+ export declare function analyzeMarkdownFile(absolutePath: string, content: string, latDir: string, projectRoot: string): MarkdownFileAnalysis;
@@ -0,0 +1,139 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { relative } from 'node:path';
3
+ import { visit } from 'unist-util-visit';
4
+ import { extractLinks, extractRefs, parseFrontmatter, parseSections, } from './lattice.js';
5
+ import { parse } from './parser.js';
6
+ import { toPosix } from './path.js';
7
+ import { analyzeLocalMarkdownDiagnostics, } from './markdown-validation.js';
8
+ function elapsed(work) {
9
+ const start = performance.now();
10
+ return [work(), performance.now() - start];
11
+ }
12
+ function inlineText(node) {
13
+ if (node.type === 'wikiLink')
14
+ return node.data.alias ?? node.value;
15
+ if ('value' in node && typeof node.value === 'string')
16
+ return node.value;
17
+ if (node.type === 'image')
18
+ return node.alt ?? '';
19
+ if (!('children' in node) || !Array.isArray(node.children))
20
+ return '';
21
+ return node.children
22
+ .map((child) => inlineText(child))
23
+ .join('');
24
+ }
25
+ function extractParagraphs(content, tree) {
26
+ const result = [];
27
+ visit(tree, 'paragraph', (node) => {
28
+ const startLine = node.position?.start.line;
29
+ const endLine = node.position?.end.line;
30
+ if (!startLine || !endLine)
31
+ return;
32
+ const text = inlineText(node).replace(/\s+/g, ' ').trim();
33
+ const startOffset = node.position?.start.offset;
34
+ const endOffset = node.position?.end.offset;
35
+ result.push({
36
+ markdown: startOffset === undefined || endOffset === undefined
37
+ ? text
38
+ : content.slice(startOffset, endOffset),
39
+ startLine,
40
+ endLine,
41
+ text,
42
+ });
43
+ });
44
+ return result;
45
+ }
46
+ function extractHeadingTitles(tree) {
47
+ const titles = [];
48
+ visit(tree, 'heading', (node) => {
49
+ titles.push(inlineText(node));
50
+ });
51
+ return titles;
52
+ }
53
+ function extractDestinationLinks(tree) {
54
+ const definitions = new Map();
55
+ visit(tree, 'definition', (node) => {
56
+ definitions.set(node.identifier.toLowerCase(), node.url);
57
+ });
58
+ const links = [];
59
+ visit(tree, (node) => {
60
+ if (node.type !== 'link' &&
61
+ node.type !== 'image' &&
62
+ node.type !== 'linkReference' &&
63
+ node.type !== 'imageReference') {
64
+ return;
65
+ }
66
+ const line = node.position?.start.line;
67
+ if (!line)
68
+ return;
69
+ const url = node.type === 'link' || node.type === 'image'
70
+ ? node.url
71
+ : definitions.get(node.identifier.toLowerCase());
72
+ if (!url)
73
+ return;
74
+ links.push({
75
+ kind: node.type === 'image' || node.type === 'imageReference'
76
+ ? 'image'
77
+ : 'link',
78
+ line,
79
+ url,
80
+ });
81
+ });
82
+ return links;
83
+ }
84
+ function extractIndexEntries(content) {
85
+ const names = new Set();
86
+ const pattern = /^- \[\[([^\]]+?)(?:\|[^\]]+)?\]\]/gm;
87
+ let match;
88
+ while ((match = pattern.exec(content)) !== null)
89
+ names.add(match[1]);
90
+ return [...names];
91
+ }
92
+ /**
93
+ * Parse and analyze one Markdown document. The syntax tree is deliberately
94
+ * confined to this call so callers and workers only exchange serializable facts.
95
+ */
96
+ export function analyzeMarkdownFile(absolutePath, content, latDir, projectRoot) {
97
+ const [tree, parseMs] = elapsed(() => parse(content));
98
+ const [sections, sectionsMs] = elapsed(() => parseSections(absolutePath, content, projectRoot, tree));
99
+ const [wikiRefs, refsMs] = elapsed(() => extractRefs(absolutePath, content, projectRoot, tree));
100
+ const [validationLinks, linksMs] = elapsed(() => extractLinks(content, tree));
101
+ const [[paragraphs, markdownLinks, headingTitles], paragraphsMs] = elapsed(() => [
102
+ extractParagraphs(content, tree),
103
+ extractDestinationLinks(tree),
104
+ extractHeadingTitles(tree),
105
+ ]);
106
+ const [frontmatter, frontmatterMs] = elapsed(() => parseFrontmatter(content));
107
+ const [indexEntries, indexEntriesMs] = elapsed(() => extractIndexEntries(content));
108
+ const [diagnostics, diagnosticsMs] = elapsed(() => analyzeLocalMarkdownDiagnostics(validationLinks, sections));
109
+ return {
110
+ absolutePath,
111
+ content,
112
+ path: toPosix(relative(latDir, absolutePath)),
113
+ projectPath: toPosix(relative(projectRoot, absolutePath)),
114
+ frontmatter,
115
+ sections,
116
+ headingTitles,
117
+ wikiRefs,
118
+ paragraphs,
119
+ markdownLinks,
120
+ validationLinks,
121
+ indexEntries,
122
+ diagnostics,
123
+ timings: {
124
+ readMs: 0,
125
+ hashMs: 0,
126
+ cacheReadMs: 0,
127
+ cacheWriteMs: 0,
128
+ cacheStatus: 'disabled',
129
+ parseMs,
130
+ sectionsMs,
131
+ refsMs,
132
+ linksMs,
133
+ paragraphsMs,
134
+ frontmatterMs,
135
+ indexEntriesMs,
136
+ diagnosticsMs,
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,4 @@
1
+ import type { analyzeMarkdownFile } from './markdown-analysis.js';
2
+ import type { ParserImportObserver } from './parser-import.js';
3
+ /** Load the Markdown analyzer at the cache-miss boundary and report its cost. */
4
+ export declare function loadMarkdownAnalyzer(onParserImport?: ParserImportObserver, detail?: string): Promise<typeof analyzeMarkdownFile>;
@@ -0,0 +1,13 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ /** Load the Markdown analyzer at the cache-miss boundary and report its cost. */
3
+ export async function loadMarkdownAnalyzer(onParserImport, detail) {
4
+ const started = performance.now();
5
+ const module = await import('./markdown-analysis.js');
6
+ onParserImport?.({
7
+ parser: 'Markdown analyzer',
8
+ imported: true,
9
+ durationMs: performance.now() - started,
10
+ detail,
11
+ });
12
+ return module.analyzeMarkdownFile;
13
+ }
@@ -0,0 +1,25 @@
1
+ import { type MdLink, type Section } from './lattice-model.js';
2
+ export declare const MAX_SECTION_SUMMARY_LENGTH = 250;
3
+ export type LocalMarkdownDiagnostic = {
4
+ rule: 'markdown-reference-definition' | 'markdown-path-separator' | 'section-leading-paragraph';
5
+ line: number;
6
+ target: string;
7
+ message: string;
8
+ anchor?: string;
9
+ marker?: 'target' | 'line' | 'heading';
10
+ };
11
+ export type LocalMarkdownTarget = {
12
+ kind: 'target';
13
+ /** Decoded on-disk path, or null for a fragment in the current file. */
14
+ path: string | null;
15
+ /** Decoded fragment without `#`, or null when none was authored. */
16
+ fragment: string | null;
17
+ } | {
18
+ kind: 'invalid-backslash';
19
+ };
20
+ /** Parse a local Markdown destination without confusing escaped #/? in paths. */
21
+ export declare function parseLocalMarkdownTarget(url: string): LocalMarkdownTarget | null;
22
+ /** Count summary text while excluding `[[...]]` markers and link content. */
23
+ export declare function sectionSummaryLength(body: string): number;
24
+ /** Validate rules that need no facts outside one parsed Markdown file. */
25
+ export declare function analyzeLocalMarkdownDiagnostics(links: readonly MdLink[], sections: readonly Section[]): LocalMarkdownDiagnostic[];
@@ -0,0 +1,104 @@
1
+ import { flattenSections } from './lattice-model.js';
2
+ export const MAX_SECTION_SUMMARY_LENGTH = 250;
3
+ function decodeLinkPart(value) {
4
+ try {
5
+ return decodeURIComponent(value);
6
+ }
7
+ catch {
8
+ return value;
9
+ }
10
+ }
11
+ /** Parse a local Markdown destination without confusing escaped #/? in paths. */
12
+ export function parseLocalMarkdownTarget(url) {
13
+ const value = url.trim();
14
+ if (value.startsWith('/'))
15
+ return null;
16
+ const windowsDrivePath = /^[a-zA-Z]:\\/.test(value);
17
+ if (!windowsDrivePath && /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {
18
+ return null;
19
+ }
20
+ // Split before decoding: `%23` and `%3F` decode to `#` and `?`, which would
21
+ // then truncate a filename that legitimately contains them.
22
+ const queryAt = value.indexOf('?');
23
+ const fragmentAt = value.indexOf('#');
24
+ const pathEnd = Math.min(queryAt === -1 ? value.length : queryAt, fragmentAt === -1 ? value.length : fragmentAt);
25
+ const rawPath = value.slice(0, pathEnd);
26
+ const path = rawPath === '' ? null : decodeLinkPart(rawPath);
27
+ const fragment = fragmentAt === -1 ? null : decodeLinkPart(value.slice(fragmentAt + 1));
28
+ if (rawPath === '' && fragment === null)
29
+ return null;
30
+ if (path?.includes('\\'))
31
+ return { kind: 'invalid-backslash' };
32
+ return { kind: 'target', path, fragment };
33
+ }
34
+ /** Count summary text while excluding `[[...]]` markers and link content. */
35
+ export function sectionSummaryLength(body) {
36
+ return body.replace(/\[\[[^\]]*\]\]/g, '').length;
37
+ }
38
+ /** Validate rules that need no facts outside one parsed Markdown file. */
39
+ export function analyzeLocalMarkdownDiagnostics(links, sections) {
40
+ const diagnostics = [];
41
+ for (const link of links) {
42
+ if ('identifier' in link) {
43
+ const kind = link.kind === 'imageReference' ? 'image' : 'link';
44
+ const renderedKind = kind === 'image' ? 'an image' : 'a link';
45
+ const escapedSource = link.source.replace('[', '\\[');
46
+ diagnostics.push({
47
+ rule: 'markdown-reference-definition',
48
+ line: link.line,
49
+ target: link.identifier,
50
+ message: link.style === 'definition'
51
+ ? `malformed reference definition (${link.source}) — ` +
52
+ `write it as "[${link.identifier}]: <destination>" on its own line, ` +
53
+ `or escape the opening bracket as "${escapedSource}" to keep it as literal text`
54
+ : link.style === 'shortcut'
55
+ ? `undefined shortcut ${kind} reference (${link.source}) — ` +
56
+ `add a definition "[${link.identifier}]: <destination>" to make it ${renderedKind}, ` +
57
+ `or escape the opening bracket as "${escapedSource}" to keep it as literal text`
58
+ : `undefined ${kind} reference (${link.source}) — definition "[${link.identifier}]" not found`,
59
+ marker: 'line',
60
+ });
61
+ continue;
62
+ }
63
+ if (parseLocalMarkdownTarget(link.url)?.kind === 'invalid-backslash') {
64
+ const kind = link.kind === 'image' ? 'image' : 'link';
65
+ diagnostics.push({
66
+ rule: 'markdown-path-separator',
67
+ line: link.line,
68
+ target: link.url,
69
+ message: `invalid ${kind} (${link.url}) — backslashes are not path ` +
70
+ 'separators in Markdown; use "/" instead',
71
+ });
72
+ }
73
+ }
74
+ for (const section of flattenSections([...sections])) {
75
+ if (!section.firstParagraph) {
76
+ diagnostics.push({
77
+ rule: 'section-leading-paragraph',
78
+ line: section.startLine,
79
+ target: section.id,
80
+ message: `section "${section.id}" has no leading paragraph. ` +
81
+ `Every section must start with a brief overview (≤${MAX_SECTION_SUMMARY_LENGTH} chars) ` +
82
+ `summarizing what it documents — this powers search snippets and command output.`,
83
+ anchor: section.githubSlug,
84
+ marker: 'heading',
85
+ });
86
+ continue;
87
+ }
88
+ const length = sectionSummaryLength(section.firstParagraph);
89
+ if (length > MAX_SECTION_SUMMARY_LENGTH) {
90
+ diagnostics.push({
91
+ rule: 'section-leading-paragraph',
92
+ line: section.startLine,
93
+ target: section.id,
94
+ message: `section "${section.id}" leading paragraph is ${length} characters ` +
95
+ `(max ${MAX_SECTION_SUMMARY_LENGTH}, excluding [[wiki links]]). ` +
96
+ `Keep the first paragraph brief — it serves as the section's summary ` +
97
+ `in search results and command output. Use subsequent paragraphs for details.`,
98
+ anchor: section.githubSlug,
99
+ marker: 'heading',
100
+ });
101
+ }
102
+ }
103
+ return diagnostics;
104
+ }
@@ -2,14 +2,16 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod';
4
4
  import { dirname } from 'node:path';
5
- import { findLatticeDir } from '../lattice.js';
5
+ import { findLatticeDir } from '../project-discovery.js';
6
6
  import { plainStyler } from '../context.js';
7
7
  import { locateCommand } from '../cli/locate.js';
8
8
  import { sectionCommand } from '../cli/section.js';
9
9
  import { searchCommand } from '../cli/search.js';
10
+ import { DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_THRESHOLD, } from '../search/search.js';
10
11
  import { expandCommand } from '../cli/expand.js';
11
12
  import { checkAllCommand } from '../cli/check.js';
12
13
  import { refsCommand } from '../cli/refs.js';
14
+ import { externalListCommand, externalShowCommand } from '../cli/external.js';
13
15
  function toMcp(result) {
14
16
  const content = [{ type: 'text', text: result.output }];
15
17
  return result.isError ? { content, isError: true } : { content };
@@ -27,24 +29,34 @@ export async function startMcpServer() {
27
29
  styler: plainStyler,
28
30
  mode: 'mcp',
29
31
  };
32
+ const requestContext = () => ({ ...ctx, analysis: undefined });
30
33
  const server = new McpServer({
31
34
  name: 'lat',
32
35
  version: '1.0.0',
33
36
  });
34
- server.tool('lat_locate', 'Find sections by name (exact, fuzzy, subsequence matching)', { query: z.string().describe('Section name or id to search for') }, async ({ query }) => toMcp(await locateCommand(ctx, query)));
35
- server.tool('lat_section', 'Show a section with its content, outgoing wiki link targets, and incoming references', {
36
- query: z.string().describe('Section id to look up (short or full form)'),
37
- }, async ({ query }) => toMcp(await sectionCommand(ctx, query)));
37
+ server.tool('lat_locate', 'Find sections by name (exact, fuzzy, subsequence matching)', { query: z.string().describe('Section name or id to search for') }, async ({ query }) => toMcp(await locateCommand(requestContext(), query)));
38
+ server.tool('lat_section', 'Show a local section or exact external target with content and references', {
39
+ query: z
40
+ .string()
41
+ .describe('Local section id or exact handle:path#fragment target'),
42
+ }, async ({ query }) => toMcp(await sectionCommand(requestContext(), query)));
38
43
  server.tool('lat_search', 'Semantic search across lat.md sections using embeddings', {
39
44
  query: z.string().describe('Search query in natural language'),
40
45
  limit: z
41
46
  .number()
42
47
  .optional()
43
- .default(5)
44
- .describe('Max results (default 5)'),
45
- }, async ({ query, limit }) => toMcp(await searchCommand(ctx, query, { limit })));
46
- server.tool('lat_expand', 'Expand [[refs]] in text to resolved lat.md section paths with context', { text: z.string().describe('Text containing [[refs]] to expand') }, async ({ text: input }) => toMcp(await expandCommand(ctx, input)));
47
- server.tool('lat_check', 'Run full lat.md validation: links, code references, indexes, and section structure', {}, async () => toMcp(await checkAllCommand(ctx)));
48
+ .default(DEFAULT_SEARCH_LIMIT)
49
+ .describe(`Max results (default ${DEFAULT_SEARCH_LIMIT})`),
50
+ threshold: z
51
+ .number()
52
+ .min(0)
53
+ .max(1)
54
+ .optional()
55
+ .default(DEFAULT_SEARCH_THRESHOLD)
56
+ .describe('Minimum cosine similarity score (default 0.35)'),
57
+ }, async ({ query, limit, threshold }) => toMcp(await searchCommand(requestContext(), query, { limit, threshold })));
58
+ server.tool('lat_expand', 'Expand [[refs]] in text to resolved lat.md section paths with context', { text: z.string().describe('Text containing [[refs]] to expand') }, async ({ text: input }) => toMcp(await expandCommand(requestContext(), input)));
59
+ server.tool('lat_check', 'Run full lat.md validation: links, code references, indexes, and section structure', {}, async () => toMcp(await checkAllCommand(requestContext())));
48
60
  server.tool('lat_refs', 'Find sections that reference a given section via wiki links or @lat code comments', {
49
61
  query: z.string().describe('Section id to find references for'),
50
62
  scope: z
@@ -52,7 +64,11 @@ export async function startMcpServer() {
52
64
  .optional()
53
65
  .default('md+code')
54
66
  .describe('Where to search: md, code, or md+code'),
55
- }, async ({ query, scope }) => toMcp(await refsCommand(ctx, query, scope)));
67
+ }, async ({ query, scope }) => toMcp(await refsCommand(requestContext(), query, scope)));
68
+ server.tool('lat_external_list', 'List configured external sources without fetching or changing caches', {}, async () => toMcp(await externalListCommand(ctx, true)));
69
+ server.tool('lat_external_show', 'Show one configured external source or exact external target without fetching it', {
70
+ source: z.string().describe('External source handle or exact target'),
71
+ }, async ({ source }) => toMcp(await externalShowCommand(ctx, source, true)));
56
72
  const transport = new StdioServerTransport();
57
73
  await server.connect(transport);
58
74
  }
@@ -0,0 +1,17 @@
1
+ /** Version of persistent parser outputs and their shared on-disk contract. */
2
+ export declare const PARSER_CACHE_VERSION = 1;
3
+ export type ParsedCacheEntry = {
4
+ version: number;
5
+ contentHash: string;
6
+ value: unknown;
7
+ };
8
+ /** Hash complete parser input bytes for cache invalidation. */
9
+ export declare function hashParserContent(content: string): string;
10
+ /** Normalize an on-disk file into its project-relative parser identity. */
11
+ export declare function parserCacheIdentity(absolutePath: string, projectRoot: string): string;
12
+ /** Return the collision-safe, short-name-sharded cache path for an identity. */
13
+ export declare function parsedCachePath(latDir: string, identity: string): string;
14
+ /** Read and decode a cache entry, treating every malformed state as a miss. */
15
+ export declare function readParsedCache(path: string): Promise<ParsedCacheEntry | null>;
16
+ /** Atomically publish one disposable parser cache entry. */
17
+ export declare function writeParsedCache(path: string, contentHash: string, value: unknown): Promise<void>;