@plebeiantech/lat.md 0.12.3-fork.17 → 0.12.3-fork.20

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 (244) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli/agent-invocation.d.ts +6 -0
  3. package/dist/src/cli/agent-invocation.js +23 -0
  4. package/dist/src/cli/fork-instructions.js +1 -1
  5. package/dist/src/cli/fork-scaffold.js +2 -2
  6. package/dist/src/cli/graph.d.ts +1 -1
  7. package/dist/src/cli/hook.js +11 -11
  8. package/dist/src/cli/index.js +41 -59
  9. package/dist/src/cli/init-preferences.js +5 -4
  10. package/dist/src/cli/init.d.ts +1 -1
  11. package/dist/src/cli/init.js +54 -45
  12. package/dist/src/cli/reindex.d.ts +1 -1
  13. package/dist/src/cli/reindex.js +2 -2
  14. package/dist/src/cli/search.d.ts +4 -4
  15. package/dist/src/cli/search.js +41 -28
  16. package/dist/src/cli/ui-build-server.d.ts +1 -1
  17. package/dist/src/cli/ui-build.d.ts +1 -1
  18. package/dist/src/cli/ui.d.ts +1 -1
  19. package/dist/src/fork/antigravity-hook.js +6 -6
  20. package/dist/src/fork/antigravity-init.d.ts +2 -2
  21. package/dist/src/fork/antigravity-init.js +2 -2
  22. package/dist/src/graph-export.js +7 -7
  23. package/dist/src/knowledge/index.js +2 -2
  24. package/dist/src/mcp/server.js +8 -8
  25. package/dist/src/search/cache.d.ts +1 -1
  26. package/dist/src/search/cache.js +40 -7
  27. package/dist/src/search/chunks.d.ts +2 -2
  28. package/dist/src/search/db.d.ts +5 -4
  29. package/dist/src/search/db.js +36 -40
  30. package/dist/src/search/embedder.js +1 -1
  31. package/dist/src/search/index.d.ts +1 -1
  32. package/dist/src/search/index.js +2 -2
  33. package/dist/src/search/lock.d.ts +4 -1
  34. package/dist/src/search/lock.js +30 -8
  35. package/dist/src/search/query.d.ts +5 -2
  36. package/dist/src/search/query.js +55 -38
  37. package/dist/src/search/search.d.ts +3 -1
  38. package/dist/src/search/search.js +21 -12
  39. package/dist/src/search/types.d.ts +2 -18
  40. package/dist/src/view/client/assets/{GraphView-BWlRdQM4.js → GraphView-Dij8mAA_.js} +1 -1
  41. package/dist/src/view/client/assets/{MarkdownEditor-MW-Ck1O0.js → MarkdownEditor-88Wbt0M1.js} +1 -1
  42. package/dist/src/view/client/assets/{abnfDiagram-VCTEODGH-Bn3RshMV.js → abnfDiagram-VCTEODGH-y0DlYa8E.js} +1 -1
  43. package/dist/src/view/client/assets/{arc-En9tdFQF.js → arc-FlMu8dnO.js} +1 -1
  44. package/dist/src/view/client/assets/{architectureDiagram-5GKGNRK7-B-9J1VAM.js → architectureDiagram-5GKGNRK7-DA_uWZW1.js} +1 -1
  45. package/dist/src/view/client/assets/{blockDiagram-I7D4REHJ-QlznwalP.js → blockDiagram-I7D4REHJ-BHQ5LU85.js} +1 -1
  46. package/dist/src/view/client/assets/{c4Diagram-7LVT6UL2-Bp2y8B4_.js → c4Diagram-7LVT6UL2-BRucKCt2.js} +1 -1
  47. package/dist/src/view/client/assets/channel-BgDNA_Ay.js +1 -0
  48. package/dist/src/view/client/assets/{chunk-2Q5K7J3B-CnBKL-lm.js → chunk-2Q5K7J3B-BJPcFivt.js} +1 -1
  49. package/dist/src/view/client/assets/{chunk-5VM5RSS4-LLYMi_n6.js → chunk-5VM5RSS4-v2q99bGz.js} +1 -1
  50. package/dist/src/view/client/assets/{chunk-F27PBJKO-VIeO7VF0.js → chunk-F27PBJKO-CcHOuJCs.js} +1 -1
  51. package/dist/src/view/client/assets/{chunk-IMKFNOWR-D5i67DVD.js → chunk-IMKFNOWR-DNxYuJNq.js} +1 -1
  52. package/dist/src/view/client/assets/{chunk-JWPE2WC7-D4qAtoL3.js → chunk-JWPE2WC7-C0eHKvLW.js} +1 -1
  53. package/dist/src/view/client/assets/{chunk-POPQ4Y6H-BMzOyiW_.js → chunk-POPQ4Y6H-57CWp2Ve.js} +1 -1
  54. package/dist/src/view/client/assets/{chunk-SVP7TREG-DWy5nbfp.js → chunk-SVP7TREG-BqcoxIJw.js} +1 -1
  55. package/dist/src/view/client/assets/{chunk-TICWLB2K-CVqCANFB.js → chunk-TICWLB2K-DRUJR5zH.js} +1 -1
  56. package/dist/src/view/client/assets/{chunk-XXDRQBXY-Bp23h6Tf.js → chunk-XXDRQBXY-Dez0oRoM.js} +1 -1
  57. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-CFZ_Sgh5.js +1 -0
  58. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-CFZ_Sgh5.js +1 -0
  59. package/dist/src/view/client/assets/{cose-bilkent-JH36ORCC-BCw7fCC2.js → cose-bilkent-JH36ORCC-BHtud6Hg.js} +1 -1
  60. package/dist/src/view/client/assets/{cynefin-OW5HDTMX--lYN4N9m.js → cynefin-OW5HDTMX-BjBW8VGk.js} +1 -1
  61. package/dist/src/view/client/assets/{cynefinDiagram-5FMLGOSQ-9sK5EV7v.js → cynefinDiagram-5FMLGOSQ-DwGuD7oF.js} +1 -1
  62. package/dist/src/view/client/assets/{dagre-GXQ25YYZ-CoMdEFCq.js → dagre-GXQ25YYZ-C5zSlGK9.js} +1 -1
  63. package/dist/src/view/client/assets/{diagram-S7CK7UJ4-D13dyWdd.js → diagram-S7CK7UJ4-C_NhA3bj.js} +1 -1
  64. package/dist/src/view/client/assets/{diagram-UQ7AKVKN-PdwLPPPA.js → diagram-UQ7AKVKN-az7QPYNb.js} +1 -1
  65. package/dist/src/view/client/assets/{diagram-VSXAHHWV-CtWWa_A7.js → diagram-VSXAHHWV-BRvDuM8_.js} +1 -1
  66. package/dist/src/view/client/assets/{diagram-VX7I27RA-BeCYcSU-.js → diagram-VX7I27RA-BWLgF6S-.js} +1 -1
  67. package/dist/src/view/client/assets/{diagram-Z3DM3KII-DiWabOBM.js → diagram-Z3DM3KII-DV3d6mL5.js} +1 -1
  68. package/dist/src/view/client/assets/{ebnfDiagram-PWID7BFC-CZQ3MD_K.js → ebnfDiagram-PWID7BFC-yUXrEAvK.js} +1 -1
  69. package/dist/src/view/client/assets/{erDiagram-RLTQ6QDP-B1AhXIrP.js → erDiagram-RLTQ6QDP-D35FN1Pt.js} +1 -1
  70. package/dist/src/view/client/assets/{flowDiagram-HODETNUW-Bhr0JM6P.js → flowDiagram-HODETNUW-BciZqsjT.js} +1 -1
  71. package/dist/src/view/client/assets/{ganttDiagram-EL5Y4UJY-CbP8iAkg.js → ganttDiagram-EL5Y4UJY-BysPGgXS.js} +1 -1
  72. package/dist/src/view/client/assets/{gitGraphDiagram-WWUBYQGX-B9uuGdLF.js → gitGraphDiagram-WWUBYQGX-C3DdJ5xG.js} +1 -1
  73. package/dist/src/view/client/assets/index-CJokoRSp.js +13 -0
  74. package/dist/src/view/client/assets/{infoDiagram-27XIBGKW-BrfwQ_Cq.js → infoDiagram-27XIBGKW-D3HZrDRb.js} +1 -1
  75. package/dist/src/view/client/assets/{ishikawaDiagram-5VMMS53U-CHtJ36re.js → ishikawaDiagram-5VMMS53U-BXyVIXTU.js} +1 -1
  76. package/dist/src/view/client/assets/{journeyDiagram-3NMN7TZE-BChcgX1s.js → journeyDiagram-3NMN7TZE-D4-1NxVU.js} +1 -1
  77. package/dist/src/view/client/assets/{kanban-definition-UXKFOSKX-BnwFsKiB.js → kanban-definition-UXKFOSKX-4Gu_5yBA.js} +1 -1
  78. package/dist/src/view/client/assets/{layout-Dnd0pLdb.js → layout-Jf1K3TMM.js} +1 -1
  79. package/dist/src/view/client/assets/{linear-CFhW2ozn.js → linear-DWfwzUT_.js} +1 -1
  80. package/dist/src/view/client/assets/{mermaid.core--OP6lXtW.js → mermaid.core-DMx_DAl8.js} +5 -5
  81. package/dist/src/view/client/assets/{mindmap-definition-YA3MSWOX-D51y2zbm.js → mindmap-definition-YA3MSWOX-8pxY_RWi.js} +1 -1
  82. package/dist/src/view/client/assets/{pegDiagram-XKGWAZYB--zyQJDvI.js → pegDiagram-XKGWAZYB-DNAw_O5t.js} +1 -1
  83. package/dist/src/view/client/assets/{pieDiagram-E7YTZNPT-Cj8mvpJy.js → pieDiagram-E7YTZNPT-uuvRCDkC.js} +1 -1
  84. package/dist/src/view/client/assets/{quadrantDiagram-AXDQQJYC-qvhJrnBf.js → quadrantDiagram-AXDQQJYC-BER2O4Aq.js} +1 -1
  85. package/dist/src/view/client/assets/{railroadDiagram-O6MQD6OU-CtWvxpSo.js → railroadDiagram-O6MQD6OU-CxEsJ_0n.js} +1 -1
  86. package/dist/src/view/client/assets/{requirementDiagram-BXWQKSXE-CBxen_KG.js → requirementDiagram-BXWQKSXE-yS9rspca.js} +1 -1
  87. package/dist/src/view/client/assets/{sankeyDiagram-P5KCCOFB-B_fUkmfc.js → sankeyDiagram-P5KCCOFB-Bqt70a2j.js} +1 -1
  88. package/dist/src/view/client/assets/{sequenceDiagram-WJ2MYXX4-Dclv4p68.js → sequenceDiagram-WJ2MYXX4-BvOOEJNr.js} +1 -1
  89. package/dist/src/view/client/assets/{sizeCapture-INFHLROL-GfG0sMTZ.js → sizeCapture-INFHLROL-CvO_Pjjo.js} +1 -1
  90. package/dist/src/view/client/assets/{stateDiagram-D77RDMKH-YQWPnzxU.js → stateDiagram-D77RDMKH-KLmVOZkq.js} +1 -1
  91. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-KCS8Mdzu.js +1 -0
  92. package/dist/src/view/client/assets/{swimlanes-42K2YHIH-5yiCveVS.js → swimlanes-42K2YHIH-DC1r4_sq.js} +1 -1
  93. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-B2TcAwis.js +8 -0
  94. package/dist/src/view/client/assets/{timeline-definition-24CTP7MA-BvAbB5Zs.js → timeline-definition-24CTP7MA-ChXLOuBL.js} +1 -1
  95. package/dist/src/view/client/assets/{vennDiagram-4TSXK5OY-guC-r60n.js → vennDiagram-4TSXK5OY-D9_h25-Y.js} +1 -1
  96. package/dist/src/view/client/assets/{wardleyDiagram-VM6X3IG4-C0yXcU2x.js → wardleyDiagram-VM6X3IG4-CsQJGGW0.js} +1 -1
  97. package/dist/src/view/client/assets/{xychartDiagram-S5SC5T6Z-RPmkPNfK.js → xychartDiagram-S5SC5T6Z-DGMuPMlm.js} +1 -1
  98. package/dist/src/view/client/index.html +1 -1
  99. package/dist/src/view/diagnostics.d.ts +2 -2
  100. package/dist/src/view/diagnostics.js +5 -5
  101. package/dist/src/view/external-document-tree.js +1 -1
  102. package/dist/src/view/git-diff.js +1 -1
  103. package/dist/src/view/git.js +1 -1
  104. package/dist/src/view/graph.d.ts +2 -2
  105. package/dist/src/view/graph.js +2 -2
  106. package/dist/src/view/markdown.js +1 -1
  107. package/dist/src/view/preindexed-search.d.ts +1 -1
  108. package/dist/src/view/protocol.d.ts +2 -18
  109. package/dist/src/view/publication.d.ts +2 -0
  110. package/dist/src/view/publication.js +78 -0
  111. package/dist/src/view/references.d.ts +3 -3
  112. package/dist/src/view/references.js +3 -3
  113. package/dist/src/view/repository.d.ts +6 -6
  114. package/dist/src/view/repository.js +28 -14
  115. package/dist/src/view/request-security.d.ts +5 -0
  116. package/dist/src/view/request-security.js +46 -0
  117. package/dist/src/view/search-result.d.ts +1 -1
  118. package/dist/src/view/search-result.js +1 -1
  119. package/dist/src/view/search.d.ts +1 -1
  120. package/dist/src/view/server-build.d.ts +2 -2
  121. package/dist/src/view/server-build.js +2 -2
  122. package/dist/src/view/server-deployment.d.ts +1 -2
  123. package/dist/src/view/server-deployment.js +6 -46
  124. package/dist/src/view/server-index-worker.d.ts +1 -1
  125. package/dist/src/view/server.d.ts +1 -1
  126. package/dist/src/view/server.js +13 -3
  127. package/dist/src/view/source-target.js +2 -2
  128. package/dist/src/view/static-build.d.ts +1 -1
  129. package/dist/src/view/static-build.js +49 -17
  130. package/dist/src/view/store.d.ts +4 -2
  131. package/dist/src/view/store.js +31 -19
  132. package/dist/src/view/table-of-contents.d.ts +1 -1
  133. package/dist/src/view/table-of-contents.js +1 -1
  134. package/dist/src/view/vercel-server-build.d.ts +1 -1
  135. package/package.json +19 -29
  136. package/templates/AGENTS.md +2 -2
  137. package/templates/cursor-rules.md +2 -2
  138. package/templates/opencode-plugin.ts +9 -9
  139. package/templates/pi-extension.ts +11 -11
  140. package/templates/skill/SKILL.md +2 -1
  141. package/dist/src/cli/check-context.d.ts +0 -56
  142. package/dist/src/cli/check-context.js +0 -215
  143. package/dist/src/cli/check-coverage.d.ts +0 -2
  144. package/dist/src/cli/check-coverage.js +0 -75
  145. package/dist/src/cli/check-frontmatter.d.ts +0 -12
  146. package/dist/src/cli/check-frontmatter.js +0 -44
  147. package/dist/src/cli/check-mode.d.ts +0 -21
  148. package/dist/src/cli/check-mode.js +0 -394
  149. package/dist/src/cli/check-status.d.ts +0 -74
  150. package/dist/src/cli/check-status.js +0 -217
  151. package/dist/src/cli/check.d.ts +0 -52
  152. package/dist/src/cli/check.js +0 -834
  153. package/dist/src/cli/context.d.ts +0 -11
  154. package/dist/src/cli/context.js +0 -58
  155. package/dist/src/cli/expand.d.ts +0 -7
  156. package/dist/src/cli/expand.js +0 -141
  157. package/dist/src/cli/external.d.ts +0 -11
  158. package/dist/src/cli/external.js +0 -185
  159. package/dist/src/cli/gen-index.d.ts +0 -45
  160. package/dist/src/cli/gen-index.js +0 -235
  161. package/dist/src/cli/link-scheme.d.ts +0 -54
  162. package/dist/src/cli/link-scheme.js +0 -101
  163. package/dist/src/cli/locate.d.ts +0 -2
  164. package/dist/src/cli/locate.js +0 -18
  165. package/dist/src/cli/refs.d.ts +0 -23
  166. package/dist/src/cli/refs.js +0 -274
  167. package/dist/src/cli/section.d.ts +0 -45
  168. package/dist/src/cli/section.js +0 -328
  169. package/dist/src/cli/select-menu.d.ts +0 -13
  170. package/dist/src/cli/select-menu.js +0 -100
  171. package/dist/src/code-refs.d.ts +0 -29
  172. package/dist/src/code-refs.js +0 -479
  173. package/dist/src/config.d.ts +0 -29
  174. package/dist/src/config.js +0 -83
  175. package/dist/src/context.d.ts +0 -26
  176. package/dist/src/context.js +0 -11
  177. package/dist/src/document-formats.d.ts +0 -7
  178. package/dist/src/document-formats.js +0 -39
  179. package/dist/src/extensions/alert-marker.d.ts +0 -25
  180. package/dist/src/extensions/alert-marker.js +0 -55
  181. package/dist/src/extensions/wiki-link/from-markdown.d.ts +0 -5
  182. package/dist/src/extensions/wiki-link/from-markdown.js +0 -34
  183. package/dist/src/extensions/wiki-link/index.d.ts +0 -4
  184. package/dist/src/extensions/wiki-link/index.js +0 -3
  185. package/dist/src/extensions/wiki-link/syntax.d.ts +0 -14
  186. package/dist/src/extensions/wiki-link/syntax.js +0 -121
  187. package/dist/src/extensions/wiki-link/to-markdown.d.ts +0 -5
  188. package/dist/src/extensions/wiki-link/to-markdown.js +0 -28
  189. package/dist/src/extensions/wiki-link/types.d.ts +0 -34
  190. package/dist/src/extensions/wiki-link/types.js +0 -1
  191. package/dist/src/external-documents.d.ts +0 -56
  192. package/dist/src/external-documents.js +0 -404
  193. package/dist/src/external-sources.d.ts +0 -136
  194. package/dist/src/external-sources.js +0 -1098
  195. package/dist/src/fork/frontmatter-fields.d.ts +0 -36
  196. package/dist/src/fork/frontmatter-fields.js +0 -23
  197. package/dist/src/format.d.ts +0 -16
  198. package/dist/src/format.js +0 -76
  199. package/dist/src/init-version.d.ts +0 -10
  200. package/dist/src/init-version.js +0 -49
  201. package/dist/src/lattice-model.d.ts +0 -84
  202. package/dist/src/lattice-model.js +0 -467
  203. package/dist/src/lattice.d.ts +0 -14
  204. package/dist/src/lattice.js +0 -353
  205. package/dist/src/markdown-analysis-cache.d.ts +0 -20
  206. package/dist/src/markdown-analysis-cache.js +0 -139
  207. package/dist/src/markdown-analysis-worker.d.ts +0 -16
  208. package/dist/src/markdown-analysis-worker.js +0 -33
  209. package/dist/src/markdown-analysis.d.ts +0 -61
  210. package/dist/src/markdown-analysis.js +0 -169
  211. package/dist/src/markdown-analyzer-loader.d.ts +0 -4
  212. package/dist/src/markdown-analyzer-loader.js +0 -13
  213. package/dist/src/markdown-validation.d.ts +0 -25
  214. package/dist/src/markdown-validation.js +0 -104
  215. package/dist/src/parser-cache.d.ts +0 -17
  216. package/dist/src/parser-cache.js +0 -65
  217. package/dist/src/parser-import.d.ts +0 -8
  218. package/dist/src/parser-import.js +0 -1
  219. package/dist/src/parser.d.ts +0 -3
  220. package/dist/src/parser.js +0 -93
  221. package/dist/src/path.d.ts +0 -2
  222. package/dist/src/path.js +0 -4
  223. package/dist/src/profiler.d.ts +0 -16
  224. package/dist/src/profiler.js +0 -81
  225. package/dist/src/project-analysis.d.ts +0 -59
  226. package/dist/src/project-analysis.js +0 -233
  227. package/dist/src/project-discovery.d.ts +0 -6
  228. package/dist/src/project-discovery.js +0 -30
  229. package/dist/src/repository-path.d.ts +0 -11
  230. package/dist/src/repository-path.js +0 -52
  231. package/dist/src/source-formats.d.ts +0 -7
  232. package/dist/src/source-formats.js +0 -30
  233. package/dist/src/source-parser.d.ts +0 -67
  234. package/dist/src/source-parser.js +0 -1749
  235. package/dist/src/untrusted.d.ts +0 -18
  236. package/dist/src/untrusted.js +0 -93
  237. package/dist/src/view/client/assets/channel-uy0fFvcW.js +0 -1
  238. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-DbSEqjA5.js +0 -1
  239. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-DbSEqjA5.js +0 -1
  240. package/dist/src/view/client/assets/index-nBmbYfun.js +0 -13
  241. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-gUj6Hzgl.js +0 -1
  242. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-DR7JhrxL.js +0 -8
  243. package/dist/src/walk.d.ts +0 -20
  244. package/dist/src/walk.js +0 -121
