@plebeiantech/lat.md 0.12.3-fork.12 → 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 (275) 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 +33 -3
  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/fork/conventions.md +30 -4
  272. package/templates/skill/SKILL.md +22 -1
  273. package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
  274. package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
  275. package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
@@ -1,17 +1,21 @@
1
- import { readFile, writeFile, lstat, rename, unlink } from 'node:fs/promises';
1
+ import { lstat, rename, unlink, writeFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { basename, dirname, extname, join, relative, resolve } from 'node:path';
4
- import { listLatticeFiles, loadAllSections, extractLinks, extractRefs, flattenSections, parseFrontmatter, parseSections, buildFileIndex, buildSectionSlugIndex, resolveRef, } from '../lattice.js';
5
- import { scanCodeRefs } from '../code-refs.js';
4
+ import { performance } from 'node:perf_hooks';
5
+ import { flattenSections, resolveRef } from '../lattice-model.js';
6
+ import { isSourceFileExtension, SOURCE_FILE_EXTENSIONS, } from '../source-formats.js';
7
+ import { inspectRepositoryPath, normalizeRepositoryPath, } from '../repository-path.js';
8
+ import { toPosix } from '../path.js';
9
+ import { TimingProfiler } from '../profiler.js';
6
10
  import { checkMode } from './check-mode.js';
7
11
  import { checkStatus } from './check-status.js';
8
12
  import { checkFrontmatter } from './check-frontmatter.js';
9
13
  import { checkCoverage } from './check-coverage.js';
10
14
  import { indexEntryNameFromDest } from './link-scheme.js';
11
15
  import { fencedLineMask } from './gen-index.js';
12
- import { SOURCE_EXTENSIONS, clearSymbolCache } from '../source-parser.js';
13
- import { toPosix, walkEntries } from '../walk.js';
14
16
  import { INIT_VERSION, readInitVersion } from '../init-version.js';
17
+ import { CheckRunContext } from './check-context.js';
18
+ import { parseLocalMarkdownTarget } from '../markdown-validation.js';
15
19
  function filePart(id) {
16
20
  const h = id.indexOf('#');
17
21
  return h === -1 ? id : id.slice(0, h);
@@ -31,50 +35,66 @@ export function ambiguousRefMessage(target, candidates, suggested) {
31
35
  lines.push(` The short path "${shortName}" is ambiguous — ${candidates.length} files match:`, fileList, ` Please fix the link to use a fully qualified path.`);
32
36
  return lines.join('\n');
33
37
  }
34
- function countByExt(paths) {
35
- const stats = {};
36
- for (const p of paths) {
37
- const ext = extname(p) || '(no ext)';
38
- stats[ext] = (stats[ext] || 0) + 1;
39
- }
40
- return stats;
38
+ async function profileTime(profile, label, work, detail) {
39
+ return profile ? profile.time(label, work, detail) : work();
41
40
  }
42
- function isSourcePath(target) {
43
- const hashIdx = target.indexOf('#');
44
- const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
45
- const ext = extname(filePart);
46
- return SOURCE_EXTENSIONS.has(ext);
41
+ function profileTimeSync(profile, label, work, detail) {
42
+ return profile ? profile.timeSync(label, work, detail) : work();
47
43
  }
48
44
  /**
49
- * Try resolving a wiki link target as a source code reference.
50
- * Returns null if the reference is valid, or an error message string.
45
+ * Validate an unresolved wiki link as a repository path or source symbol.
46
+ * Returns null when valid, otherwise a user-facing error message.
51
47
  */
52
- export async function sourceRefError(target, projectRoot) {
53
- if (!isSourcePath(target)) {
54
- // Check if it looks like a file path with an unsupported extension
55
- const hashIdx = target.indexOf('#');
56
- const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
57
- const ext = extname(filePart);
58
- if (ext && hashIdx !== -1) {
59
- const supported = [...SOURCE_EXTENSIONS].sort().join(', ');
60
- return `broken link [[${target}]] — unsupported file extension "${ext}". Supported: ${supported}`;
61
- }
48
+ export async function repositoryRefError(target, projectRoot, sourceOptions = {}) {
49
+ const hashIdx = target.indexOf('#');
50
+ const authoredPath = hashIdx === -1 ? target : target.slice(0, hashIdx);
51
+ if (!authoredPath) {
62
52
  return `broken link [[${target}]] — no matching section found`;
63
53
  }
64
- const hashIdx = target.indexOf('#');
65
- const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
54
+ const filePart = normalizeRepositoryPath(authoredPath);
55
+ if (!filePart) {
56
+ return `broken link [[${target}]] — repository path "${authoredPath}" must stay within the project root`;
57
+ }
66
58
  const symbolPart = hashIdx === -1 ? '' : target.slice(hashIdx + 1);
67
- const absPath = join(projectRoot, filePart);
68
- if (!existsSync(absPath)) {
59
+ const ext = extname(filePart);
60
+ const sourcePath = isSourceFileExtension(ext);
61
+ const inspected = await inspectRepositoryPath(projectRoot, filePart);
62
+ if (inspected.kind === 'outside') {
63
+ return `broken link [[${target}]] — repository path "${filePart}" resolves outside the project root`;
64
+ }
65
+ if (hashIdx === -1) {
66
+ if (inspected.kind === 'missing') {
67
+ return `broken link [[${target}]] — repository file or directory "${filePart}" not found`;
68
+ }
69
+ if (inspected.kind === 'other') {
70
+ return `broken link [[${target}]] — repository path "${filePart}" is not a regular file or directory`;
71
+ }
72
+ return null;
73
+ }
74
+ if (inspected.kind === 'directory') {
75
+ return `broken link [[${target}]] — directory "${filePart}" cannot have a fragment`;
76
+ }
77
+ if (inspected.kind === 'other') {
78
+ return `broken link [[${target}]] — repository path "${filePart}" is not a regular file or directory`;
79
+ }
80
+ if (!sourcePath) {
81
+ if (ext || inspected.kind === 'file') {
82
+ const shownExtension = ext || '(none)';
83
+ const supported = SOURCE_FILE_EXTENSIONS.join(', ');
84
+ return `broken link [[${target}]] — unsupported file extension "${shownExtension}" for fragment reference. Supported: ${supported}`;
85
+ }
86
+ return `broken link [[${target}]] — no matching section found`;
87
+ }
88
+ if (inspected.kind === 'missing') {
69
89
  return `broken link [[${target}]] — file "${filePart}" not found`;
70
90
  }
71
91
  if (!symbolPart) {
72
- // File-only link with no symbol — valid as long as file exists
92
+ // Preserve the existing file-level behavior for an empty fragment.
73
93
  return null;
74
94
  }
75
95
  try {
76
96
  const { resolveSourceSymbol } = await import('../source-parser.js');
77
- const { found, error } = await resolveSourceSymbol(filePart, symbolPart, projectRoot);
97
+ const { found, error } = await resolveSourceSymbol(filePart, symbolPart, projectRoot, sourceOptions);
78
98
  if (error) {
79
99
  return `broken link [[${target}]] — ${error}`;
80
100
  }
@@ -87,20 +107,50 @@ export async function sourceRefError(target, projectRoot) {
87
107
  return `broken link [[${target}]] — failed to parse "${filePart}": ${err instanceof Error ? err.message : String(err)}`;
88
108
  }
89
109
  }
90
- export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
91
- clearSymbolCache();
92
- const files = await listLatticeFiles(latticeDir);
93
- const allSections = await loadAllSections(latticeDir, projectRoot);
94
- const flat = flattenSections(allSections);
95
- const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
96
- const fileIndex = buildFileIndex(allSections);
97
- const slugIndex = buildSectionSlugIndex(allSections);
110
+ export async function checkMd(latticeDir, projectRoot = dirname(latticeDir), context) {
111
+ const run = context ?? new CheckRunContext(latticeDir, projectRoot);
112
+ run.clearSourceSymbolCache();
113
+ const files = await run.markdownFiles();
114
+ const { sectionIds, fileIndex, slugIndex } = await run.sectionIndex();
98
115
  const errors = [];
116
+ const external = await run.externalResolver();
117
+ for (const error of external.snapshot.errors) {
118
+ errors.push({
119
+ file: relative(process.cwd(), error.file),
120
+ line: 1,
121
+ target: '',
122
+ message: error.message,
123
+ });
124
+ }
99
125
  for (const file of files) {
100
- const content = await readFile(file, 'utf-8');
101
- const refs = extractRefs(file, content, projectRoot);
126
+ const refs = await run.refs(file);
102
127
  const relPath = relative(process.cwd(), file);
103
128
  for (const ref of refs) {
129
+ try {
130
+ if (external.parse(ref.target)) {
131
+ await run.resolveExternal(ref.target);
132
+ continue;
133
+ }
134
+ }
135
+ catch (error) {
136
+ errors.push({
137
+ file: relPath,
138
+ line: ref.line,
139
+ target: ref.target,
140
+ message: `broken external link [[${ref.target}]] — ${error.message}`,
141
+ });
142
+ continue;
143
+ }
144
+ const unknownExternal = external.unknownTargetMessage(ref.target);
145
+ if (unknownExternal) {
146
+ errors.push({
147
+ file: relPath,
148
+ line: ref.line,
149
+ target: ref.target,
150
+ message: unknownExternal,
151
+ });
152
+ continue;
153
+ }
104
154
  const { resolved, ambiguous, suggested } = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
105
155
  if (ambiguous) {
106
156
  errors.push({
@@ -111,8 +161,8 @@ export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
111
161
  });
112
162
  }
113
163
  else if (!sectionIds.has(resolved.toLowerCase())) {
114
- // Try resolving as a source code reference (e.g. [[src/foo.ts#bar]])
115
- const sourceErr = await sourceRefError(ref.target, projectRoot);
164
+ // Try resolving as a repository path or source symbol.
165
+ const sourceErr = await run.resolveRepositoryLink(ref.target, () => repositoryRefError(ref.target, projectRoot, run.sourceSymbolOptions()));
116
166
  if (sourceErr !== null) {
117
167
  errors.push({
118
168
  file: relPath,
@@ -124,84 +174,36 @@ export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
124
174
  }
125
175
  }
126
176
  }
127
- return { errors, files: countByExt(files) };
177
+ return { errors };
128
178
  }
129
- function decodeLinkPart(value) {
130
- try {
131
- return decodeURIComponent(value);
132
- }
133
- catch {
134
- return value;
135
- }
136
- }
137
- /** Parse a local markdown destination without confusing escaped #/? in paths. */
138
- function localLinkTarget(url) {
139
- const u = url.trim();
140
- if (u.startsWith('/'))
141
- return null;
142
- const windowsDrivePath = /^[a-zA-Z]:\\/.test(u);
143
- if (!windowsDrivePath && /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(u)) {
144
- return null;
145
- }
146
- // Split before decoding: `%23` and `%3F` decode to `#` and `?`, which would
147
- // then truncate a filename that legitimately contains them.
148
- const queryAt = u.indexOf('?');
149
- const fragmentAt = u.indexOf('#');
150
- const pathEnd = Math.min(queryAt === -1 ? u.length : queryAt, fragmentAt === -1 ? u.length : fragmentAt);
151
- const rawPath = u.slice(0, pathEnd);
152
- const path = rawPath === '' ? null : decodeLinkPart(rawPath);
153
- const fragment = fragmentAt === -1 ? null : decodeLinkPart(u.slice(fragmentAt + 1));
154
- if (rawPath === '' && fragment === null)
155
- return null;
156
- if (path?.includes('\\'))
157
- return { kind: 'invalid-backslash' };
158
- return {
159
- kind: 'target',
160
- path,
161
- fragment,
162
- };
163
- }
164
- export async function checkLinks(latticeDir) {
165
- const files = await listLatticeFiles(latticeDir);
179
+ // --- Relative link validation ---
180
+ export async function checkLinks(latticeDir, context) {
181
+ const run = context ?? new CheckRunContext(latticeDir, dirname(latticeDir));
182
+ const files = await run.markdownFiles();
166
183
  const errors = [];
167
- const headingCache = new Map();
168
- const headingsFor = async (file) => {
169
- const cached = headingCache.get(file);
170
- if (cached)
171
- return cached;
172
- const content = await readFile(file, 'utf-8');
173
- const headings = new Set(flattenSections(parseSections(file, content)).map((section) => section.githubSlug));
174
- headingCache.set(file, headings);
175
- return headings;
176
- };
177
184
  for (const file of files) {
178
- const content = await readFile(file, 'utf-8');
185
+ const links = await run.links(file);
179
186
  const relPath = toPosix(relative(process.cwd(), file));
180
- for (const link of extractLinks(content)) {
181
- if ('identifier' in link) {
182
- const kind = link.kind === 'imageReference' ? 'image' : 'link';
183
- errors.push({
184
- file: relPath,
185
- line: link.line,
186
- target: link.identifier,
187
- message: `undefined ${kind} reference (${link.source}) — definition "[${link.identifier}]" not found`,
188
- });
187
+ for (const diagnostic of await run.diagnostics(file)) {
188
+ if (diagnostic.rule !== 'markdown-reference-definition' &&
189
+ diagnostic.rule !== 'markdown-path-separator') {
189
190
  continue;
190
191
  }
191
- const target = localLinkTarget(link.url);
192
+ errors.push({
193
+ file: relPath,
194
+ line: diagnostic.line,
195
+ target: diagnostic.target,
196
+ message: diagnostic.message,
197
+ });
198
+ }
199
+ for (const link of links) {
200
+ if ('identifier' in link)
201
+ continue;
202
+ const target = parseLocalMarkdownTarget(link.url);
192
203
  if (target === null)
193
204
  continue;
194
- if (target.kind === 'invalid-backslash') {
195
- const kind = link.kind === 'image' ? 'image' : 'link';
196
- errors.push({
197
- file: relPath,
198
- line: link.line,
199
- target: link.url,
200
- message: `invalid ${kind} (${link.url}) — backslashes are not path ` +
201
- 'separators in Markdown; use "/" instead',
202
- });
205
+ if (target.kind === 'invalid-backslash')
203
206
  continue;
204
- }
205
207
  const abs = target.path ? resolve(dirname(file), target.path) : file;
206
208
  if (!existsSync(abs)) {
207
209
  const kind = link.kind === 'image' ? 'image' : 'link';
@@ -217,7 +219,7 @@ export async function checkLinks(latticeDir) {
217
219
  if (target.fragment &&
218
220
  extname(abs).toLowerCase() === '.md' &&
219
221
  link.kind !== 'image') {
220
- const headings = await headingsFor(abs);
222
+ const headings = await run.headings(abs);
221
223
  if (!headings.has(target.fragment)) {
222
224
  const shown = toPosix(relative(process.cwd(), abs));
223
225
  errors.push({
@@ -232,17 +234,51 @@ export async function checkLinks(latticeDir) {
232
234
  }
233
235
  return errors;
234
236
  }
235
- export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir)) {
236
- const allSections = await loadAllSections(latticeDir, projectRoot);
237
- const flat = flattenSections(allSections);
238
- const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
239
- const fileIndex = buildFileIndex(allSections);
240
- const slugIndex = buildSectionSlugIndex(allSections);
241
- const scan = await scanCodeRefs(projectRoot);
237
+ export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir), context) {
238
+ const run = context ?? new CheckRunContext(latticeDir, projectRoot);
239
+ const [{ sectionIds, fileIndex, slugIndex }, scan, external] = await Promise.all([
240
+ run.sectionIndex(),
241
+ run.codeRefs(),
242
+ run.externalResolver(),
243
+ ]);
242
244
  const errors = [];
245
+ for (const error of external.snapshot.errors) {
246
+ errors.push({
247
+ file: relative(process.cwd(), error.file),
248
+ line: 1,
249
+ target: '',
250
+ message: error.message,
251
+ });
252
+ }
243
253
  const mentionedSections = new Set();
244
254
  for (const ref of scan.refs) {
245
- const { resolved, ambiguous, suggested } = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
255
+ try {
256
+ const externalTarget = profileTimeSync(run.profile, 'classify code-reference target', () => external.parse(ref.target), ref.target);
257
+ if (externalTarget) {
258
+ await run.resolveExternal(ref.target);
259
+ continue;
260
+ }
261
+ }
262
+ catch (error) {
263
+ errors.push({
264
+ file: relative(process.cwd(), join(projectRoot, ref.file)),
265
+ line: ref.line,
266
+ target: ref.target,
267
+ message: `@lat: [[${ref.target}]] — ${error.message}`,
268
+ });
269
+ continue;
270
+ }
271
+ const unknownExternal = profileTimeSync(run.profile, 'check unknown external handle', () => external.unknownTargetMessage(ref.target), ref.target);
272
+ if (unknownExternal) {
273
+ errors.push({
274
+ file: relative(process.cwd(), join(projectRoot, ref.file)),
275
+ line: ref.line,
276
+ target: ref.target,
277
+ message: `@lat: [[${ref.target}]] — ${unknownExternal}`,
278
+ });
279
+ continue;
280
+ }
281
+ const { resolved, ambiguous, suggested } = profileTimeSync(run.profile, 'resolve internal code reference', () => resolveRef(ref.target, sectionIds, fileIndex, slugIndex), ref.target);
246
282
  mentionedSections.add(resolved.toLowerCase());
247
283
  const displayPath = relative(process.cwd(), join(projectRoot, ref.file));
248
284
  if (ambiguous) {
@@ -262,15 +298,13 @@ export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir
262
298
  });
263
299
  }
264
300
  }
265
- const files = await listLatticeFiles(latticeDir);
301
+ const files = await run.markdownFiles();
266
302
  for (const file of files) {
267
- const content = await readFile(file, 'utf-8');
268
- const fm = parseFrontmatter(content);
303
+ const fm = await run.frontmatter(file);
269
304
  if (!fm.requireCodeMention)
270
305
  continue;
271
- const sections = parseSections(file, content, projectRoot);
272
- const fileSections = flattenSections(sections);
273
- const leafSections = fileSections.filter((s) => s.children.length === 0);
306
+ const fileSections = flattenSections(await run.sections(file));
307
+ const leafSections = fileSections.filter((section) => section.children.length === 0);
274
308
  const relPath = relative(process.cwd(), file);
275
309
  for (const leaf of leafSections) {
276
310
  if (!mentionedSections.has(leaf.id.toLowerCase())) {
@@ -283,7 +317,7 @@ export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir
283
317
  }
284
318
  }
285
319
  }
286
- return { errors, files: countByExt(scan.files) };
320
+ return { errors };
287
321
  }
288
322
  /**
289
323
  * Extract the immediate (first-level) entries from walkEntries results.
@@ -335,17 +369,41 @@ function entryToStem(name) {
335
369
  function indexSnippet(entries) {
336
370
  return entries.map((e) => `- [[${entryToStem(e)}]] — <describe>`).join('\n');
337
371
  }
338
- export async function checkIndex(latticeDir, opts = {}) {
372
+ export async function checkIndex(latticeDir, context, opts = {}) {
373
+ const run = context ?? new CheckRunContext(latticeDir, dirname(latticeDir));
339
374
  const errors = [];
340
- const allPaths = await walkEntries(latticeDir);
341
- // Flag non-.md files — only markdown belongs in the checked directory.
375
+ const allPaths = await run.entries();
376
+ const referencedResources = new Set();
377
+ for (const file of await run.markdownFiles()) {
378
+ for (const link of await run.links(file)) {
379
+ if ('identifier' in link)
380
+ continue;
381
+ const target = parseLocalMarkdownTarget(link.url);
382
+ if (!target || target.kind === 'invalid-backslash' || !target.path) {
383
+ continue;
384
+ }
385
+ const absolutePath = resolve(dirname(file), target.path);
386
+ const resourcePath = toPosix(relative(latticeDir, absolutePath));
387
+ if (resourcePath &&
388
+ resourcePath !== '..' &&
389
+ !resourcePath.startsWith('../') &&
390
+ !resourcePath.toLowerCase().endsWith('.md')) {
391
+ referencedResources.add(resourcePath);
392
+ }
393
+ }
394
+ }
395
+ // Flag non-.md files unless Markdown reaches them as local resources. The
396
+ // machine-local external override is the other intentional non-Markdown
397
+ // configuration file in the vault.
342
398
  for (const p of allPaths) {
343
399
  const name = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
344
- if (!name.endsWith('.md')) {
400
+ if (!name.endsWith('.md') &&
401
+ p !== 'config.local.yaml' &&
402
+ !referencedResources.has(p)) {
345
403
  const relDir = basename(latticeDir) + '/';
346
404
  errors.push({
347
405
  dir: relDir,
348
- message: `"${p}" is not a .md file — only markdown files belong in the checked directory`,
406
+ message: `"${p}" is not a .md file or a referenced local resource — remove it or link to it from Markdown`,
349
407
  });
350
408
  }
351
409
  }
@@ -382,7 +440,7 @@ export async function checkIndex(latticeDir, opts = {}) {
382
440
  const indexFullPath = join(latticeDir, indexRelPath);
383
441
  let content;
384
442
  try {
385
- content = await readFile(indexFullPath, 'utf-8');
443
+ content = await run.content(indexFullPath);
386
444
  }
387
445
  catch {
388
446
  content = null;
@@ -533,53 +591,26 @@ async function writeGeneratedIndex(latticeDir, dir, indexFullPath, dirName, exis
533
591
  return null;
534
592
  }
535
593
  // --- Section structure validation ---
536
- /** Max characters for the first paragraph of a section (excluding [[wiki links]]). */
537
- const MAX_BODY_LENGTH = 250;
538
- /** Count body text length excluding `[[...]]` wiki link markers and content. */
539
- function bodyTextLength(body) {
540
- return body.replace(/\[\[[^\]]*\]\]/g, '').length;
541
- }
542
- export async function checkSections(latticeDir, projectRoot = dirname(latticeDir)) {
543
- const files = await listLatticeFiles(latticeDir);
594
+ export async function checkSections(latticeDir, projectRoot = dirname(latticeDir), context) {
595
+ const run = context ?? new CheckRunContext(latticeDir, projectRoot);
596
+ const files = await run.markdownFiles();
544
597
  const errors = [];
545
598
  for (const file of files) {
546
- const content = await readFile(file, 'utf-8');
547
- const sections = parseSections(file, content, projectRoot);
548
- const flat = flattenSections(sections);
549
599
  const relPath = relative(process.cwd(), file);
550
- for (const section of flat) {
551
- if (!section.firstParagraph) {
552
- errors.push({
553
- file: relPath,
554
- line: section.startLine,
555
- target: section.id,
556
- message: `section "${section.id}" has no leading paragraph. ` +
557
- `Every section must start with a brief overview (≤${MAX_BODY_LENGTH} chars) ` +
558
- `summarizing what it documents — this powers search snippets and command output.`,
559
- });
600
+ for (const diagnostic of await run.diagnostics(file)) {
601
+ if (diagnostic.rule !== 'section-leading-paragraph')
560
602
  continue;
561
- }
562
- const len = bodyTextLength(section.firstParagraph);
563
- if (len > MAX_BODY_LENGTH) {
564
- errors.push({
565
- file: relPath,
566
- line: section.startLine,
567
- target: section.id,
568
- message: `section "${section.id}" leading paragraph is ${len} characters ` +
569
- `(max ${MAX_BODY_LENGTH}, excluding [[wiki links]]). ` +
570
- `Keep the first paragraph brief — it serves as the section's summary ` +
571
- `in search results and command output. Use subsequent paragraphs for details.`,
572
- });
573
- }
603
+ errors.push({
604
+ file: relPath,
605
+ line: diagnostic.line,
606
+ target: diagnostic.target,
607
+ message: diagnostic.message,
608
+ });
574
609
  }
575
610
  }
576
611
  return errors;
577
612
  }
578
613
  // --- Formatting helpers (shared by all check commands) ---
579
- function formatFileStats(files, s) {
580
- const entries = Object.entries(files).sort(([a], [b]) => a.localeCompare(b));
581
- return s.dim(`Scanned ${entries.map(([ext, n]) => `${n} ${ext}`).join(', ')}`);
582
- }
583
614
  function formatCheckErrors(errors, s) {
584
615
  const lines = [];
585
616
  for (const err of errors) {
@@ -609,42 +640,64 @@ function formatCheckIndexErrors(errors, s) {
609
640
  function formatErrorCount(count, s) {
610
641
  return s.red(`\n${count} error${count === 1 ? '' : 's'} found`);
611
642
  }
612
- // --- Unified command functions ---
613
- export async function checkAllCommand(ctx, opts = {}) {
614
- const startTime = Date.now();
615
- const md = await checkMd(ctx.latDir, ctx.projectRoot);
616
- const linkErrors = await checkLinks(ctx.latDir);
617
- const code = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
643
+ export async function checkAllCommand(ctx, options = {}) {
644
+ const startTime = performance.now();
645
+ const profile = options.profile ? new TimingProfiler() : undefined;
618
646
  // `--fix` is index-only: an index is derived from frontmatter, so it can be
619
647
  // regenerated. Nothing else `lat check` reports has a mechanical fix.
620
- const indexErrors = await checkIndex(ctx.latDir, opts);
621
- const sectionErrors = await checkSections(ctx.latDir, ctx.projectRoot);
622
- const modeErrors = await checkMode(ctx.latDir, ctx.projectRoot);
623
- const statusErrors = await checkStatus(ctx.latDir, ctx.projectRoot);
624
- const fmErrors = await checkFrontmatter(ctx.latDir, ctx.projectRoot);
625
- const coverageErrors = await checkCoverage(ctx.latDir, ctx.projectRoot);
626
- const elapsed = Date.now() - startTime;
627
- const allErrors = [...md.errors, ...linkErrors, ...code.errors];
628
- const allFiles = { ...md.files };
629
- for (const [ext, n] of Object.entries(code.files)) {
630
- allFiles[ext] = (allFiles[ext] || 0) + n;
631
- }
632
- const s = ctx.styler;
633
- const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
634
- const lines = [
635
- formatFileStats(allFiles, s) + s.dim(` in ${elapsedStr}`),
648
+ //
649
+ // A fixing run also has to finish before any other check starts. The checks
650
+ // below run concurrently over one shared parse cache, and --fix rewrites the
651
+ // very files they read; interleaving that write with those reads would make
652
+ // the run's verdict depend on scheduling. So it gets its own context, and
653
+ // the readers get a fresh one built after the writing is done.
654
+ const fixIndexErrors = options.fix
655
+ ? await profileTime(profile, 'generate directory indexes', () => checkIndex(ctx.latDir, new CheckRunContext(ctx.latDir, ctx.projectRoot, profile), { fix: true }))
656
+ : null;
657
+ const run = new CheckRunContext(ctx.latDir, ctx.projectRoot, profile);
658
+ const [md, linkErrors, code, checkedIndexErrors, sectionErrors, modeErrors, statusErrors, fmErrors, coverageErrors,] = await Promise.all([
659
+ profileTime(profile, 'check Markdown wiki links', () => checkMd(ctx.latDir, ctx.projectRoot, run)),
660
+ profileTime(profile, 'check relative Markdown links', () => checkLinks(ctx.latDir, run)),
661
+ profileTime(profile, 'check @lat code references', () => checkCodeRefs(ctx.latDir, ctx.projectRoot, run)),
662
+ // Already generated above when --fix is set; re-reporting it would only
663
+ // describe the state the fix just left behind.
664
+ fixIndexErrors
665
+ ? Promise.resolve([])
666
+ : profileTime(profile, 'check directory indexes', () => checkIndex(ctx.latDir, run)),
667
+ profileTime(profile, 'check section structure', () => checkSections(ctx.latDir, ctx.projectRoot, run)),
668
+ profileTime(profile, 'check Diátaxis modes', () => checkMode(ctx.latDir, ctx.projectRoot)),
669
+ profileTime(profile, 'check review status', () => checkStatus(ctx.latDir, ctx.projectRoot)),
670
+ profileTime(profile, 'check frontmatter placement', () => checkFrontmatter(ctx.latDir, ctx.projectRoot)),
671
+ profileTime(profile, 'check documentation coverage', () => checkCoverage(ctx.latDir, ctx.projectRoot)),
672
+ ]);
673
+ const indexErrors = fixIndexErrors ?? checkedIndexErrors;
674
+ const elapsed = performance.now() - startTime;
675
+ const allErrors = [
676
+ ...new Map([...md.errors, ...linkErrors, ...code.errors].map((error) => [
677
+ `${error.file}\0${error.line}\0${error.target}\0${error.message}`,
678
+ error,
679
+ ])).values(),
636
680
  ];
681
+ const s = ctx.styler;
682
+ const elapsedStr = elapsed < 1000
683
+ ? `${Math.round(elapsed)}ms`
684
+ : `${(elapsed / 1000).toFixed(1)}s`;
685
+ const lines = profile ? profile.format(elapsed) : [];
637
686
  // Init version warning first — user should fix setup before addressing errors
638
687
  if (!ctx.headless) {
639
688
  const storedVersion = readInitVersion(ctx.latDir);
640
689
  if (storedVersion === null) {
641
- lines.push('', s.yellow('Warning:') +
690
+ if (lines.length > 0)
691
+ lines.push('');
692
+ lines.push(s.yellow('Warning:') +
642
693
  ' No init version recorded — run ' +
643
694
  s.cyan('lat init') +
644
695
  ' to set up agent hooks and configuration.');
645
696
  }
646
697
  else if (storedVersion < INIT_VERSION) {
647
- lines.push('', s.yellow('Warning:') +
698
+ if (lines.length > 0)
699
+ lines.push('');
700
+ lines.push(s.yellow('Warning:') +
648
701
  ' Your setup is outdated (v' +
649
702
  storedVersion +
650
703
  ' → v' +
@@ -672,7 +725,7 @@ export async function checkAllCommand(ctx, opts = {}) {
672
725
  lines.push(formatErrorCount(totalErrors, s));
673
726
  return { output: lines.join('\n'), isError: true };
674
727
  }
675
- lines.push(s.green('All checks passed'));
728
+ lines.push(s.green(`All checks passed in ${elapsedStr}`));
676
729
  // Suggest ripgrep if check was slow (>1s) and rg is not available
677
730
  if (elapsed > 1000) {
678
731
  const { hasRipgrep } = await import('../code-refs.js');
@@ -687,9 +740,9 @@ export async function checkAllCommand(ctx, opts = {}) {
687
740
  return { output: lines.join('\n') };
688
741
  }
689
742
  export async function checkMdCommand(ctx) {
690
- const { errors, files } = await checkMd(ctx.latDir, ctx.projectRoot);
743
+ const { errors } = await checkMd(ctx.latDir, ctx.projectRoot);
691
744
  const s = ctx.styler;
692
- const lines = [formatFileStats(files, s)];
745
+ const lines = [];
693
746
  lines.push(...formatCheckErrors(errors, s));
694
747
  if (errors.length > 0) {
695
748
  lines.push(formatErrorCount(errors.length, s));
@@ -711,9 +764,9 @@ export async function checkLinksCommand(ctx) {
711
764
  return { output: lines.join('\n') };
712
765
  }
713
766
  export async function checkCodeRefsCommand(ctx) {
714
- const { errors, files } = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
767
+ const { errors } = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
715
768
  const s = ctx.styler;
716
- const lines = [formatFileStats(files, s)];
769
+ const lines = [];
717
770
  lines.push(...formatCheckErrors(errors, s));
718
771
  if (errors.length > 0) {
719
772
  lines.push(formatErrorCount(errors.length, s));
@@ -723,7 +776,7 @@ export async function checkCodeRefsCommand(ctx) {
723
776
  return { output: lines.join('\n') };
724
777
  }
725
778
  export async function checkIndexCommand(ctx, opts = {}) {
726
- const errors = await checkIndex(ctx.latDir, opts);
779
+ const errors = await checkIndex(ctx.latDir, undefined, opts);
727
780
  const s = ctx.styler;
728
781
  const lines = [];
729
782
  if (opts.fix) {
@@ -1,7 +1,7 @@
1
1
  import { statSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { styleText } from 'node:util';
4
- import { findLatticeDir } from '../lattice.js';
4
+ import { findLatticeDir } from '../project-discovery.js';
5
5
  export function makeStyler() {
6
6
  return {
7
7
  bold: (s) => styleText('bold', s),