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

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 (278) 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/dist/vendor/lat.md-server/cli.d.ts +2 -0
  263. package/dist/vendor/lat.md-server/cli.js +12 -0
  264. package/dist/vendor/lat.md-server/index.d.ts +38 -0
  265. package/dist/vendor/lat.md-server/index.js +128 -0
  266. package/package.json +52 -12
  267. package/plugins/lat-md/README.md +61 -0
  268. package/plugins/lat-md/hooks.json +38 -0
  269. package/plugins/lat-md/plugin.json +9 -0
  270. package/plugins/lat-md/rules/AGENTS.md +118 -0
  271. package/plugins/lat-md/skills/lat-md/SKILL.md +178 -0
  272. package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +182 -0
  273. package/templates/AGENTS.md +15 -3
  274. package/templates/cursor-rules.md +12 -3
  275. package/templates/skill/SKILL.md +22 -1
  276. package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
  277. package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
  278. package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
@@ -1,21 +1,29 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { watch as watchFiles } from 'node:fs';
2
- import { readFile, realpath } from 'node:fs/promises';
3
+ import { readFile, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
3
4
  import { basename, extname, isAbsolute, relative, resolve, sep, } from 'node:path';
4
- import { LAT_REF_RE, scanCodeRefs } from '../code-refs.js';
5
- import { listLatticeFiles, parseFrontmatter, } from '../lattice.js';
6
- import { SOURCE_EXTENSIONS } from '../source-parser.js';
7
- import { toPosix } from '../walk.js';
5
+ import { createCodeReferenceDiscovery, LAT_REF_RE, } from '../code-refs.js';
6
+ import { createExternalResolver, } from '../external-sources.js';
7
+ import { listLatticeFiles } from '../project-discovery.js';
8
+ import { analyzeMarkdownPath } from '../markdown-analysis-cache.js';
9
+ import { isSourceFileExtension } from '../source-formats.js';
10
+ import { toPosix } from '../path.js';
8
11
  import { renderMarkdown } from './markdown.js';
9
12
  import { buildViewDiagnostics } from './diagnostics.js';
13
+ import { applyDocumentEdit, ViewDocumentConflictError, } from './document-edit.js';
10
14
  import { buildGitDiffTree } from './git-diff.js';
11
15
  import { buildViewGraph } from './graph.js';
12
16
  import { buildViewTableOfContents } from './table-of-contents.js';
13
17
  import { emptyViewGitSnapshot, findViewGitRepository, readViewGitSnapshot, sameViewGitSnapshot, } from './git.js';
14
18
  import { DEFAULT_VIEW_LOGO_TEXT } from './protocol.js';
15
- import { createMarkdownWikiLinkResolver, getViewSource, ViewDocumentNotFoundError, } from './repository.js';
16
- import { buildViewReferenceIndex, parseViewMarkdownFile, renderSectionBackReferences, } from './references.js';
19
+ import { createMarkdownWikiLinkResolver, getViewExternal, getViewSource, ViewDocumentNotFoundError, } from './repository.js';
20
+ import { buildViewReferenceIndex, renderSectionBackReferences, } from './references.js';
21
+ import { rewriteDocumentLink } from './document-route.js';
17
22
  const DEFAULT_DEBOUNCE_MS = 75;
18
23
  const DEFAULT_GIT_POLL_MS = 2_000;
24
+ const EXTERNAL_REFRESH_PATH = '@lat-external-refresh';
25
+ const DOCUMENT_EDIT_WRITE_ATTEMPTS = 3;
26
+ const DOCUMENT_EDIT_TEMP_PREFIX = '.lat-edit-';
19
27
  function isInside(root, candidate) {
20
28
  const rel = relative(root, candidate);
21
29
  return (rel === '' ||
@@ -25,12 +33,16 @@ function projectPath(projectRoot, path) {
25
33
  const normalized = isAbsolute(path) ? relative(projectRoot, path) : path;
26
34
  return toPosix(normalized).replace(/^\.\//, '');
27
35
  }
36
+ function isInternalLatCachePath(path, latPath) {
37
+ const cachePath = latPath ? `${latPath}/.cache` : '.cache';
38
+ return path === cachePath || path.startsWith(`${cachePath}/`);
39
+ }
28
40
  function excludedCodePath(projectRoot, path, excludedPaths) {
29
41
  const absolutePath = resolve(projectRoot, path);
30
42
  return excludedPaths.some((root) => isInside(resolve(root), absolutePath));
31
43
  }
32
44
  function sourcePath(path) {
33
- return SOURCE_EXTENSIONS.has(extname(path).toLowerCase());
45
+ return isSourceFileExtension(extname(path).toLowerCase());
34
46
  }
35
47
  function obviouslyIgnoredCodePath(path, latPath) {
36
48
  const parts = path.split('/');
@@ -73,9 +85,13 @@ async function loadCodeReferenceFiles(projectRoot, refs) {
73
85
  return files;
74
86
  }
75
87
  async function scanCodeState(projectRoot, excludedPaths = []) {
76
- const scan = await scanCodeRefs(projectRoot);
88
+ const discovery = createCodeReferenceDiscovery(projectRoot);
89
+ const [scan, sourceFiles] = await Promise.all([
90
+ discovery.scan(),
91
+ discovery.listSourceFiles(),
92
+ ]);
77
93
  const allowed = (path) => !excludedCodePath(projectRoot, path, excludedPaths);
78
- const files = scan.files.filter(allowed);
94
+ const files = sourceFiles.filter(allowed);
79
95
  const refs = scan.refs.filter((ref) => allowed(ref.file));
80
96
  const scope = new Set(files.map((path) => projectPath(projectRoot, path)));
81
97
  return {
@@ -83,14 +99,34 @@ async function scanCodeState(projectRoot, excludedPaths = []) {
83
99
  scope,
84
100
  };
85
101
  }
86
- function viewIndex(latDir, paths, diagnostics, git) {
102
+ function viewIndex(latDir, paths, diagnostics, git, references, external) {
87
103
  const files = [...paths].sort();
104
+ const externalFiles = new Map();
105
+ for (const target of references.externalByTarget.keys()) {
106
+ try {
107
+ const parsed = external.parse(target);
108
+ if (!parsed)
109
+ continue;
110
+ const hash = parsed.identity.indexOf('#');
111
+ const baseTarget = hash === -1 ? parsed.identity : parsed.identity.slice(0, hash);
112
+ externalFiles.set(baseTarget, {
113
+ handle: parsed.handle,
114
+ path: parsed.resolvedPath,
115
+ target: baseTarget,
116
+ });
117
+ }
118
+ catch {
119
+ // Invalid external targets remain diagnostics, not sidebar entries.
120
+ }
121
+ }
88
122
  const directoryName = basename(latDir);
89
123
  const indexName = directoryName.endsWith('.md')
90
124
  ? directoryName
91
125
  : `${directoryName}.md`;
92
126
  return {
93
127
  files,
128
+ externalFiles: [...externalFiles.values()].sort((left, right) => left.handle.localeCompare(right.handle) ||
129
+ left.path.localeCompare(right.path)),
94
130
  entry: files.includes(indexName) ? indexName : (files[0] ?? ''),
95
131
  errorCounts: Object.fromEntries([...diagnostics]
96
132
  .filter(([, errors]) => errors.length > 0)
@@ -103,21 +139,34 @@ function viewIndex(latDir, paths, diagnostics, git) {
103
139
  logoText: DEFAULT_VIEW_LOGO_TEXT,
104
140
  };
105
141
  }
106
- async function buildSnapshot(latDir, projectRoot, markdownFiles, codeFiles, git, generation, markdownGeneration) {
142
+ async function buildSnapshot(latDir, projectRoot, markdownFiles, codeFiles, git, generation, markdownGeneration, options) {
107
143
  const files = new Map([...markdownFiles].sort(([left], [right]) => left.localeCompare(right)));
108
144
  const allSections = [...files.values()].flatMap((file) => file.sections);
109
- const diagnostics = await buildViewDiagnostics(files.values(), codeFiles.values(), allSections, projectRoot);
110
- const references = buildViewReferenceIndex(files.values(), codeFiles.values(), allSections);
145
+ const external = await createExternalResolver(latDir, projectRoot, {
146
+ ignoreLocal: options.externalIgnoreLocal,
147
+ ca: options.externalCa,
148
+ });
149
+ await external.reconcile();
150
+ const diagnostics = await buildViewDiagnostics(files.values(), codeFiles.values(), allSections, projectRoot, external, latDir);
151
+ const references = buildViewReferenceIndex(files.values(), codeFiles.values(), allSections, (target) => {
152
+ try {
153
+ return external.parse(target)?.identity ?? null;
154
+ }
155
+ catch {
156
+ return target;
157
+ }
158
+ });
111
159
  return {
112
160
  generation,
113
161
  markdownGeneration,
114
162
  files,
115
163
  allSections,
116
164
  references,
117
- graph: buildViewGraph(files.values(), codeFiles.values(), allSections, diagnostics, git, generation),
165
+ graph: buildViewGraph(files.values(), codeFiles.values(), allSections, diagnostics, git, generation, external),
118
166
  diagnostics,
119
167
  git,
120
- index: viewIndex(latDir, [...files.keys()], diagnostics, git),
168
+ index: viewIndex(latDir, [...files.keys()], diagnostics, git, references, external),
169
+ external,
121
170
  };
122
171
  }
123
172
  /** Own the immutable, incrementally refreshed project snapshot for `lat ui`. */
@@ -133,11 +182,13 @@ export class ViewStore {
133
182
  ignoredCodePaths = new Set();
134
183
  listeners = new Set();
135
184
  watcher = null;
185
+ externalWatchers = [];
136
186
  pendingPaths = new Set();
137
187
  debounceTimer = null;
138
188
  gitPollTimer = null;
139
189
  gitPollQueued = false;
140
190
  refreshTail = Promise.resolve();
191
+ editTail = Promise.resolve();
141
192
  closed = false;
142
193
  constructor(latDir, projectRoot, realLatDir, snapshot, codeFiles, codeScope, gitRepository, options) {
143
194
  this.latDir = latDir;
@@ -168,6 +219,29 @@ export class ViewStore {
168
219
  catch (error) {
169
220
  process.stderr.write(`lat ui watcher: ${error.message}\n`);
170
221
  }
222
+ this.refreshExternalWatchers();
223
+ }
224
+ refreshExternalWatchers() {
225
+ for (const watcher of this.externalWatchers)
226
+ watcher.close();
227
+ this.externalWatchers = [];
228
+ if (this.options.watch === false)
229
+ return;
230
+ const paths = new Set([...this.snapshotValue.external.snapshot.sources.values()]
231
+ .map((source) => source.localPath)
232
+ .filter((path) => Boolean(path)));
233
+ for (const path of paths) {
234
+ try {
235
+ const watcher = watchFiles(path, { recursive: true }, () => this.scheduleRefresh(EXTERNAL_REFRESH_PATH));
236
+ watcher.on('error', (error) => {
237
+ process.stderr.write(`lat ui external watcher: ${error.message}\n`);
238
+ });
239
+ this.externalWatchers.push(watcher);
240
+ }
241
+ catch (error) {
242
+ process.stderr.write(`lat ui external watcher: ${error.message}\n`);
243
+ }
244
+ }
171
245
  }
172
246
  subscribe(listener) {
173
247
  this.listeners.add(listener);
@@ -179,6 +253,15 @@ export class ViewStore {
179
253
  getGraph() {
180
254
  return this.snapshotValue.graph;
181
255
  }
256
+ async renderSectionOutput(markdown, sectionId) {
257
+ const snapshot = this.snapshotValue;
258
+ const section = snapshot.allSections.find((candidate) => candidate.id.toLowerCase() === sectionId.toLowerCase());
259
+ const requestedPath = section
260
+ ? toPosix(relative(this.latDir, resolve(this.projectRoot, section.filePath)))
261
+ : 'section-output.md';
262
+ const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references, snapshot.external);
263
+ return (await renderMarkdown(markdown, requestedPath, resolver)).tree;
264
+ }
182
265
  async getDocument(requestedPath) {
183
266
  if (!requestedPath ||
184
267
  requestedPath.includes('\\') ||
@@ -191,24 +274,27 @@ export class ViewStore {
191
274
  if (!file) {
192
275
  throw new ViewDocumentNotFoundError('Markdown document not found');
193
276
  }
194
- const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references);
195
- const rendered = await renderMarkdown(file.content, requestedPath, resolver, { errors: [...(snapshot.diagnostics.get(requestedPath) ?? [])] }, file.tree);
277
+ const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references, snapshot.external);
278
+ const rendered = await renderMarkdown(file.content, requestedPath, resolver, {
279
+ errors: [...(snapshot.diagnostics.get(requestedPath) ?? [])],
280
+ rewriteMarkdownLink: (url) => rewriteDocumentLink(url, requestedPath),
281
+ });
196
282
  const errors = [...(snapshot.diagnostics.get(requestedPath) ?? [])];
197
283
  const gitFile = snapshot.git.files.get(requestedPath);
198
284
  const gitTree = gitFile
199
- ? buildGitDiffTree(gitFile.baseContent, file.content, file.tree)
285
+ ? buildGitDiffTree(gitFile.baseContent, file.content)
200
286
  : null;
201
287
  const gitRendered = gitTree
202
- ? await renderMarkdown(file.content, requestedPath, resolver, { errors }, gitTree)
288
+ ? await renderMarkdown(file.content, requestedPath, resolver, {
289
+ errors,
290
+ rewriteMarkdownLink: (url) => rewriteDocumentLink(url, requestedPath),
291
+ }, gitTree)
203
292
  : null;
204
293
  return {
205
294
  path: requestedPath,
206
295
  ...rendered,
207
- gitHtml: gitRendered?.html ?? null,
208
- tableOfContents: buildViewTableOfContents(file.sections, file.tree, {
209
- errors,
210
- gitTree,
211
- }),
296
+ gitTree: gitRendered?.tree ?? null,
297
+ tableOfContents: buildViewTableOfContents(file.sections, file.headingTitles, { errors, gitTree }),
212
298
  graphNodeIds: Object.fromEntries(snapshot.graph.nodes
213
299
  .filter((node) => node.documentPath === requestedPath && node.kind === 'document')
214
300
  .map((node) => {
@@ -223,20 +309,107 @@ export class ViewStore {
223
309
  return [headingId, node.id];
224
310
  })),
225
311
  errors,
226
- backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections, snapshot.references)),
312
+ backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections, snapshot.references, snapshot.external)),
227
313
  frontmatter: {
228
- requireCodeMention: parseFrontmatter(file.content).requireCodeMention === true,
314
+ requireCodeMention: file.frontmatter.requireCodeMention === true,
229
315
  },
230
316
  };
231
317
  }
318
+ async editableDocumentPath(requestedPath) {
319
+ if (!requestedPath ||
320
+ requestedPath.includes('\\') ||
321
+ isAbsolute(requestedPath) ||
322
+ !requestedPath.toLowerCase().endsWith('.md') ||
323
+ !this.snapshotValue.files.has(requestedPath)) {
324
+ throw new ViewDocumentNotFoundError('Markdown document not found');
325
+ }
326
+ let realFile;
327
+ try {
328
+ realFile = await realpath(resolve(this.latDir, requestedPath));
329
+ }
330
+ catch {
331
+ throw new ViewDocumentNotFoundError('Markdown document not found');
332
+ }
333
+ if (!isInside(this.realLatDir, realFile)) {
334
+ throw new ViewDocumentNotFoundError('Markdown document not found');
335
+ }
336
+ return realFile;
337
+ }
338
+ async getDocumentSource(requestedPath) {
339
+ const path = await this.editableDocumentPath(requestedPath);
340
+ return { path: requestedPath, content: await readFile(path, 'utf8') };
341
+ }
342
+ async getDocumentResource(requestedPath) {
343
+ if (!requestedPath ||
344
+ requestedPath.includes('\\') ||
345
+ isAbsolute(requestedPath) ||
346
+ requestedPath.toLowerCase().endsWith('.md')) {
347
+ throw new ViewDocumentNotFoundError('Document resource not found');
348
+ }
349
+ let path;
350
+ try {
351
+ path = await realpath(resolve(this.latDir, requestedPath));
352
+ if (!isInside(this.realLatDir, path) || !(await stat(path)).isFile()) {
353
+ throw new ViewDocumentNotFoundError('Document resource not found');
354
+ }
355
+ return await readFile(path);
356
+ }
357
+ catch (error) {
358
+ if (error instanceof ViewDocumentNotFoundError)
359
+ throw error;
360
+ throw new ViewDocumentNotFoundError('Document resource not found');
361
+ }
362
+ }
363
+ editDocument(requestedPath, baseContent, editedContent) {
364
+ const operation = this.editTail.then(async () => {
365
+ const path = await this.editableDocumentPath(requestedPath);
366
+ for (let attempt = 0; attempt < DOCUMENT_EDIT_WRITE_ATTEMPTS; attempt++) {
367
+ const currentContent = await readFile(path, 'utf8');
368
+ const edit = applyDocumentEdit(baseContent, editedContent, currentContent);
369
+ if (edit.content === currentContent) {
370
+ return { path: requestedPath, ...edit };
371
+ }
372
+ const currentStat = await stat(path);
373
+ const temporaryPath = resolve(this.latDir, `${DOCUMENT_EDIT_TEMP_PREFIX}${process.pid}-${randomUUID()}.tmp`);
374
+ try {
375
+ await writeFile(temporaryPath, edit.content, {
376
+ encoding: 'utf8',
377
+ flag: 'wx',
378
+ mode: currentStat.mode,
379
+ });
380
+ if ((await readFile(path, 'utf8')) !== currentContent)
381
+ continue;
382
+ await rename(temporaryPath, path);
383
+ }
384
+ finally {
385
+ await rm(temporaryPath, { force: true });
386
+ }
387
+ await this.refresh([resolve(this.latDir, requestedPath)]);
388
+ return { path: requestedPath, ...edit };
389
+ }
390
+ throw new ViewDocumentConflictError('Could not save because this file kept changing. Your edits are still in the editor.');
391
+ });
392
+ this.editTail = operation.then(() => undefined, () => undefined);
393
+ return operation;
394
+ }
232
395
  getSource(requestedPath, requestedSymbol = '', origin, requestedLine = 0) {
233
396
  const snapshot = this.snapshotValue;
234
397
  return getViewSource(this.latDir, this.projectRoot, requestedPath, requestedSymbol, origin, requestedLine, snapshot.allSections, snapshot.references);
235
398
  }
399
+ getExternal(target) {
400
+ const snapshot = this.snapshotValue;
401
+ return getViewExternal(this.latDir, this.projectRoot, target, snapshot.external, snapshot.allSections, snapshot.references);
402
+ }
236
403
  refresh(paths) {
237
404
  if (this.closed)
238
405
  return Promise.resolve();
239
- const normalized = paths.map((path) => path ? projectPath(this.projectRoot, path) : '');
406
+ const latPath = projectPath(this.projectRoot, this.latDir);
407
+ const normalized = paths
408
+ .map((path) => (path ? projectPath(this.projectRoot, path) : ''))
409
+ .filter((path) => !isInternalLatCachePath(path, latPath));
410
+ if (paths.length > 0 && normalized.length === 0) {
411
+ return Promise.resolve();
412
+ }
240
413
  return this.queueRefresh(normalized, false);
241
414
  }
242
415
  queueRefresh(paths, pollGit) {
@@ -254,13 +427,30 @@ export class ViewStore {
254
427
  this.gitPollTimer = null;
255
428
  this.watcher?.close();
256
429
  this.watcher = null;
430
+ for (const watcher of this.externalWatchers)
431
+ watcher.close();
432
+ this.externalWatchers = [];
433
+ await this.editTail;
257
434
  await this.refreshTail;
258
435
  this.listeners.clear();
259
436
  }
260
437
  scheduleRefresh(path) {
261
438
  if (this.closed)
262
439
  return;
263
- this.pendingPaths.add(path ? projectPath(this.projectRoot, path) : '');
440
+ if (path !== EXTERNAL_REFRESH_PATH &&
441
+ basename(path).startsWith(DOCUMENT_EDIT_TEMP_PREFIX) &&
442
+ path.endsWith('.tmp')) {
443
+ return;
444
+ }
445
+ const normalizedPath = path === EXTERNAL_REFRESH_PATH
446
+ ? path
447
+ : path
448
+ ? projectPath(this.projectRoot, path)
449
+ : '';
450
+ const latPath = projectPath(this.projectRoot, this.latDir);
451
+ if (isInternalLatCachePath(normalizedPath, latPath))
452
+ return;
453
+ this.pendingPaths.add(normalizedPath);
264
454
  if (this.debounceTimer)
265
455
  clearTimeout(this.debounceTimer);
266
456
  this.debounceTimer = setTimeout(() => {
@@ -300,12 +490,13 @@ export class ViewStore {
300
490
  }
301
491
  if (!isInside(this.realLatDir, realFile))
302
492
  return null;
303
- const content = await readFile(realFile, 'utf8');
304
- return parseViewMarkdownFile(absolutePath, content, this.latDir, this.projectRoot);
493
+ return analyzeMarkdownPath(absolutePath, this.latDir, this.projectRoot);
305
494
  }
306
495
  async applyRefresh(paths, pollGit) {
307
496
  const fullRefresh = paths.includes('');
308
497
  const latPath = projectPath(this.projectRoot, this.latDir);
498
+ const externalChanged = paths.includes(EXTERNAL_REFRESH_PATH) ||
499
+ paths.includes(`${latPath}/config.local.yaml`);
309
500
  const touchesMarkdown = fullRefresh ||
310
501
  paths.some((path) => path === latPath || path.startsWith(`${latPath}/`));
311
502
  let markdownFiles = new Map(this.snapshotValue.files);
@@ -419,10 +610,12 @@ export class ViewStore {
419
610
  if (!markdownChanged &&
420
611
  !codeChanged &&
421
612
  !gitChanged &&
422
- !linkedResourceChanged)
613
+ !linkedResourceChanged &&
614
+ !externalChanged)
423
615
  return;
424
616
  this.codeFiles = codeFiles;
425
- this.snapshotValue = await buildSnapshot(this.latDir, this.projectRoot, markdownFiles, codeFiles, git, this.snapshotValue.generation + 1, this.snapshotValue.markdownGeneration + (markdownChanged ? 1 : 0));
617
+ this.snapshotValue = await buildSnapshot(this.latDir, this.projectRoot, markdownFiles, codeFiles, git, this.snapshotValue.generation + 1, this.snapshotValue.markdownGeneration + (markdownChanged ? 1 : 0), this.options);
618
+ this.refreshExternalWatchers();
426
619
  const change = {
427
620
  generation: this.snapshotValue.generation,
428
621
  markdownGeneration: this.snapshotValue.markdownGeneration,
@@ -449,8 +642,7 @@ export async function createViewStore(latDir, projectRoot, options = {}) {
449
642
  const realFile = await realpath(absolutePath);
450
643
  if (!isInside(realLatDir, realFile))
451
644
  return;
452
- const content = await readFile(realFile, 'utf8');
453
- const parsed = parseViewMarkdownFile(absolutePath, content, latDir, projectRoot);
645
+ const parsed = await analyzeMarkdownPath(absolutePath, latDir, projectRoot);
454
646
  markdownFiles.set(parsed.path, parsed);
455
647
  }));
456
648
  if (markdownFiles.size === 0) {
@@ -465,7 +657,7 @@ export async function createViewStore(latDir, projectRoot, options = {}) {
465
657
  process.stderr.write(`lat ui git: ${error.message}\n`);
466
658
  }
467
659
  }
468
- const store = new ViewStore(latDir, projectRoot, realLatDir, await buildSnapshot(latDir, projectRoot, markdownFiles, codeState.files, git, 0, 0), codeState.files, codeState.scope, gitRepository, options);
660
+ const store = new ViewStore(latDir, projectRoot, realLatDir, await buildSnapshot(latDir, projectRoot, markdownFiles, codeState.files, git, 0, 0, options), codeState.files, codeState.scope, gitRepository, options);
469
661
  store.startWatching();
470
662
  return store;
471
663
  }
@@ -1,9 +1,9 @@
1
1
  import type { Root } from 'mdast';
2
- import { type Section } from '../lattice.js';
2
+ import { type Section } from '../lattice-model.js';
3
3
  import type { ViewDocumentError, ViewDocumentTocItem } from './protocol.js';
4
4
  export type ViewTableOfContentsOptions = {
5
5
  errors?: readonly ViewDocumentError[];
6
6
  gitTree?: Root | null;
7
7
  };
8
8
  /** Project rendered headings into the document's local navigation. */
9
- export declare function buildViewTableOfContents(sections: Section[], tree: Root, options?: ViewTableOfContentsOptions): ViewDocumentTocItem[];
9
+ export declare function buildViewTableOfContents(sections: Section[], titles: readonly string[], options?: ViewTableOfContentsOptions): ViewDocumentTocItem[];
@@ -1,20 +1,4 @@
1
- import { visit } from 'unist-util-visit';
2
- import { flattenSections } from '../lattice.js';
3
- function headingText(node) {
4
- if (!node || typeof node !== 'object')
5
- return '';
6
- const value = node;
7
- if (value.type === 'text' || value.type === 'inlineCode') {
8
- return value.value ?? '';
9
- }
10
- if (value.type === 'image' || value.type === 'imageReference') {
11
- return value.alt ?? '';
12
- }
13
- if (value.type === 'wikiLink') {
14
- return value.data?.alias ?? value.value ?? '';
15
- }
16
- return value.children?.map(headingText).join('') ?? '';
17
- }
1
+ import { flattenSections } from '../lattice-model.js';
18
2
  function sectionAtLine(sections, line) {
19
3
  let owner = null;
20
4
  for (const section of sections) {
@@ -76,12 +60,8 @@ function gitChangedSections(sections, tree) {
76
60
  return changed;
77
61
  }
78
62
  /** Project rendered headings into the document's local navigation. */
79
- export function buildViewTableOfContents(sections, tree, options = {}) {
63
+ export function buildViewTableOfContents(sections, titles, options = {}) {
80
64
  const flatSections = flattenSections(sections);
81
- const titles = [];
82
- visit(tree, 'heading', (node) => {
83
- titles.push(headingText(node));
84
- });
85
65
  const errorCounts = new Map();
86
66
  for (const error of options.errors ?? []) {
87
67
  const owner = sectionAtLine(flatSections, error.line);
@@ -1,6 +1,9 @@
1
+ /** Dependency trees are never project source, even without ignore metadata. */
2
+ export declare const ALWAYS_IGNORED_DIRECTORIES: readonly ["node_modules"];
1
3
  /**
2
- * Walk a directory tree respecting .gitignore rules. Returns relative paths
3
- * of all non-ignored files, excluding .git/ and dotfiles (e.g. .gitignore).
4
+ * Walk a directory tree respecting nested .gitignore rules. Directories are
5
+ * consumed through a bounded queue, and each completed job submits its visible
6
+ * children back to the pool. Returns sorted project-relative POSIX paths.
4
7
  *
5
8
  * This is the single entry point for all directory walking in lat.md — both
6
9
  * code-ref scanning and lat.md/ index validation use it so .gitignore rules
@@ -15,4 +18,3 @@ export declare function walkEntries(dir: string): Promise<string[]>;
15
18
  * a single invariant — stored paths are always POSIX — so downstream lookups
16
19
  * (e.g. `buildFileIndex`, ref resolution) work identically on every platform.
17
20
  */
18
- export declare function toPosix(p: string): string;
package/dist/src/walk.js CHANGED
@@ -1,18 +1,115 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { availableParallelism } from 'node:os';
3
+ import { join } from 'node:path';
1
4
  // @ts-expect-error -- no type declarations
2
5
  import walk from 'ignore-walk';
6
+ const IgnoreWalker = walk
7
+ .Walker;
8
+ /** Dependency trees are never project source, even without ignore metadata. */
9
+ export const ALWAYS_IGNORED_DIRECTORIES = ['node_modules'];
10
+ class IgnoreContext extends IgnoreWalker {
11
+ filterEntry(entry, partial, entryBasename) {
12
+ const candidate = entryBasename ?? entry;
13
+ if (candidate
14
+ .split(/[\\/]/)
15
+ .some((part) => ALWAYS_IGNORED_DIRECTORIES.includes(part) ||
16
+ (part.startsWith('.') && part !== '.' && part !== '..'))) {
17
+ return false;
18
+ }
19
+ return super.filterEntry(entry, partial, entryBasename);
20
+ }
21
+ }
22
+ async function readDirectory(job) {
23
+ const entries = await readdir(job.path, { withFileTypes: true });
24
+ if (entries.some((entry) => entry.name === '.gitignore')) {
25
+ const rules = await readFile(join(job.path, '.gitignore'), 'utf8');
26
+ job.ignoreContext.onReadIgnoreFile('.gitignore', rules, () => { });
27
+ }
28
+ const directories = [];
29
+ const files = [];
30
+ for (const entry of entries) {
31
+ if (entry.isSymbolicLink())
32
+ continue;
33
+ if (entry.isDirectory()) {
34
+ const passDirectory = job.ignoreContext.filterEntry(entry.name, true);
35
+ if (!passDirectory)
36
+ continue;
37
+ const passFile = job.ignoreContext.filterEntry(entry.name);
38
+ const path = join(job.path, entry.name);
39
+ directories.push({
40
+ path,
41
+ relativePath: job.relativePath
42
+ ? `${job.relativePath}/${entry.name}`
43
+ : entry.name,
44
+ ignoreContext: new IgnoreContext({
45
+ path,
46
+ parent: job.ignoreContext,
47
+ ignoreFiles: ['.gitignore'],
48
+ exact: passFile || job.ignoreContext.filterEntry(`${entry.name}/`),
49
+ }),
50
+ });
51
+ }
52
+ else if (job.ignoreContext.filterEntry(entry.name)) {
53
+ files.push(job.relativePath ? `${job.relativePath}/${entry.name}` : entry.name);
54
+ }
55
+ }
56
+ return { directories, files };
57
+ }
58
+ async function walkWithDirectoryPool(root) {
59
+ const queue = [root];
60
+ const files = [];
61
+ const workerLimit = availableParallelism();
62
+ let nextJob = 0;
63
+ let activeWorkers = 0;
64
+ return new Promise((resolve, reject) => {
65
+ let settled = false;
66
+ const schedule = () => {
67
+ if (settled)
68
+ return;
69
+ while (activeWorkers < workerLimit && nextJob < queue.length) {
70
+ const job = queue[nextJob++];
71
+ activeWorkers++;
72
+ void readDirectory(job).then((result) => {
73
+ files.push(...result.files);
74
+ queue.push(...result.directories);
75
+ activeWorkers--;
76
+ if (activeWorkers === 0 && nextJob === queue.length) {
77
+ settled = true;
78
+ files.sort((a, b) => a.localeCompare(b, 'en'));
79
+ resolve(files);
80
+ }
81
+ else {
82
+ schedule();
83
+ }
84
+ }, (error) => {
85
+ if (settled)
86
+ return;
87
+ settled = true;
88
+ reject(error);
89
+ });
90
+ }
91
+ };
92
+ schedule();
93
+ });
94
+ }
3
95
  /**
4
- * Walk a directory tree respecting .gitignore rules. Returns relative paths
5
- * of all non-ignored files, excluding .git/ and dotfiles (e.g. .gitignore).
96
+ * Walk a directory tree respecting nested .gitignore rules. Directories are
97
+ * consumed through a bounded queue, and each completed job submits its visible
98
+ * children back to the pool. Returns sorted project-relative POSIX paths.
6
99
  *
7
100
  * This is the single entry point for all directory walking in lat.md — both
8
101
  * code-ref scanning and lat.md/ index validation use it so .gitignore rules
9
102
  * are consistently honored.
10
103
  */
11
104
  export function walkEntries(dir) {
12
- return walk({
105
+ return walkWithDirectoryPool({
13
106
  path: dir,
14
- ignoreFiles: ['.gitignore'],
15
- }).then((entries) => entries.filter((e) => !e.startsWith('.git/') && !e.startsWith('.')));
107
+ relativePath: '',
108
+ ignoreContext: new IgnoreContext({
109
+ path: dir,
110
+ ignoreFiles: ['.gitignore'],
111
+ }),
112
+ });
16
113
  }
17
114
  /**
18
115
  * Normalize a filesystem path to forward-slash (POSIX) form. Node's
@@ -22,6 +119,3 @@ export function walkEntries(dir) {
22
119
  * a single invariant — stored paths are always POSIX — so downstream lookups
23
120
  * (e.g. `buildFileIndex`, ref resolution) work identically on every platform.
24
121
  */
25
- export function toPosix(p) {
26
- return p.replace(/\\/g, '/');
27
- }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { runLatServer } from './index.js';
5
+ const entry = process.argv[2];
6
+ if (!entry)
7
+ throw new Error('Usage: lat-ui-server <app-module>');
8
+ const module = (await import(pathToFileURL(resolve(entry)).href));
9
+ if (typeof module.default !== 'function') {
10
+ throw new Error(`${entry} must default-export an Express app`);
11
+ }
12
+ await runLatServer(module.default, { close: module.close });