@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,65 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { basename, dirname, extname, join, relative } from 'node:path';
4
+ import { toPosix } from './path.js';
5
+ /** Version of persistent parser outputs and their shared on-disk contract. */
6
+ export const PARSER_CACHE_VERSION = 1;
7
+ /** Hash complete parser input bytes for cache invalidation. */
8
+ export function hashParserContent(content) {
9
+ return createHash('sha1').update(content).digest('hex');
10
+ }
11
+ /** Normalize an on-disk file into its project-relative parser identity. */
12
+ export function parserCacheIdentity(absolutePath, projectRoot) {
13
+ return toPosix(relative(projectRoot, absolutePath)).normalize('NFC');
14
+ }
15
+ function readablePath(identity) {
16
+ const normalized = identity
17
+ .replace(/[^a-zA-Z0-9_-]+/g, '_')
18
+ .replace(/^_+|_+$/g, '')
19
+ .toLowerCase();
20
+ return (normalized || 'parsed').slice(-120);
21
+ }
22
+ function cacheShard(identity) {
23
+ const shortName = basename(identity);
24
+ const stem = shortName.slice(0, shortName.length - extname(shortName).length);
25
+ const firstTwo = [...stem.toLowerCase()].slice(0, 2).join('');
26
+ return firstTwo.replace(/[^a-z0-9]/g, '_') || '_';
27
+ }
28
+ /** Return the collision-safe, short-name-sharded cache path for an identity. */
29
+ export function parsedCachePath(latDir, identity) {
30
+ const normalizedIdentity = toPosix(identity).normalize('NFC');
31
+ const digest = hashParserContent(normalizedIdentity);
32
+ return join(latDir, '.cache', 'parsed', cacheShard(normalizedIdentity), `${digest}_${readablePath(normalizedIdentity)}`);
33
+ }
34
+ /** Read and decode a cache entry, treating every malformed state as a miss. */
35
+ export async function readParsedCache(path) {
36
+ try {
37
+ const serialized = await readFile(path, 'utf8');
38
+ const newline = serialized.indexOf('\n');
39
+ if (newline < 0)
40
+ return null;
41
+ const match = /^v(\d+):([a-f0-9]{40})$/.exec(serialized.slice(0, newline).trim());
42
+ if (!match)
43
+ return null;
44
+ return {
45
+ version: Number(match[1]),
46
+ contentHash: match[2],
47
+ value: JSON.parse(serialized.slice(newline + 1)),
48
+ };
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ /** Atomically publish one disposable parser cache entry. */
55
+ export async function writeParsedCache(path, contentHash, value) {
56
+ await mkdir(dirname(path), { recursive: true });
57
+ const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
58
+ try {
59
+ await writeFile(temp, `v${PARSER_CACHE_VERSION}:${contentHash}\n${JSON.stringify(value)}\n`);
60
+ await rename(temp, path);
61
+ }
62
+ finally {
63
+ await rm(temp, { force: true }).catch(() => { });
64
+ }
65
+ }
@@ -0,0 +1,8 @@
1
+ /** One observable decision to load or skip a lazily imported parser module. */
2
+ export type ParserImportEvent = {
3
+ parser: 'AsciiDoc parser' | 'Markdown analyzer' | 'reStructuredText parser';
4
+ imported: boolean;
5
+ durationMs: number;
6
+ detail?: string;
7
+ };
8
+ export type ParserImportObserver = (event: ParserImportEvent) => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -2,17 +2,92 @@ import { unified } from 'unified';
2
2
  import remarkParse from 'remark-parse';
3
3
  import remarkStringify from 'remark-stringify';
4
4
  import remarkFrontmatter from 'remark-frontmatter';
5
+ import remarkMath from 'remark-math';
6
+ import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal';
7
+ import { gfmFootnoteFromMarkdown, gfmFootnoteToMarkdown, } from 'mdast-util-gfm-footnote';
8
+ import { gfmStrikethroughFromMarkdown, gfmStrikethroughToMarkdown, } from 'mdast-util-gfm-strikethrough';
9
+ import { gfmTableFromMarkdown, gfmTableToMarkdown } from 'mdast-util-gfm-table';
10
+ import { gfmTaskListItemFromMarkdown, gfmTaskListItemToMarkdown, } from 'mdast-util-gfm-task-list-item';
11
+ import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal';
12
+ import { gfmFootnote } from 'micromark-extension-gfm-footnote';
13
+ import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough';
14
+ import { gfmTable } from 'micromark-extension-gfm-table';
15
+ import { gfmTaskListItem } from 'micromark-extension-gfm-task-list-item';
16
+ import { visit } from 'unist-util-visit';
17
+ import { alertMarkerToMarkdown, markAlertMarkers, } from './extensions/alert-marker.js';
5
18
  import { wikiLinkSyntax, wikiLinkFromMarkdown, wikiLinkToMarkdown, } from './extensions/wiki-link/index.js';
6
19
  const processor = unified()
7
20
  .use(remarkParse)
8
21
  .use(remarkFrontmatter)
22
+ .use(remarkMath)
9
23
  .use(remarkStringify)
10
- .data('micromarkExtensions', [wikiLinkSyntax()])
11
- .data('fromMarkdownExtensions', [wikiLinkFromMarkdown()])
12
- .data('toMarkdownExtensions', [wikiLinkToMarkdown()]);
24
+ .data('micromarkExtensions', [
25
+ gfmAutolinkLiteral(),
26
+ gfmFootnote(),
27
+ gfmStrikethrough(),
28
+ gfmTable(),
29
+ gfmTaskListItem(),
30
+ wikiLinkSyntax(),
31
+ ])
32
+ .data('fromMarkdownExtensions', [
33
+ gfmAutolinkLiteralFromMarkdown(),
34
+ gfmFootnoteFromMarkdown(),
35
+ gfmStrikethroughFromMarkdown(),
36
+ gfmTableFromMarkdown(),
37
+ gfmTaskListItemFromMarkdown(),
38
+ wikiLinkFromMarkdown(),
39
+ ])
40
+ .data('toMarkdownExtensions', [
41
+ alertMarkerToMarkdown(),
42
+ gfmFootnoteToMarkdown(),
43
+ gfmStrikethroughToMarkdown(),
44
+ gfmTableToMarkdown(),
45
+ gfmTaskListItemToMarkdown(),
46
+ wikiLinkToMarkdown(),
47
+ ]);
48
+ const BARE_AUTOLINK_DATA_KEY = 'latBareAutolink';
49
+ function bareAutolinkText(node) {
50
+ if (node.children.length !== 1 || node.children[0].type !== 'text') {
51
+ return null;
52
+ }
53
+ const text = node.children[0].value;
54
+ if (node.url !== text &&
55
+ node.url !== `http://${text}` &&
56
+ node.url !== `mailto:${text}`) {
57
+ return null;
58
+ }
59
+ return text;
60
+ }
13
61
  export function parse(markdown) {
14
- return processor.parse(markdown);
62
+ const tree = processor.parse(markdown);
63
+ markAlertMarkers(tree);
64
+ visit(tree, 'link', (node) => {
65
+ const text = bareAutolinkText(node);
66
+ const sourceLength = node.position?.end.offset !== undefined &&
67
+ node.position.start.offset !== undefined
68
+ ? node.position.end.offset - node.position.start.offset
69
+ : null;
70
+ if (text === null || sourceLength !== text.length)
71
+ return;
72
+ node.data = {
73
+ ...node.data,
74
+ [BARE_AUTOLINK_DATA_KEY]: true,
75
+ };
76
+ });
77
+ return tree;
15
78
  }
16
79
  export function toMarkdown(tree) {
17
- return processor.stringify(tree);
80
+ const serializable = structuredClone(tree);
81
+ visit(serializable, 'link', (node, index, parent) => {
82
+ if (index === undefined ||
83
+ !parent ||
84
+ node.data?.[BARE_AUTOLINK_DATA_KEY] !== true) {
85
+ return;
86
+ }
87
+ const text = bareAutolinkText(node);
88
+ if (text === null)
89
+ return;
90
+ parent.children[index] = { type: 'text', value: text };
91
+ });
92
+ return processor.stringify(serializable);
18
93
  }
@@ -0,0 +1,2 @@
1
+ /** Normalize path separators for stable project-relative identities. */
2
+ export declare function toPosix(path: string): string;
@@ -0,0 +1,4 @@
1
+ /** Normalize path separators for stable project-relative identities. */
2
+ export function toPosix(path) {
3
+ return path.replace(/\\/g, '/');
4
+ }
@@ -0,0 +1,16 @@
1
+ export type Profiler = {
2
+ time<T>(label: string, work: () => Promise<T>, detail?: string): Promise<T>;
3
+ timeSync<T>(label: string, work: () => T, detail?: string): T;
4
+ record(label: string, durationMs: number, detail?: string): void;
5
+ };
6
+ export declare class TimingProfiler implements Profiler {
7
+ private readonly root;
8
+ private readonly activeNode;
9
+ private node;
10
+ time<T>(label: string, work: () => Promise<T>, detail?: string): Promise<T>;
11
+ timeSync<T>(label: string, work: () => T, detail?: string): T;
12
+ record(label: string, durationMs: number, detail?: string): void;
13
+ private recordNode;
14
+ format(totalMs: number): string[];
15
+ private formatNode;
16
+ }
@@ -0,0 +1,81 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { performance } from 'node:perf_hooks';
3
+ export class TimingProfiler {
4
+ root = {
5
+ label: '',
6
+ calls: 0,
7
+ totalMs: 0,
8
+ maxMs: 0,
9
+ children: new Map(),
10
+ };
11
+ activeNode = new AsyncLocalStorage();
12
+ node(label) {
13
+ const parent = this.activeNode.getStore() ?? this.root;
14
+ let node = parent.children.get(label);
15
+ if (!node) {
16
+ node = {
17
+ label,
18
+ calls: 0,
19
+ totalMs: 0,
20
+ maxMs: 0,
21
+ children: new Map(),
22
+ };
23
+ parent.children.set(label, node);
24
+ }
25
+ return node;
26
+ }
27
+ async time(label, work, detail) {
28
+ const node = this.node(label);
29
+ const start = performance.now();
30
+ try {
31
+ return await this.activeNode.run(node, work);
32
+ }
33
+ finally {
34
+ this.recordNode(node, performance.now() - start, detail);
35
+ }
36
+ }
37
+ timeSync(label, work, detail) {
38
+ const node = this.node(label);
39
+ const start = performance.now();
40
+ try {
41
+ return this.activeNode.run(node, work);
42
+ }
43
+ finally {
44
+ this.recordNode(node, performance.now() - start, detail);
45
+ }
46
+ }
47
+ record(label, durationMs, detail) {
48
+ this.recordNode(this.node(label), durationMs, detail);
49
+ }
50
+ recordNode(node, durationMs, detail) {
51
+ node.calls++;
52
+ node.totalMs += durationMs;
53
+ if (durationMs > node.maxMs) {
54
+ node.maxMs = durationMs;
55
+ node.maxDetail = detail;
56
+ }
57
+ }
58
+ format(totalMs) {
59
+ const lines = [`Profile (${totalMs.toFixed(1)}ms total):`];
60
+ for (const node of this.root.children.values()) {
61
+ this.formatNode(node, 1, lines);
62
+ }
63
+ return lines;
64
+ }
65
+ formatNode(node, depth, lines) {
66
+ const indent = ' '.repeat(depth);
67
+ let summary = `${indent}${node.label}: ${node.totalMs.toFixed(1)}ms`;
68
+ if (node.calls > 1) {
69
+ summary +=
70
+ ` total across ${node.calls} calls` +
71
+ ` (${(node.totalMs / node.calls).toFixed(1)}ms avg,` +
72
+ ` ${node.maxMs.toFixed(1)}ms max` +
73
+ (node.maxDetail ? `: ${node.maxDetail}` : '') +
74
+ ')';
75
+ }
76
+ lines.push(summary);
77
+ for (const child of node.children.values()) {
78
+ this.formatNode(child, depth + 1, lines);
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,59 @@
1
+ import { type Ref, type Section, type SectionSlugIndex } from './lattice-model.js';
2
+ import type { MarkdownFileAnalysis } from './markdown-analysis.js';
3
+ import { type ScanResult } from './code-refs.js';
4
+ import { type ResolveSourceSymbolOptions } from './source-parser.js';
5
+ import { type ExternalResolver } from './external-sources.js';
6
+ import type { ParserImportObserver } from './parser-import.js';
7
+ export type MarkdownAnalysisExecutor = 'inline' | 'workers' | 'auto';
8
+ export type MarkdownProjectAnalysis = {
9
+ latDir: string;
10
+ projectRoot: string;
11
+ entries: string[];
12
+ markdownFiles: string[];
13
+ files: ReadonlyMap<string, MarkdownFileAnalysis>;
14
+ filesByAbsolutePath: ReadonlyMap<string, MarkdownFileAnalysis>;
15
+ allSections: Section[];
16
+ sections: Section[];
17
+ sectionById: ReadonlyMap<string, Section>;
18
+ sectionIds: ReadonlySet<string>;
19
+ fileIndex: Map<string, string[]>;
20
+ slugIndex: SectionSlugIndex;
21
+ wikiRefs: readonly Ref[];
22
+ outgoingRefsBySection: ReadonlyMap<string, readonly Ref[]>;
23
+ refsByTarget: ReadonlyMap<string, readonly Ref[]>;
24
+ incomingRefsBySection: ReadonlyMap<string, readonly Ref[]>;
25
+ };
26
+ export type AnalyzeMarkdownProjectOptions = {
27
+ executor?: MarkdownAnalysisExecutor;
28
+ maxWorkers?: number;
29
+ cache?: boolean;
30
+ onFileAnalyzed?: (analysis: MarkdownFileAnalysis) => void;
31
+ onParserImport?: ParserImportObserver;
32
+ };
33
+ /** Read and analyze every Markdown file into one immutable command snapshot. */
34
+ export declare function analyzeMarkdownProject(latDir: string, projectRoot: string, options?: AnalyzeMarkdownProjectOptions): Promise<MarkdownProjectAnalysis>;
35
+ /** Lazy request-scoped owner for one immutable project analysis. */
36
+ export declare class MarkdownProjectSession {
37
+ readonly latDir: string;
38
+ readonly projectRoot: string;
39
+ readonly options: AnalyzeMarkdownProjectOptions;
40
+ private analysisPromise?;
41
+ private codeRefsPromise?;
42
+ private externalPromise?;
43
+ private readonly sourceParserRuntime;
44
+ constructor(latDir: string, projectRoot: string, options?: AnalyzeMarkdownProjectOptions);
45
+ analysis(): Promise<MarkdownProjectAnalysis>;
46
+ codeRefs(): Promise<ScanResult>;
47
+ external(): Promise<ExternalResolver>;
48
+ sourceSymbolOptions(): ResolveSourceSymbolOptions;
49
+ }
50
+ type AnalysisContext = {
51
+ latDir: string;
52
+ projectRoot: string;
53
+ analysis?: MarkdownProjectSession;
54
+ };
55
+ /** Reuse one lazy project snapshot throughout a CLI or MCP request. */
56
+ export declare function commandProjectAnalysis(context: AnalysisContext): Promise<MarkdownProjectAnalysis>;
57
+ /** Obtain the complete lazy request session for nested semantic operations. */
58
+ export declare function commandProjectSession(context: AnalysisContext): MarkdownProjectSession;
59
+ export {};
@@ -0,0 +1,233 @@
1
+ import { availableParallelism } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { Worker } from 'node:worker_threads';
4
+ import { buildFileIndex, buildSectionSlugIndex, flattenSections, resolveRef, } from './lattice-model.js';
5
+ import { prepareMarkdownAnalysis, publishMarkdownAnalysis, } from './markdown-analysis-cache.js';
6
+ import { walkEntries } from './walk.js';
7
+ import { scanCodeRefs } from './code-refs.js';
8
+ import { SourceParserRuntime, } from './source-parser.js';
9
+ import { createExternalResolver, } from './external-sources.js';
10
+ import { loadMarkdownAnalyzer } from './markdown-analyzer-loader.js';
11
+ async function analyzeInline(files, latDir, projectRoot, onParserImport) {
12
+ if (files.length === 0)
13
+ return [];
14
+ const analyzeMarkdownFile = await loadMarkdownAnalyzer(onParserImport, 'main thread');
15
+ return Promise.all(files.map((file) => publishMarkdownAnalysis(file, analyzeMarkdownFile(file.absolutePath, file.content, latDir, projectRoot))));
16
+ }
17
+ function runWorkerTask(worker, task) {
18
+ return new Promise((resolve, reject) => {
19
+ const onMessage = (response) => {
20
+ cleanup();
21
+ if ('error' in response) {
22
+ reject(new Error(`Failed to analyze ${task.absolutePath}: ${response.error}`));
23
+ }
24
+ else {
25
+ resolve(response);
26
+ }
27
+ };
28
+ const onError = (error) => {
29
+ cleanup();
30
+ reject(error);
31
+ };
32
+ const onExit = (code) => {
33
+ cleanup();
34
+ reject(new Error(`Markdown analysis worker exited with code ${code} while reading ${task.absolutePath}`));
35
+ };
36
+ const cleanup = () => {
37
+ worker.off('message', onMessage);
38
+ worker.off('error', onError);
39
+ worker.off('exit', onExit);
40
+ };
41
+ worker.once('message', onMessage);
42
+ worker.once('error', onError);
43
+ worker.once('exit', onExit);
44
+ worker.postMessage(task);
45
+ });
46
+ }
47
+ async function analyzeWithWorkers(files, latDir, projectRoot, maxWorkers, onParserImport) {
48
+ const workerCount = Math.max(1, Math.min(files.length, maxWorkers ?? 10, Math.max(1, availableParallelism() - 1)));
49
+ const workers = Array.from({ length: workerCount }, () => {
50
+ const sourceRuntime = import.meta.url.endsWith('.ts');
51
+ return new Worker(new URL(sourceRuntime
52
+ ? './markdown-analysis-worker.ts'
53
+ : './markdown-analysis-worker.js', import.meta.url), {
54
+ execArgv: sourceRuntime
55
+ ? ['--import', 'tsx']
56
+ : process.execArgv.filter((argument) => !argument.startsWith('--input-type')),
57
+ });
58
+ });
59
+ const analyses = new Array(files.length);
60
+ let next = 0;
61
+ try {
62
+ await Promise.all(workers.map(async (worker, workerIndex) => {
63
+ while (true) {
64
+ const id = next++;
65
+ if (id >= files.length)
66
+ return;
67
+ const file = files[id];
68
+ const response = await runWorkerTask(worker, {
69
+ id,
70
+ absolutePath: file.absolutePath,
71
+ content: file.content,
72
+ latDir,
73
+ projectRoot,
74
+ });
75
+ analyses[id] = response.analysis;
76
+ if (response.importMs !== undefined) {
77
+ onParserImport?.({
78
+ parser: 'Markdown analyzer',
79
+ imported: true,
80
+ durationMs: response.importMs,
81
+ detail: `worker ${workerIndex + 1}`,
82
+ });
83
+ }
84
+ }
85
+ }));
86
+ return Promise.all(analyses.map((analysis, index) => publishMarkdownAnalysis(files[index], analysis)));
87
+ }
88
+ finally {
89
+ await Promise.all(workers.map((worker) => worker.terminate()));
90
+ }
91
+ }
92
+ /** Read and analyze every Markdown file into one immutable command snapshot. */
93
+ export async function analyzeMarkdownProject(latDir, projectRoot, options = {}) {
94
+ const entries = await walkEntries(latDir);
95
+ const markdownFiles = entries
96
+ .filter((entry) => entry.endsWith('.md'))
97
+ .sort()
98
+ .map((entry) => join(latDir, entry));
99
+ const prepared = await Promise.all(markdownFiles.map((absolutePath) => prepareMarkdownAnalysis(absolutePath, latDir, projectRoot, options.cache !== false)));
100
+ const analyses = new Array(markdownFiles.length);
101
+ const misses = [];
102
+ const missIndexes = [];
103
+ for (const [index, file] of prepared.entries()) {
104
+ if (file.analysis)
105
+ analyses[index] = file.analysis;
106
+ else {
107
+ misses.push(file);
108
+ missIndexes.push(index);
109
+ }
110
+ }
111
+ if (misses.length === 0) {
112
+ options.onParserImport?.({
113
+ parser: 'Markdown analyzer',
114
+ imported: false,
115
+ durationMs: 0,
116
+ detail: prepared.length === 0
117
+ ? 'no Markdown files'
118
+ : `all ${prepared.length} Markdown files cached`,
119
+ });
120
+ }
121
+ const executor = options.executor === 'auto' || options.executor === undefined
122
+ ? misses.length >= 8
123
+ ? 'workers'
124
+ : 'inline'
125
+ : options.executor;
126
+ const parsed = executor === 'workers' && misses.length > 1
127
+ ? await analyzeWithWorkers(misses, latDir, projectRoot, options.maxWorkers, options.onParserImport)
128
+ : await analyzeInline(misses, latDir, projectRoot, options.onParserImport);
129
+ for (const [index, analysis] of parsed.entries()) {
130
+ analyses[missIndexes[index]] = analysis;
131
+ }
132
+ const files = new Map();
133
+ const filesByAbsolutePath = new Map();
134
+ for (const analysis of analyses) {
135
+ files.set(analysis.path, analysis);
136
+ filesByAbsolutePath.set(analysis.absolutePath, analysis);
137
+ options.onFileAnalyzed?.(analysis);
138
+ }
139
+ const allSections = analyses.flatMap((analysis) => analysis.sections);
140
+ const sections = flattenSections(allSections);
141
+ const sectionById = new Map(sections.map((section) => [section.id.toLowerCase(), section]));
142
+ const sectionIds = new Set(sectionById.keys());
143
+ const fileIndex = buildFileIndex(allSections);
144
+ const slugIndex = buildSectionSlugIndex(allSections);
145
+ const wikiRefs = analyses.flatMap((analysis) => analysis.wikiRefs);
146
+ const outgoingRefsBySection = new Map();
147
+ const refsByTarget = new Map();
148
+ const incomingRefsBySection = new Map();
149
+ const append = (index, key, ref) => {
150
+ const refs = index.get(key);
151
+ if (refs)
152
+ refs.push(ref);
153
+ else
154
+ index.set(key, [ref]);
155
+ };
156
+ for (const ref of wikiRefs) {
157
+ const from = ref.fromSection.toLowerCase();
158
+ const target = ref.target.toLowerCase();
159
+ append(outgoingRefsBySection, from, ref);
160
+ append(refsByTarget, target, ref);
161
+ const resolved = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
162
+ if (!resolved.ambiguous) {
163
+ const resolvedId = resolved.resolved.toLowerCase();
164
+ if (sectionIds.has(resolvedId)) {
165
+ append(incomingRefsBySection, resolvedId, ref);
166
+ }
167
+ }
168
+ }
169
+ return {
170
+ latDir,
171
+ projectRoot,
172
+ entries,
173
+ markdownFiles,
174
+ files,
175
+ filesByAbsolutePath,
176
+ allSections,
177
+ sections,
178
+ sectionById,
179
+ sectionIds,
180
+ fileIndex,
181
+ slugIndex,
182
+ wikiRefs,
183
+ outgoingRefsBySection,
184
+ refsByTarget,
185
+ incomingRefsBySection,
186
+ };
187
+ }
188
+ /** Lazy request-scoped owner for one immutable project analysis. */
189
+ export class MarkdownProjectSession {
190
+ latDir;
191
+ projectRoot;
192
+ options;
193
+ analysisPromise;
194
+ codeRefsPromise;
195
+ externalPromise;
196
+ sourceParserRuntime = new SourceParserRuntime();
197
+ constructor(latDir, projectRoot, options = {}) {
198
+ this.latDir = latDir;
199
+ this.projectRoot = projectRoot;
200
+ this.options = options;
201
+ }
202
+ analysis() {
203
+ this.analysisPromise ??= analyzeMarkdownProject(this.latDir, this.projectRoot, this.options);
204
+ return this.analysisPromise;
205
+ }
206
+ codeRefs() {
207
+ this.codeRefsPromise ??= scanCodeRefs(this.projectRoot);
208
+ return this.codeRefsPromise;
209
+ }
210
+ external() {
211
+ this.externalPromise ??= (async () => {
212
+ const resolver = await createExternalResolver(this.latDir, this.projectRoot);
213
+ await resolver.reconcile();
214
+ return resolver;
215
+ })();
216
+ return this.externalPromise;
217
+ }
218
+ sourceSymbolOptions() {
219
+ return {
220
+ latDir: this.latDir,
221
+ runtime: this.sourceParserRuntime,
222
+ };
223
+ }
224
+ }
225
+ /** Reuse one lazy project snapshot throughout a CLI or MCP request. */
226
+ export function commandProjectAnalysis(context) {
227
+ return commandProjectSession(context).analysis();
228
+ }
229
+ /** Obtain the complete lazy request session for nested semantic operations. */
230
+ export function commandProjectSession(context) {
231
+ context.analysis ??= new MarkdownProjectSession(context.latDir, context.projectRoot);
232
+ return context.analysis;
233
+ }
@@ -0,0 +1,6 @@
1
+ /** Find the nearest ancestor containing the project's lat.md directory. */
2
+ export declare function findLatticeDir(from?: string): string | null;
3
+ /** Find the root of the nearest project containing a lat.md directory. */
4
+ export declare function findProjectRoot(from?: string): string | null;
5
+ /** List project Markdown files without loading the Markdown parser. */
6
+ export declare function listLatticeFiles(latticeDir: string): Promise<string[]>;
@@ -0,0 +1,30 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ /** Find the nearest ancestor containing the project's lat.md directory. */
4
+ export function findLatticeDir(from) {
5
+ let dir = resolve(from ?? process.cwd());
6
+ while (true) {
7
+ const candidate = join(dir, 'lat.md');
8
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
9
+ return candidate;
10
+ }
11
+ const parent = dirname(dir);
12
+ if (parent === dir)
13
+ return null;
14
+ dir = parent;
15
+ }
16
+ }
17
+ /** Find the root of the nearest project containing a lat.md directory. */
18
+ export function findProjectRoot(from) {
19
+ const latDir = findLatticeDir(from);
20
+ return latDir ? dirname(latDir) : null;
21
+ }
22
+ /** List project Markdown files without loading the Markdown parser. */
23
+ export async function listLatticeFiles(latticeDir) {
24
+ const { walkEntries } = await import('./walk.js');
25
+ const entries = await walkEntries(latticeDir);
26
+ return entries
27
+ .filter((entry) => entry.endsWith('.md'))
28
+ .sort()
29
+ .map((entry) => join(latticeDir, entry));
30
+ }
@@ -0,0 +1,11 @@
1
+ export type RepositoryPathKind = 'directory' | 'file' | 'other';
2
+ export type RepositoryPathInspection = {
3
+ kind: RepositoryPathKind;
4
+ realPath: string;
5
+ } | {
6
+ kind: 'missing' | 'outside';
7
+ };
8
+ /** Normalize a portable project-root-relative path without allowing escapes. */
9
+ export declare function normalizeRepositoryPath(authoredPath: string): string | null;
10
+ /** Inspect an existing path after resolving symlinks beneath the project root. */
11
+ export declare function inspectRepositoryPath(projectRoot: string, path: string): Promise<RepositoryPathInspection>;
@@ -0,0 +1,52 @@
1
+ import { realpath, stat } from 'node:fs/promises';
2
+ import { isAbsolute, posix, relative, resolve, sep } from 'node:path';
3
+ import { toPosix } from './path.js';
4
+ /** Normalize a portable project-root-relative path without allowing escapes. */
5
+ export function normalizeRepositoryPath(authoredPath) {
6
+ if (!authoredPath || authoredPath.includes('\0'))
7
+ return null;
8
+ const portable = toPosix(authoredPath);
9
+ if (posix.isAbsolute(portable) || /^[a-z]:\//i.test(portable))
10
+ return null;
11
+ const normalized = posix.normalize(portable).replace(/^\.\//, '');
12
+ if (normalized === '..' ||
13
+ normalized.startsWith('../') ||
14
+ normalized.includes('\0')) {
15
+ return null;
16
+ }
17
+ return normalized;
18
+ }
19
+ function isInside(root, candidate) {
20
+ const path = relative(root, candidate);
21
+ return (path === '' ||
22
+ (!isAbsolute(path) && path !== '..' && !path.startsWith(`..${sep}`)));
23
+ }
24
+ /** Inspect an existing path after resolving symlinks beneath the project root. */
25
+ export async function inspectRepositoryPath(projectRoot, path) {
26
+ const candidate = resolve(projectRoot, ...path.split('/'));
27
+ let realRoot;
28
+ let realPath;
29
+ try {
30
+ [realRoot, realPath] = await Promise.all([
31
+ realpath(projectRoot),
32
+ realpath(candidate),
33
+ ]);
34
+ }
35
+ catch {
36
+ return { kind: 'missing' };
37
+ }
38
+ if (!isInside(realRoot, realPath))
39
+ return { kind: 'outside' };
40
+ try {
41
+ const info = await stat(realPath);
42
+ const kind = info.isFile()
43
+ ? 'file'
44
+ : info.isDirectory()
45
+ ? 'directory'
46
+ : 'other';
47
+ return { kind, realPath };
48
+ }
49
+ catch {
50
+ return { kind: 'missing' };
51
+ }
52
+ }