@@ -1,834 +0,0 @@
1
- import { lstat, rename, unlink, writeFile } from 'node:fs/promises';
2
- import { existsSync } from 'node:fs';
3
- import { basename, dirname, extname, join, relative, resolve } from 'node:path';
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';
10
- import { checkMode } from './check-mode.js';
11
- import { checkStatus } from './check-status.js';
12
- import { checkFrontmatter } from './check-frontmatter.js';
13
- import { checkCoverage } from './check-coverage.js';
14
- import { indexEntryNameFromDest } from './link-scheme.js';
15
- import { fencedLineMask } from './gen-index.js';
16
- import { INIT_VERSION, readInitVersion } from '../init-version.js';
17
- import { CheckRunContext } from './check-context.js';
18
- import { parseLocalMarkdownTarget } from '../markdown-validation.js';
19
- function filePart(id) {
20
- const h = id.indexOf('#');
21
- return h === -1 ? id : id.slice(0, h);
22
- }
23
- /** Format an ambiguous-ref error as structured markdown-like text. */
24
- export function ambiguousRefMessage(target, candidates, suggested) {
25
- const shortName = filePart(target);
26
- const fileList = candidates.map((c) => ` - "${filePart(c)}.md"`).join('\n');
27
- const lines = [];
28
- if (suggested) {
29
- lines.push(`ambiguous link '[[${target}]]' — did you mean '[[${suggested}]]'?`);
30
- }
31
- else {
32
- const options = candidates.map((a) => `'[[${a}]]'`).join(', ');
33
- lines.push(`ambiguous link '[[${target}]]' — multiple paths match, use either of: ${options}`);
34
- }
35
- lines.push(` The short path "${shortName}" is ambiguous — ${candidates.length} files match:`, fileList, ` Please fix the link to use a fully qualified path.`);
36
- return lines.join('\n');
37
- }
38
- async function profileTime(profile, label, work, detail) {
39
- return profile ? profile.time(label, work, detail) : work();
40
- }
41
- function profileTimeSync(profile, label, work, detail) {
42
- return profile ? profile.timeSync(label, work, detail) : work();
43
- }
44
- /**
45
- * Validate an unresolved wiki link as a repository path or source symbol.
46
- * Returns null when valid, otherwise a user-facing error message.
47
- */
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) {
52
- return `broken link [[${target}]] — no matching section found`;
53
- }
54
- const filePart = normalizeRepositoryPath(authoredPath);
55
- if (!filePart) {
56
- return `broken link [[${target}]] — repository path "${authoredPath}" must stay within the project root`;
57
- }
58
- const symbolPart = hashIdx === -1 ? '' : target.slice(hashIdx + 1);
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') {
89
- return `broken link [[${target}]] — file "${filePart}" not found`;
90
- }
91
- if (!symbolPart) {
92
- // Preserve the existing file-level behavior for an empty fragment.
93
- return null;
94
- }
95
- try {
96
- const { resolveSourceSymbol } = await import('../source-parser.js');
97
- const { found, error } = await resolveSourceSymbol(filePart, symbolPart, projectRoot, sourceOptions);
98
- if (error) {
99
- return `broken link [[${target}]] — ${error}`;
100
- }
101
- if (!found) {
102
- return `broken link [[${target}]] — symbol "${symbolPart}" not found in "${filePart}"`;
103
- }
104
- return null;
105
- }
106
- catch (err) {
107
- return `broken link [[${target}]] — failed to parse "${filePart}": ${err instanceof Error ? err.message : String(err)}`;
108
- }
109
- }
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();
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
- }
125
- for (const file of files) {
126
- const refs = await run.refs(file);
127
- const relPath = relative(process.cwd(), file);
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
- }
154
- const { resolved, ambiguous, suggested } = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
155
- if (ambiguous) {
156
- errors.push({
157
- file: relPath,
158
- line: ref.line,
159
- target: ref.target,
160
- message: ambiguousRefMessage(ref.target, ambiguous, suggested),
161
- });
162
- }
163
- else if (!sectionIds.has(resolved.toLowerCase())) {
164
- // Try resolving as a repository path or source symbol.
165
- const sourceErr = await run.resolveRepositoryLink(ref.target, () => repositoryRefError(ref.target, projectRoot, run.sourceSymbolOptions()));
166
- if (sourceErr !== null) {
167
- errors.push({
168
- file: relPath,
169
- line: ref.line,
170
- target: ref.target,
171
- message: sourceErr,
172
- });
173
- }
174
- }
175
- }
176
- }
177
- return { errors };
178
- }
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();
183
- const errors = [];
184
- for (const file of files) {
185
- const links = await run.links(file);
186
- const relPath = toPosix(relative(process.cwd(), file));
187
- for (const diagnostic of await run.diagnostics(file)) {
188
- if (diagnostic.rule !== 'markdown-reference-definition' &&
189
- diagnostic.rule !== 'markdown-path-separator') {
190
- continue;
191
- }
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);
203
- if (target === null)
204
- continue;
205
- if (target.kind === 'invalid-backslash')
206
- continue;
207
- const abs = target.path ? resolve(dirname(file), target.path) : file;
208
- if (!existsSync(abs)) {
209
- const kind = link.kind === 'image' ? 'image' : 'link';
210
- const shown = toPosix(relative(process.cwd(), abs));
211
- errors.push({
212
- file: relPath,
213
- line: link.line,
214
- target: link.url,
215
- message: `broken ${kind} (${link.url}) — file "${shown}" not found`,
216
- });
217
- continue;
218
- }
219
- if (target.fragment &&
220
- extname(abs).toLowerCase() === '.md' &&
221
- link.kind !== 'image') {
222
- const headings = await run.headings(abs);
223
- if (!headings.has(target.fragment)) {
224
- const shown = toPosix(relative(process.cwd(), abs));
225
- errors.push({
226
- file: relPath,
227
- line: link.line,
228
- target: link.url,
229
- message: `broken link (${link.url}) — heading "#${target.fragment}" not found in "${shown}"`,
230
- });
231
- }
232
- }
233
- }
234
- }
235
- return errors;
236
- }
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
- ]);
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
- }
253
- const mentionedSections = new Set();
254
- for (const ref of scan.refs) {
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);
282
- mentionedSections.add(resolved.toLowerCase());
283
- const displayPath = relative(process.cwd(), join(projectRoot, ref.file));
284
- if (ambiguous) {
285
- errors.push({
286
- file: displayPath,
287
- line: ref.line,
288
- target: ref.target,
289
- message: ambiguousRefMessage(ref.target, ambiguous, suggested),
290
- });
291
- }
292
- else if (!sectionIds.has(resolved.toLowerCase())) {
293
- errors.push({
294
- file: displayPath,
295
- line: ref.line,
296
- target: ref.target,
297
- message: `@lat: [[${ref.target}]] — no matching section found`,
298
- });
299
- }
300
- }
301
- const files = await run.markdownFiles();
302
- for (const file of files) {
303
- const fm = await run.frontmatter(file);
304
- if (!fm.requireCodeMention)
305
- continue;
306
- const fileSections = flattenSections(await run.sections(file));
307
- const leafSections = fileSections.filter((section) => section.children.length === 0);
308
- const relPath = relative(process.cwd(), file);
309
- for (const leaf of leafSections) {
310
- if (!mentionedSections.has(leaf.id.toLowerCase())) {
311
- errors.push({
312
- file: relPath,
313
- line: leaf.startLine,
314
- target: leaf.id,
315
- message: `section "${leaf.id}" requires a code mention but none found`,
316
- });
317
- }
318
- }
319
- }
320
- return { errors };
321
- }
322
- /**
323
- * Extract the immediate (first-level) entries from walkEntries results.
324
- * Returns unique file and directory names visible in a given directory.
325
- */
326
- function immediateEntries(walkedPaths) {
327
- const entries = new Set();
328
- for (const p of walkedPaths) {
329
- const slash = p.indexOf('/');
330
- entries.add(slash === -1 ? p : p.slice(0, slash));
331
- }
332
- return [...entries].sort();
333
- }
334
- /**
335
- * Parse bullet items from an index file. Recognizes both the legacy
336
- * hand-written form (`- [[name]] — description`) and the generated form
337
- * written by `lat check --fix` / `lat check index --fix`
338
- * (`- [Title](name) — summary`) — the two do not collide because the second
339
- * only matches when a `(` immediately follows the closing `]`, which never
340
- * happens in a `[[...]]` wiki link.
341
- */
342
- function parseIndexEntries(content) {
343
- const names = new Set();
344
- const lines = content.split('\n');
345
- const fenced = fencedLineMask(lines);
346
- const unfencedContent = lines.map((l, i) => (fenced[i] ? '' : l)).join('\n');
347
- const wikiRe = /^- \[\[([^\]]+?)(?:\|[^\]]+)?\]\]/gm;
348
- let match;
349
- while ((match = wikiRe.exec(unfencedContent)) !== null) {
350
- names.add(match[1]);
351
- }
352
- // Label allows escaped brackets (`\]`, `\[`) without ending the match early.
353
- const mdRe = /^- \[((?:\\.|[^\]])*)\]\(([^)]*)\)/gm;
354
- while ((match = mdRe.exec(unfencedContent)) !== null) {
355
- const name = indexEntryNameFromDest(match[2]);
356
- if (name !== null)
357
- names.add(entryToStem(name));
358
- }
359
- return names;
360
- }
361
- /**
362
- * Convert a filesystem entry name to its wiki link stem.
363
- * Strips `.md` extension from files; directories stay as-is.
364
- */
365
- function entryToStem(name) {
366
- return name.endsWith('.md') ? name.slice(0, -3) : name;
367
- }
368
- /** Generate a bullet-list snippet for the given entry names. */
369
- function indexSnippet(entries) {
370
- return entries.map((e) => `- [[${entryToStem(e)}]] — <describe>`).join('\n');
371
- }
372
- export async function checkIndex(latticeDir, context, opts = {}) {
373
- const run = context ?? new CheckRunContext(latticeDir, dirname(latticeDir));
374
- const errors = [];
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.
398
- for (const p of allPaths) {
399
- const name = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
400
- if (!name.endsWith('.md') &&
401
- p !== 'config.local.yaml' &&
402
- !referencedResources.has(p)) {
403
- const relDir = basename(latticeDir) + '/';
404
- errors.push({
405
- dir: relDir,
406
- message: `"${p}" is not a .md file or a referenced local resource — remove it or link to it from Markdown`,
407
- });
408
- }
409
- }
410
- // Only .md files participate in index validation
411
- const mdPaths = allPaths.filter((p) => p.endsWith('.md'));
412
- // Collect all directories to check (including root, represented as '')
413
- const dirs = new Set(['']);
414
- for (const p of mdPaths) {
415
- const parts = p.split('/');
416
- // Add every directory prefix
417
- for (let i = 1; i < parts.length; i++) {
418
- dirs.add(parts.slice(0, i).join('/'));
419
- }
420
- }
421
- // Deepest directories first: a subdirectory's index must already be
422
- // regenerated before its parent index reads a title/summary from it.
423
- const orderedDirs = [...dirs].sort((a, b) => b.split('/').length - a.split('/').length);
424
- for (const dir of orderedDirs) {
425
- // Determine the index file name and its expected path.
426
- // The index file shares the directory's name — for `lat.md/` it's `lat.md`,
427
- // for a subdir `api/` it's `api.md`.
428
- const dirName = dir === '' ? basename(latticeDir) : dir.split('/').pop();
429
- const indexFileName = dirName.endsWith('.md') ? dirName : dirName + '.md';
430
- const indexRelPath = dir === '' ? indexFileName : dir + '/' + indexFileName;
431
- // Get the immediate children of this directory
432
- const prefix = dir === '' ? '' : dir + '/';
433
- const childPaths = mdPaths
434
- .filter((p) => p.startsWith(prefix) && p !== indexRelPath)
435
- .map((p) => p.slice(prefix.length));
436
- const children = immediateEntries(childPaths);
437
- if (children.length === 0)
438
- continue;
439
- // Check if the index file exists
440
- const indexFullPath = join(latticeDir, indexRelPath);
441
- let content;
442
- try {
443
- content = await run.content(indexFullPath);
444
- }
445
- catch {
446
- content = null;
447
- }
448
- if (content === null) {
449
- if (opts.fix) {
450
- const refusal = await writeGeneratedIndex(latticeDir, dir, indexFullPath, dirName, null, children);
451
- if (refusal)
452
- errors.push(refusal);
453
- continue;
454
- }
455
- const relDir = dir === '' ? basename(latticeDir) + '/' : dir + '/';
456
- errors.push({
457
- dir: relDir,
458
- message: `missing index file "${indexRelPath}" — create it with a directory listing:\n\n${indexSnippet(children)}`,
459
- snippet: indexSnippet(children),
460
- });
461
- continue;
462
- }
463
- // Parse existing entries and validate.
464
- // Listed entries are wiki link stems (no .md extension).
465
- // Children are filesystem names (with .md for files, bare for dirs).
466
- const listed = parseIndexEntries(content);
467
- const childStems = new Set(children.map(entryToStem));
468
- const relDir = dir === '' ? basename(latticeDir) + '/' : dir + '/';
469
- const missing = [];
470
- for (const child of children) {
471
- if (!listed.has(entryToStem(child))) {
472
- missing.push(child);
473
- }
474
- }
475
- const indexStem = entryToStem(indexFileName);
476
- const stale = [];
477
- for (const name of listed) {
478
- if (!childStems.has(name) && name !== indexStem) {
479
- stale.push(name);
480
- }
481
- }
482
- if (missing.length === 0 && stale.length === 0)
483
- continue;
484
- if (opts.fix) {
485
- const refusal = await writeGeneratedIndex(latticeDir, dir, indexFullPath, dirName, content, children);
486
- if (refusal)
487
- errors.push(refusal);
488
- continue;
489
- }
490
- if (missing.length > 0) {
491
- errors.push({
492
- dir: relDir,
493
- message: `"${indexRelPath}" is missing entries — add:\n\n${indexSnippet(missing)}`,
494
- snippet: indexSnippet(missing),
495
- });
496
- }
497
- for (const name of stale) {
498
- errors.push({
499
- dir: relDir,
500
- message: `"${indexRelPath}" lists "[[${name}]]" but it does not exist`,
501
- });
502
- }
503
- }
504
- return errors;
505
- }
506
- /**
507
- * Build entry sources for a directory's children and write the generated
508
- * index. Returns an IndexError describing a refusal if the index path (or
509
- * the temporary path used to write it) is a symlink; returns null on
510
- * success.
511
- *
512
- * The write never follows a symlink: it writes to a temp file in the same
513
- * directory (created with O_EXCL, so a pre-planted symlink there is refused
514
- * rather than written through) and renames it over the target, since rename
515
- * replaces a symlink rather than following it.
516
- */
517
- async function writeGeneratedIndex(latticeDir, dir, indexFullPath, dirName, existingContent, children) {
518
- const relIndexPath = relative(latticeDir, indexFullPath);
519
- const relDir = dir === '' ? basename(latticeDir) + '/' : dir + '/';
520
- try {
521
- const stat = await lstat(indexFullPath);
522
- if (stat.isSymbolicLink()) {
523
- return {
524
- dir: relDir,
525
- message: `refusing to write generated index "${relIndexPath}" — it is a symlink`,
526
- };
527
- }
528
- }
529
- catch {
530
- // Does not exist yet — fine, we're creating it.
531
- }
532
- const { renderIndexEntries, spliceIndexContent } = await import('./gen-index.js');
533
- const entries = children.map((child) => {
534
- const isDir = !child.endsWith('.md');
535
- const dest = isDir ? `${child}/${child}.md` : child;
536
- const readFrom = isDir
537
- ? join(latticeDir, dir, child, `${child}.md`)
538
- : join(latticeDir, dir, child);
539
- return { name: child, dest, readFrom };
540
- });
541
- const rendered = await renderIndexEntries(entries);
542
- const dirLabel = dirName.endsWith('.md') ? dirName.slice(0, -3) : dirName;
543
- const spliced = spliceIndexContent(existingContent, dirLabel, rendered);
544
- if (!spliced.ok) {
545
- return {
546
- dir: relDir,
547
- message: `refusing to write generated index "${relIndexPath}" — ${spliced.message}`,
548
- };
549
- }
550
- const newContent = spliced.content;
551
- const tempPath = `${indexFullPath}.tmp`;
552
- try {
553
- const tempStat = await lstat(tempPath);
554
- if (tempStat.isSymbolicLink() || tempStat.isFile()) {
555
- // Something is already at the temp path (possibly a pre-planted
556
- // symlink). Refuse rather than write through or over it.
557
- //
558
- // This also catches a leftover temp file from a run killed between the
559
- // write and the rename, which would otherwise block --fix for this
560
- // directory forever. That case is harmless to clear, but the reader
561
- // cannot tell the two apart from here, so say what to do rather than
562
- // deleting a file this command was never asked to remove.
563
- return {
564
- dir: relDir,
565
- message: `refusing to write generated index "${relIndexPath}" — its temporary write path "${relative(latticeDir, tempPath)}" already exists. Inspect it, then delete it to allow --fix to proceed.`,
566
- };
567
- }
568
- }
569
- catch {
570
- // Nothing at the temp path — fine.
571
- }
572
- try {
573
- // 'wx' is the load-bearing flag, not a detail: it fails rather than
574
- // follows if anything appeared at the temp path since the lstat above,
575
- // which closes the window between the two checks.
576
- await writeFile(tempPath, newContent, { encoding: 'utf-8', flag: 'wx' });
577
- }
578
- catch (err) {
579
- return {
580
- dir: relDir,
581
- message: `refusing to write generated index "${relIndexPath}" — could not create temporary write path "${relative(latticeDir, tempPath)}" (${err.message})`,
582
- };
583
- }
584
- try {
585
- await rename(tempPath, indexFullPath);
586
- }
587
- catch (err) {
588
- await unlink(tempPath).catch(() => { });
589
- throw err;
590
- }
591
- return null;
592
- }
593
- // --- Section structure validation ---
594
- export async function checkSections(latticeDir, projectRoot = dirname(latticeDir), context) {
595
- const run = context ?? new CheckRunContext(latticeDir, projectRoot);
596
- const files = await run.markdownFiles();
597
- const errors = [];
598
- for (const file of files) {
599
- const relPath = relative(process.cwd(), file);
600
- for (const diagnostic of await run.diagnostics(file)) {
601
- if (diagnostic.rule !== 'section-leading-paragraph')
602
- continue;
603
- errors.push({
604
- file: relPath,
605
- line: diagnostic.line,
606
- target: diagnostic.target,
607
- message: diagnostic.message,
608
- });
609
- }
610
- }
611
- return errors;
612
- }
613
- // --- Formatting helpers (shared by all check commands) ---
614
- function formatCheckErrors(errors, s) {
615
- const lines = [];
616
- for (const err of errors) {
617
- lines.push('');
618
- const loc = s.cyan(err.file + ':' + err.line);
619
- const [first, ...rest] = err.message.split('\n');
620
- lines.push(`- ${loc}: ${s.red(first)}`);
621
- for (const line of rest) {
622
- lines.push(` ${s.red(line)}`);
623
- }
624
- }
625
- return lines;
626
- }
627
- function formatCheckIndexErrors(errors, s) {
628
- const lines = [];
629
- for (const err of errors) {
630
- lines.push('');
631
- const loc = s.cyan(err.dir);
632
- const [first, ...rest] = err.message.split('\n');
633
- lines.push(`- ${loc}: ${s.red(first)}`);
634
- for (const line of rest) {
635
- lines.push(` ${s.red(line)}`);
636
- }
637
- }
638
- return lines;
639
- }
640
- function formatErrorCount(count, s) {
641
- return s.red(`\n${count} error${count === 1 ? '' : 's'} found`);
642
- }
643
- export async function checkAllCommand(ctx, options = {}) {
644
- const startTime = performance.now();
645
- const profile = options.profile ? new TimingProfiler() : undefined;
646
- // `--fix` is index-only: an index is derived from frontmatter, so it can be
647
- // regenerated. Nothing else `lat check` reports has a mechanical fix.
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(),
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) : [];
686
- // Init version warning first — user should fix setup before addressing errors
687
- if (!ctx.headless) {
688
- const storedVersion = readInitVersion(ctx.latDir);
689
- if (storedVersion === null) {
690
- if (lines.length > 0)
691
- lines.push('');
692
- lines.push(s.yellow('Warning:') +
693
- ' No init version recorded — run ' +
694
- s.cyan('lat init') +
695
- ' to set up agent hooks and configuration.');
696
- }
697
- else if (storedVersion < INIT_VERSION) {
698
- if (lines.length > 0)
699
- lines.push('');
700
- lines.push(s.yellow('Warning:') +
701
- ' Your setup is outdated (v' +
702
- storedVersion +
703
- ' → v' +
704
- INIT_VERSION +
705
- '). Re-run ' +
706
- s.cyan('lat init') +
707
- ' to update agent hooks and configuration.');
708
- }
709
- }
710
- lines.push(...formatCheckErrors(allErrors, s));
711
- lines.push(...formatCheckIndexErrors(indexErrors, s));
712
- lines.push(...formatCheckErrors(sectionErrors, s));
713
- lines.push(...formatCheckErrors(modeErrors, s));
714
- lines.push(...formatCheckErrors(statusErrors, s));
715
- lines.push(...formatCheckErrors(fmErrors, s));
716
- lines.push(...formatCheckErrors(coverageErrors, s));
717
- const totalErrors = allErrors.length +
718
- indexErrors.length +
719
- sectionErrors.length +
720
- modeErrors.length +
721
- statusErrors.length +
722
- fmErrors.length +
723
- coverageErrors.length;
724
- if (totalErrors > 0) {
725
- lines.push(formatErrorCount(totalErrors, s));
726
- return { output: lines.join('\n'), isError: true };
727
- }
728
- lines.push(s.green(`All checks passed in ${elapsedStr}`));
729
- // Suggest ripgrep if check was slow (>1s) and rg is not available
730
- if (elapsed > 1000) {
731
- const { hasRipgrep } = await import('../code-refs.js');
732
- if (!(await hasRipgrep())) {
733
- lines.push(s.yellow('Tip:') +
734
- ' Install ' +
735
- s.cyan('ripgrep') +
736
- ' (rg) for faster code scanning.' +
737
- ' See https://github.com/BurntSushi/ripgrep#installation');
738
- }
739
- }
740
- return { output: lines.join('\n') };
741
- }
742
- export async function checkMdCommand(ctx) {
743
- const { errors } = await checkMd(ctx.latDir, ctx.projectRoot);
744
- const s = ctx.styler;
745
- const lines = [];
746
- lines.push(...formatCheckErrors(errors, s));
747
- if (errors.length > 0) {
748
- lines.push(formatErrorCount(errors.length, s));
749
- return { output: lines.join('\n'), isError: true };
750
- }
751
- lines.push(s.green('md: All links OK'));
752
- return { output: lines.join('\n') };
753
- }
754
- export async function checkLinksCommand(ctx) {
755
- const errors = await checkLinks(ctx.latDir);
756
- const s = ctx.styler;
757
- const lines = [];
758
- lines.push(...formatCheckErrors(errors, s));
759
- if (errors.length > 0) {
760
- lines.push(formatErrorCount(errors.length, s));
761
- return { output: lines.join('\n'), isError: true };
762
- }
763
- lines.push(s.green('links: All relative links resolve'));
764
- return { output: lines.join('\n') };
765
- }
766
- export async function checkCodeRefsCommand(ctx) {
767
- const { errors } = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
768
- const s = ctx.styler;
769
- const lines = [];
770
- lines.push(...formatCheckErrors(errors, s));
771
- if (errors.length > 0) {
772
- lines.push(formatErrorCount(errors.length, s));
773
- return { output: lines.join('\n'), isError: true };
774
- }
775
- lines.push(s.green('code-refs: All references OK'));
776
- return { output: lines.join('\n') };
777
- }
778
- export async function checkIndexCommand(ctx, opts = {}) {
779
- const errors = await checkIndex(ctx.latDir, undefined, opts);
780
- const s = ctx.styler;
781
- const lines = [];
782
- if (opts.fix) {
783
- lines.push(s.green('index: directory index files regenerated'));
784
- if (errors.length > 0) {
785
- lines.push(...formatCheckIndexErrors(errors, s));
786
- lines.push(formatErrorCount(errors.length, s));
787
- return { output: lines.join('\n'), isError: true };
788
- }
789
- return { output: lines.join('\n') };
790
- }
791
- lines.push(...formatCheckIndexErrors(errors, s));
792
- if (errors.length > 0) {
793
- lines.push(formatErrorCount(errors.length, s));
794
- return { output: lines.join('\n'), isError: true };
795
- }
796
- lines.push(s.green('index: All directory index files OK'));
797
- return { output: lines.join('\n') };
798
- }
799
- export async function checkSectionsCommand(ctx) {
800
- const errors = await checkSections(ctx.latDir, ctx.projectRoot);
801
- const s = ctx.styler;
802
- const lines = [];
803
- lines.push(...formatCheckErrors(errors, s));
804
- if (errors.length > 0) {
805
- lines.push(formatErrorCount(errors.length, s));
806
- return { output: lines.join('\n'), isError: true };
807
- }
808
- lines.push(s.green('sections: All sections have valid leading paragraphs'));
809
- return { output: lines.join('\n') };
810
- }
811
- export async function checkModeCommand(ctx) {
812
- const errors = await checkMode(ctx.latDir, ctx.projectRoot);
813
- const s = ctx.styler;
814
- const lines = [];
815
- lines.push(...formatCheckErrors(errors, s));
816
- if (errors.length > 0) {
817
- lines.push(formatErrorCount(errors.length, s));
818
- return { output: lines.join('\n'), isError: true };
819
- }
820
- lines.push(s.green('mode: All documents match their Diátaxis mode'));
821
- return { output: lines.join('\n') };
822
- }
823
- export async function checkStatusCommand(ctx) {
824
- const errors = await checkStatus(ctx.latDir, ctx.projectRoot);
825
- const s = ctx.styler;
826
- const lines = [];
827
- lines.push(...formatCheckErrors(errors, s));
828
- if (errors.length > 0) {
829
- lines.push(formatErrorCount(errors.length, s));
830
- return { output: lines.join('\n'), isError: true };
831
- }
832
- lines.push(s.green('status: Every recorded review matches its document'));
833
- return { output: lines.join('\n') };
834
- }