@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
@@ -1,25 +1,15 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { dirname, join, basename, relative, resolve } from 'node:path';
3
- import { existsSync, statSync } from 'node:fs';
2
+ import { dirname, basename, relative } from 'node:path';
4
3
  import GithubSlugger from 'github-slugger';
5
4
  import { parse as parseYaml } from 'yaml';
6
5
  import { parse } from './parser.js';
7
- import { toPosix, walkEntries } from './walk.js';
6
+ import { toPosix } from './path.js';
8
7
  import { visit } from 'unist-util-visit';
9
- /**
10
- * Every field this build reads out of the `lat:` mapping. Kept in one place so
11
- * a field added later is covered by the misplacement check without a second
12
- * edit — the trap this list guards against is silent, so it must not depend on
13
- * whoever adds the next field remembering to register it twice.
14
- */
15
- export const LAT_FIELDS = [
16
- 'require-code-mention',
17
- 'mode',
18
- 'require-mode',
19
- 'status',
20
- 'reviewed-hash',
21
- 'tags',
22
- ];
8
+ import { listLatticeFiles } from './project-discovery.js';
9
+ import { LAT_FIELDS, } from './fork/frontmatter-fields.js';
10
+ export { buildFileIndex, buildSectionSlugIndex, findSections, flattenSections, resolveRef, } from './lattice-model.js';
11
+ export { findLatticeDir, findProjectRoot, listLatticeFiles, } from './project-discovery.js';
12
+ export { LAT_FIELDS } from './fork/frontmatter-fields.js';
23
13
  export function parseFrontmatter(content) {
24
14
  const match = content.match(/^---\n([\s\S]*?)\n---/);
25
15
  if (!match)
@@ -66,30 +56,6 @@ export function parseFrontmatter(content) {
66
56
  result.problems = problems;
67
57
  return result;
68
58
  }
69
- export function findLatticeDir(from) {
70
- let dir = resolve(from ?? process.cwd());
71
- while (true) {
72
- const candidate = join(dir, 'lat.md');
73
- if (existsSync(candidate) && statSync(candidate).isDirectory()) {
74
- return candidate;
75
- }
76
- const parent = dirname(dir);
77
- if (parent === dir)
78
- return null;
79
- dir = parent;
80
- }
81
- }
82
- export function findProjectRoot(from) {
83
- const latDir = findLatticeDir(from);
84
- return latDir ? dirname(latDir) : null;
85
- }
86
- export async function listLatticeFiles(latticeDir) {
87
- const entries = await walkEntries(latticeDir);
88
- return entries
89
- .filter((e) => e.endsWith('.md'))
90
- .sort()
91
- .map((e) => join(latticeDir, e));
92
- }
93
59
  function headingText(node) {
94
60
  return node.children
95
61
  .filter((c) => c.type === 'text')
@@ -207,477 +173,23 @@ export function parseSections(filePath, content, projectRoot, tree = parse(conte
207
173
  }
208
174
  return roots;
209
175
  }
210
- export async function loadAllSections(latticeDir, projectRoot = dirname(latticeDir)) {
211
- const files = await listLatticeFiles(latticeDir);
176
+ export async function loadAllSections(latticeDir, projectRoot = dirname(latticeDir), profile) {
177
+ const files = profile
178
+ ? await profile.time('list Markdown files', () => listLatticeFiles(latticeDir))
179
+ : await listLatticeFiles(latticeDir);
212
180
  const all = [];
213
181
  for (const file of files) {
214
- const content = await readFile(file, 'utf-8');
215
- all.push(...parseSections(file, content, projectRoot));
182
+ const detail = toPosix(relative(latticeDir, file));
183
+ const content = profile
184
+ ? await profile.time('read Markdown file', () => readFile(file, 'utf-8'), detail)
185
+ : await readFile(file, 'utf-8');
186
+ const sections = profile
187
+ ? profile.timeSync('parse Markdown sections', () => parseSections(file, content, projectRoot), detail)
188
+ : parseSections(file, content, projectRoot);
189
+ all.push(...sections);
216
190
  }
217
191
  return all;
218
192
  }
219
- export function flattenSections(sections) {
220
- const result = [];
221
- for (const s of sections) {
222
- result.push(s);
223
- result.push(...flattenSections(s.children));
224
- }
225
- return result;
226
- }
227
- function levenshtein(a, b) {
228
- const m = a.length;
229
- const n = b.length;
230
- const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
231
- for (let i = 0; i <= m; i++)
232
- dp[i][0] = i;
233
- for (let j = 0; j <= n; j++)
234
- dp[0][j] = j;
235
- for (let i = 1; i <= m; i++) {
236
- for (let j = 1; j <= n; j++) {
237
- dp[i][j] =
238
- a[i - 1] === b[j - 1]
239
- ? dp[i - 1][j - 1]
240
- : 1 + Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]);
241
- }
242
- }
243
- return dp[m][n];
244
- }
245
- /**
246
- * Returns the trailing segment(s) of a section id.
247
- * e.g. "markdown#Frontmatter#require-code-mention" → ["Frontmatter#require-code-mention", "require-code-mention"]
248
- * The full id itself is not included (handled by exact match).
249
- */
250
- function tailSegments(id) {
251
- const parts = id.split('#');
252
- const tails = [];
253
- for (let i = 1; i < parts.length; i++) {
254
- tails.push(parts.slice(i).join('#'));
255
- }
256
- return tails;
257
- }
258
- /**
259
- * Build an index mapping path suffixes to their full vault-relative paths.
260
- * Used by resolveRef to allow short references when a suffix is unambiguous.
261
- *
262
- * For a file like `lat.md/guides/setup`, indexes both `guides/setup` and `setup`.
263
- * This ensures backward-compatible short refs after the vault root moved to the
264
- * project root (so section IDs now include the `lat.md/` prefix).
265
- */
266
- export function buildFileIndex(sections) {
267
- const flat = flattenSections(sections);
268
- const index = new Map();
269
- for (const s of flat) {
270
- const parts = s.file.split('/');
271
- // Index all trailing path suffixes (excluding the full path itself,
272
- // which is handled by exact match). Keys are lowercase for
273
- // case-insensitive lookup.
274
- for (let i = 1; i < parts.length; i++) {
275
- const suffix = parts.slice(i).join('/').toLowerCase();
276
- if (!index.has(suffix))
277
- index.set(suffix, new Set());
278
- index.get(suffix).add(s.file);
279
- }
280
- }
281
- const result = new Map();
282
- for (const [stem, paths] of index) {
283
- result.set(stem, [...paths]);
284
- }
285
- return result;
286
- }
287
- /**
288
- * Map GitHub-slugged heading paths back to canonical, literal-heading ids.
289
- * Every path segment accepts either representation so an implicit literal h1
290
- * can still prefix slugged child headings during short-ref resolution.
291
- */
292
- export function buildSectionSlugIndex(sections) {
293
- const aliases = new Map();
294
- const stacks = new Map();
295
- const sluggers = new Map();
296
- for (const section of flattenSections(sections)) {
297
- let slugger = sluggers.get(section.file);
298
- if (!slugger) {
299
- slugger = new GithubSlugger();
300
- sluggers.set(section.file, slugger);
301
- }
302
- // Always advance the fallback slugger so manually constructed Section
303
- // objects without githubSlug still get correct duplicate suffixes.
304
- const fallbackSlug = slugger.slug(section.heading);
305
- const headingSlug = section.githubSlug ?? fallbackSlug;
306
- const stack = stacks.get(section.file) ?? [];
307
- while (stack.length > 0 && stack[stack.length - 1].depth >= section.depth) {
308
- stack.pop();
309
- }
310
- const levels = [
311
- ...stack,
312
- {
313
- depth: section.depth,
314
- literal: section.heading.toLowerCase(),
315
- slug: headingSlug.toLowerCase(),
316
- },
317
- ];
318
- let paths = [''];
319
- for (const level of levels) {
320
- const options = [...new Set([level.literal, level.slug])];
321
- paths = paths.flatMap((path) => options.map((part) => (path ? `${path}#${part}` : part)));
322
- }
323
- for (const path of paths) {
324
- const alias = `${section.file.toLowerCase()}#${path}`;
325
- if (!aliases.has(alias))
326
- aliases.set(alias, section.id);
327
- }
328
- stack.push(levels[levels.length - 1]);
329
- stacks.set(section.file, stack);
330
- }
331
- return aliases;
332
- }
333
- /**
334
- * Normalize separators only in the file portion of a section reference.
335
- * Headings are authored text and may legitimately contain backslashes.
336
- */
337
- function normalizeRefFilePath(target) {
338
- const hashIdx = target.indexOf('#');
339
- if (hashIdx === -1)
340
- return toPosix(target);
341
- return toPosix(target.slice(0, hashIdx)) + target.slice(hashIdx);
342
- }
343
- /**
344
- * Resolve a potentially short reference to its canonical full-path form.
345
- * If the file segment of the ref is a bare stem that uniquely maps to one
346
- * full path, expands it. Otherwise returns the ref unchanged.
347
- *
348
- * When ambiguous (multiple files share the stem), returns all candidates.
349
- * If exactly one candidate actually contains the referenced section,
350
- * `suggested` is set to that candidate so the caller can propose a fix.
351
- */
352
- export function resolveRef(target, sectionIds, fileIndex, slugIndex) {
353
- target = normalizeRefFilePath(target);
354
- const resolveKnown = (candidate) => {
355
- // Preserve existing wiki-link meaning when a literal heading happens to
356
- // collide with a different heading's GitHub slug.
357
- if (sectionIds.has(candidate.toLowerCase()))
358
- return candidate;
359
- return slugIndex?.get(candidate.toLowerCase()) ?? null;
360
- };
361
- const resolveInFile = (file, rest) => {
362
- const direct = resolveKnown(file + rest);
363
- if (direct)
364
- return direct;
365
- // Try inserting root headings between file and rest. This also works for
366
- // slugged child segments because the slug index contains mixed paths.
367
- const rootHeadings = findRootHeadings(file, sectionIds);
368
- for (const h1 of rootHeadings) {
369
- const withRoot = rest ? `${file}#${h1}${rest}` : `${file}#${h1}`;
370
- const resolved = resolveKnown(withRoot);
371
- if (resolved)
372
- return resolved;
373
- }
374
- return null;
375
- };
376
- // Already matches a known section — no resolution needed
377
- const direct = resolveKnown(target);
378
- if (direct) {
379
- return { resolved: direct, ambiguous: null, suggested: null };
380
- }
381
- // Extract the file segment (before first #) and try resolving it
382
- const hashIdx = target.indexOf('#');
383
- const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
384
- const rest = hashIdx === -1 ? '' : target.slice(hashIdx);
385
- // Try resolving the file part: either it's a full path or a bare stem
386
- // File index keys are lowercase for case-insensitive lookup.
387
- const lcFilePart = filePart.toLowerCase();
388
- const filePaths = fileIndex.has(lcFilePart)
389
- ? fileIndex.get(lcFilePart)
390
- : [filePart];
391
- if (filePaths.length === 1) {
392
- const fp = filePaths[0];
393
- const resolved = resolveInFile(fp, rest);
394
- if (resolved) {
395
- return { resolved, ambiguous: null, suggested: null };
396
- }
397
- }
398
- else if (filePaths.length > 1) {
399
- // Multiple files share this stem — ambiguous at the filename level
400
- const all = filePaths.map((c) => c + rest);
401
- const valid = filePaths.filter((c) => resolveInFile(c, rest) !== null);
402
- return {
403
- resolved: target,
404
- ambiguous: all,
405
- suggested: valid.length === 1 ? valid[0] + rest : null,
406
- };
407
- }
408
- return { resolved: target, ambiguous: null, suggested: null };
409
- }
410
- /**
411
- * Find root (h1) headings for a file by scanning sectionIds for entries
412
- * that have exactly the pattern `file#heading` (no further # segments).
413
- */
414
- function findRootHeadings(file, sectionIds) {
415
- const prefix = file.toLowerCase() + '#';
416
- const headings = [];
417
- for (const id of sectionIds) {
418
- if (id.startsWith(prefix) && !id.includes('#', prefix.length)) {
419
- headings.push(id.slice(prefix.length));
420
- }
421
- }
422
- return headings;
423
- }
424
- const MAX_DISTANCE_RATIO = 0.4;
425
- export function findSections(sections, query) {
426
- const flat = flattenSections(sections);
427
- // Leading # means "search for a heading", strip it
428
- const normalized = normalizeRefFilePath(query.startsWith('#') ? query.slice(1) : query);
429
- const q = normalized.toLowerCase();
430
- const isFullPath = normalized.includes('#');
431
- const byId = new Map(flat.map((s) => [s.id.toLowerCase(), s]));
432
- const slugIndex = buildSectionSlugIndex(sections);
433
- const sectionFor = (candidate) => {
434
- const key = candidate.toLowerCase();
435
- return byId.get(key) ?? byId.get(slugIndex.get(key)?.toLowerCase() ?? '');
436
- };
437
- // Tier 1: exact full-id match
438
- const literalExact = flat.filter((s) => s.id.toLowerCase() === q);
439
- const slugExact = literalExact.length === 0 && isFullPath
440
- ? sectionFor(normalized)
441
- : undefined;
442
- const exact = slugExact ? [slugExact] : literalExact;
443
- const exactMatches = exact.map((s) => ({
444
- section: s,
445
- reason: 'exact match',
446
- }));
447
- if (exactMatches.length > 0 && isFullPath)
448
- return exactMatches;
449
- // Build file index early — used by both tier 1a and 1b
450
- const fileIndex = buildFileIndex(sections);
451
- // Tier 1a: bare name matches file — return root sections of that file
452
- // Also checks via file index (e.g. "dev-process" → "lat.md/dev-process")
453
- if (!isFullPath && exactMatches.length === 0) {
454
- const matchFiles = new Set();
455
- // Direct match
456
- for (const s of flat) {
457
- if (s.file.toLowerCase() === q &&
458
- !s.id.includes('#', s.file.length + 1)) {
459
- matchFiles.add(s.file);
460
- }
461
- }
462
- // File index expansion (keys are lowercase)
463
- const indexPaths = fileIndex.get(q) ?? [];
464
- for (const p of indexPaths) {
465
- matchFiles.add(p);
466
- }
467
- if (matchFiles.size > 0) {
468
- const fileRoots = flat.filter((s) => matchFiles.has(s.file) && !s.id.includes('#', s.file.length + 1));
469
- if (fileRoots.length > 0) {
470
- return fileRoots.map((s) => ({
471
- section: s,
472
- reason: 'exact match',
473
- }));
474
- }
475
- }
476
- }
477
- // Tier 1b: file stem expansion
478
- // For bare names: "locate" → matches root section of "tests/locate.md"
479
- // For paths with #: "setup#Install" → expands to "guides/setup#Install"
480
- const stemMatches = [];
481
- if (isFullPath) {
482
- // Expand file stem in the file part of the query
483
- const hashIdx = normalized.indexOf('#');
484
- const filePart = normalized.slice(0, hashIdx);
485
- const rest = normalized.slice(hashIdx);
486
- const stemPaths = fileIndex.get(filePart.toLowerCase()) ?? [];
487
- // Also try filePart as a direct file path (for root-level files not in index)
488
- const allPaths = stemPaths.length > 0 ? stemPaths : filePart ? [filePart] : [];
489
- for (const p of allPaths) {
490
- const s = sectionFor(p + rest);
491
- if (s) {
492
- if (exact.includes(s))
493
- continue;
494
- stemMatches.push({
495
- section: s,
496
- reason: stemPaths.length > 0
497
- ? `file stem expanded: ${filePart} → ${p}`
498
- : 'exact match',
499
- });
500
- continue;
501
- }
502
- // Try inserting root headings: file#rest → file#h1#rest
503
- const rootsOfFile = flat.filter((s) => s.file.toLowerCase() === p.toLowerCase() &&
504
- !s.id.includes('#', s.file.length + 1));
505
- for (const root of rootsOfFile) {
506
- const match = sectionFor(root.id + rest);
507
- if (match) {
508
- if (exact.includes(match))
509
- continue;
510
- stemMatches.push({
511
- section: match,
512
- reason: stemPaths.length > 0
513
- ? `file stem expanded: ${filePart} → ${p}`
514
- : 'exact match',
515
- });
516
- }
517
- }
518
- }
519
- if (stemMatches.length > 0)
520
- return [...exactMatches, ...stemMatches];
521
- }
522
- else {
523
- // Bare name: match root sections of files via stem index (keys lowercase)
524
- const paths = fileIndex.get(q) ?? [];
525
- for (const p of paths) {
526
- for (const s of flat) {
527
- if (exact.includes(s))
528
- continue;
529
- // Root sections have id = "file#heading" (exactly 2 segments)
530
- if (s.file.toLowerCase() === p.toLowerCase() &&
531
- !s.id.includes('#', s.file.length + 1)) {
532
- stemMatches.push({ section: s, reason: 'file stem match' });
533
- }
534
- }
535
- }
536
- }
537
- // Tier 2: exact match on trailing segments (subsection name match)
538
- const seen = new Set([
539
- ...exact.map((s) => s.id),
540
- ...stemMatches.map((m) => m.section.id),
541
- ]);
542
- const slugTailMatches = new Set();
543
- if (!isFullPath) {
544
- for (const [alias, canonical] of slugIndex) {
545
- if (alias.slice(alias.lastIndexOf('#') + 1) === q) {
546
- slugTailMatches.add(canonical.toLowerCase());
547
- }
548
- }
549
- }
550
- const literalTailMatches = new Set(isFullPath
551
- ? []
552
- : flat
553
- .filter((s) => tailSegments(s.id).some((tail) => tail.toLowerCase() === q))
554
- .map((s) => s.id.toLowerCase()));
555
- const subsection = isFullPath
556
- ? []
557
- : flat
558
- .filter((s) => {
559
- if (seen.has(s.id))
560
- return false;
561
- const id = s.id.toLowerCase();
562
- return literalTailMatches.size > 0
563
- ? literalTailMatches.has(id)
564
- : slugTailMatches.has(id);
565
- })
566
- .map((s) => ({ section: s, reason: 'section name match' }));
567
- // Tier 2b: subsequence match — query segments are a subsequence of section id segments
568
- // e.g. "Markdown#Resolution Rules" matches "markdown#Wiki Links#Resolution Rules"
569
- // Also tries expanding the file part via the file index for short refs.
570
- const seenSub = new Set([...seen, ...subsection.map((m) => m.section.id)]);
571
- const qParts = q.split('#');
572
- // Build query variants: original + file-index-expanded forms
573
- const qVariants = [qParts];
574
- if (qParts.length >= 2) {
575
- const expanded = fileIndex.get(qParts[0]);
576
- if (expanded) {
577
- for (const exp of expanded) {
578
- qVariants.push([exp.toLowerCase(), ...qParts.slice(1)]);
579
- }
580
- }
581
- }
582
- const subsequence = qParts.length >= 2
583
- ? flat
584
- .filter((s) => {
585
- if (seenSub.has(s.id))
586
- return false;
587
- const sParts = s.id.toLowerCase().split('#');
588
- return qVariants.some((variant) => {
589
- if (sParts.length <= variant.length)
590
- return false;
591
- let qi = 0;
592
- for (const sp of sParts) {
593
- if (sp === variant[qi])
594
- qi++;
595
- if (qi === variant.length)
596
- return true;
597
- }
598
- return false;
599
- });
600
- })
601
- .map((s) => {
602
- const skipped = s.id.split('#').length - qParts.length;
603
- return {
604
- section: s,
605
- reason: `path match, ${skipped} intermediate ${skipped === 1 ? 'section' : 'sections'} skipped`,
606
- };
607
- })
608
- : [];
609
- // Tier 3: fuzzy match by edit distance on each segment tail and full id
610
- const seenAll = new Set([
611
- ...seenSub,
612
- ...subsequence.map((m) => m.section.id),
613
- ]);
614
- const fuzzy = [];
615
- // For full-path queries, extract the file and heading parts so we can
616
- // fuzzy-match only the heading portion when the file part matches exactly.
617
- // This prevents the shared file prefix from inflating similarity scores
618
- // (e.g. "cli#locat" would otherwise fuzzy-match "cli#prompt").
619
- const qHashIdx = normalized.indexOf('#');
620
- const qFile = qHashIdx === -1 ? null : normalized.slice(0, qHashIdx).toLowerCase();
621
- const qHeading = qHashIdx === -1 ? null : normalized.slice(qHashIdx + 1).toLowerCase();
622
- for (const s of flat) {
623
- if (seenAll.has(s.id))
624
- continue;
625
- const candidates = [s.id, ...tailSegments(s.id)];
626
- let best = Infinity;
627
- let bestCandidate = '';
628
- for (const c of candidates) {
629
- let d;
630
- let maxLen;
631
- const cl = c.toLowerCase();
632
- const cHashIdx = cl.indexOf('#');
633
- // When both query and candidate have # and their file parts match,
634
- // compare only the heading portions to avoid file-prefix inflation
635
- if (qFile && qHeading && cHashIdx !== -1) {
636
- const cFile = cl.slice(0, cHashIdx);
637
- const cHeading = cl.slice(cHashIdx + 1);
638
- if (cFile === qFile) {
639
- d = levenshtein(cHeading, qHeading);
640
- maxLen = Math.max(cHeading.length, qHeading.length);
641
- }
642
- else {
643
- d = levenshtein(cl, q);
644
- maxLen = Math.max(c.length, q.length);
645
- }
646
- }
647
- else {
648
- d = levenshtein(cl, q);
649
- maxLen = Math.max(c.length, q.length);
650
- }
651
- if (maxLen > 0 && d / maxLen <= MAX_DISTANCE_RATIO && d < best) {
652
- best = d;
653
- bestCandidate = c;
654
- }
655
- }
656
- if (best < Infinity) {
657
- fuzzy.push({ section: s, distance: best, matched: bestCandidate });
658
- }
659
- }
660
- fuzzy.sort((a, b) => a.distance - b.distance);
661
- const fuzzyMatches = fuzzy.map((f) => ({
662
- section: f.section,
663
- reason: f.matched.toLowerCase() === f.section.id.toLowerCase()
664
- ? `fuzzy match, distance ${f.distance}`
665
- : `fuzzy match on "${f.matched}", distance ${f.distance}`,
666
- }));
667
- // Sort results: shallower depth first, then fewer path segments
668
- const sortKey = (s) => {
669
- const pathDepth = (s.file.match(/\//g) || []).length;
670
- return s.depth * 100 + pathDepth;
671
- };
672
- const sortedStems = [...stemMatches].sort((a, b) => sortKey(a.section) - sortKey(b.section));
673
- return [
674
- ...exactMatches,
675
- ...sortedStems,
676
- ...subsection,
677
- ...subsequence,
678
- ...fuzzyMatches,
679
- ];
680
- }
681
193
  export function extractRefs(filePath, content, projectRoot, tree = parse(content)) {
682
194
  const file = projectRoot
683
195
  ? toPosix(relative(projectRoot, filePath)).replace(/\.md$/, '')
@@ -749,7 +261,7 @@ function closingBracket(value, open) {
749
261
  }
750
262
  return -1;
751
263
  }
752
- /** Extract link destinations and undefined full/collapsed references. */
264
+ /** Extract link destinations and undefined reference syntax. */
753
265
  export function extractLinks(content, tree = parse(content)) {
754
266
  const links = [];
755
267
  visit(tree, ['link', 'image', 'definition'], (node) => {
@@ -764,12 +276,16 @@ export function extractLinks(content, tree = parse(content)) {
764
276
  'code',
765
277
  'inlineCode',
766
278
  'html',
279
+ 'math',
280
+ 'inlineMath',
767
281
  'yaml',
768
282
  'link',
769
283
  'image',
770
284
  'definition',
771
285
  'linkReference',
772
286
  'imageReference',
287
+ 'footnoteReference',
288
+ 'alertMarker',
773
289
  'wikiLink',
774
290
  ], (node) => {
775
291
  const start = node.position?.start.offset;
@@ -778,28 +294,58 @@ export function extractLinks(content, tree = parse(content)) {
778
294
  excluded.push({ start, end });
779
295
  }
780
296
  });
297
+ // GFM removes task-list markers from the paragraph AST. Exclude just the
298
+ // marker prefix so strict shortcut checks still apply to the item body.
299
+ visit(tree, 'listItem', (node) => {
300
+ if (typeof node.checked !== 'boolean')
301
+ return;
302
+ const start = node.position?.start.offset;
303
+ const bodyStart = node.children[0]?.position?.start.offset;
304
+ if (start === undefined || bodyStart === undefined)
305
+ return;
306
+ const marker = /\[[ xX]\]/.exec(content.slice(start, bodyStart));
307
+ if (marker) {
308
+ excluded.push({
309
+ start: start + marker.index,
310
+ end: start + marker.index + marker[0].length,
311
+ });
312
+ }
313
+ });
314
+ // Likewise, exclude only a footnote definition's label and delimiter. Its
315
+ // body remains prose and must still obey strict shortcut validation.
316
+ visit(tree, 'footnoteDefinition', (node) => {
317
+ const start = node.position?.start.offset;
318
+ const bodyStart = node.children[0]?.position?.start.offset;
319
+ if (start !== undefined && bodyStart !== undefined) {
320
+ excluded.push({ start, end: bodyStart });
321
+ }
322
+ });
781
323
  const overlapsExcluded = (start, end) => excluded.some((range) => start < range.end && end > range.start);
782
324
  for (let open = 0; open < content.length; open++) {
783
325
  if (content[open] !== '[' || isEscapedAt(content, open))
784
326
  continue;
785
327
  const labelEnd = closingBracket(content, open);
786
- const identifierStart = labelEnd + 1;
787
- if (labelEnd === -1 ||
788
- content[identifierStart] !== '[' ||
789
- isEscapedAt(content, identifierStart)) {
328
+ if (labelEnd === -1)
790
329
  continue;
791
- }
792
- const identifierEnd = closingBracket(content, identifierStart);
330
+ const identifierStart = labelEnd + 1;
331
+ const hasSecondLabel = content[identifierStart] === '[' &&
332
+ !isEscapedAt(content, identifierStart);
333
+ const identifierEnd = hasSecondLabel
334
+ ? closingBracket(content, identifierStart)
335
+ : labelEnd;
793
336
  if (identifierEnd === -1)
794
337
  continue;
795
338
  const isImage = open > 0 && content[open - 1] === '!' && !isEscapedAt(content, open - 1);
339
+ const isMalformedDefinition = !isImage && !hasSecondLabel && content[labelEnd + 1] === ':';
796
340
  const start = isImage ? open - 1 : open;
797
- const end = identifierEnd + 1;
341
+ const end = identifierEnd + 1 + (isMalformedDefinition ? 1 : 0);
798
342
  if (overlapsExcluded(start, end)) {
799
343
  open = identifierEnd;
800
344
  continue;
801
345
  }
802
- const explicitIdentifier = content.slice(identifierStart + 1, identifierEnd);
346
+ const explicitIdentifier = hasSecondLabel
347
+ ? content.slice(identifierStart + 1, identifierEnd)
348
+ : '';
803
349
  const identifier = explicitIdentifier || content.slice(open + 1, labelEnd);
804
350
  if (identifier.trim() === '') {
805
351
  open = identifierEnd;
@@ -809,6 +355,13 @@ export function extractLinks(content, tree = parse(content)) {
809
355
  identifier: identifier.trim(),
810
356
  source: content.slice(start, end),
811
357
  kind: isImage ? 'imageReference' : 'linkReference',
358
+ style: !hasSecondLabel
359
+ ? isMalformedDefinition
360
+ ? 'definition'
361
+ : 'shortcut'
362
+ : explicitIdentifier
363
+ ? 'full'
364
+ : 'collapsed',
812
365
  line: content.slice(0, start).split('\n').length,
813
366
  });
814
367
  open = identifierEnd;
@@ -0,0 +1,20 @@
1
+ import type { MarkdownAnalysisTimings, MarkdownFileAnalysis } from './markdown-analysis.js';
2
+ import type { ParserImportObserver } from './parser-import.js';
3
+ export { PARSER_CACHE_VERSION } from './parser-cache.js';
4
+ export type MarkdownAnalysisCacheStatus = 'disabled' | 'hit' | 'miss';
5
+ export type PreparedMarkdownAnalysis = {
6
+ absolutePath: string;
7
+ cachePath: string;
8
+ content: string;
9
+ contentHash: string;
10
+ analysis?: MarkdownFileAnalysis;
11
+ timings: Pick<MarkdownAnalysisTimings, 'readMs' | 'hashMs' | 'cacheReadMs' | 'cacheStatus'>;
12
+ };
13
+ /** Return the collision-safe, sharded cache path for one Markdown file. */
14
+ export declare function markdownAnalysisCachePath(latDir: string, projectRoot: string, absolutePath: string): string;
15
+ /** Read, hash, and attempt to hydrate one Markdown file from persistent cache. */
16
+ export declare function prepareMarkdownAnalysis(absolutePath: string, latDir: string, projectRoot: string, cache?: boolean): Promise<PreparedMarkdownAnalysis>;
17
+ /** Attach I/O timings and best-effort publish one newly parsed analysis. */
18
+ export declare function publishMarkdownAnalysis(prepared: PreparedMarkdownAnalysis, analysis: MarkdownFileAnalysis): Promise<MarkdownFileAnalysis>;
19
+ /** Analyze one path through the same persistent cache used by project runs. */
20
+ export declare function analyzeMarkdownPath(absolutePath: string, latDir: string, projectRoot: string, cache?: boolean, onParserImport?: ParserImportObserver): Promise<MarkdownFileAnalysis>;