@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,394 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { basename, dirname, join, relative } from 'node:path';
3
- import { listLatticeFiles, parseFrontmatter } from '../lattice.js';
4
- import { parse } from '../parser.js';
5
- import { quoteUntrusted } from '../untrusted.js';
6
- import { toPosix } from '../path.js';
7
- export const DIATAXIS_MODES = [
8
- 'tutorial',
9
- 'how-to',
10
- 'reference',
11
- 'explanation',
12
- ];
13
- /** Directory below lat.md/ that holds each mode. */
14
- export const MODE_DIRS = {
15
- tutorial: 'tutorials',
16
- 'how-to': 'how-to',
17
- reference: 'reference',
18
- explanation: 'explanation',
19
- };
20
- /** Reverse lookup: first path segment -> mode. Built once, not per file. */
21
- // Null-prototype: the key is a directory name read off disk.
22
- const DIR_TO_MODE = Object.create(null);
23
- for (const mode of DIATAXIS_MODES) {
24
- DIR_TO_MODE[MODE_DIRS[mode]] = mode;
25
- }
26
- const OUTCOME_RE = /\b(outcome|by the end|you will (build|learn|have|create)|what you (will )?(build|learn|have|create))\b/i;
27
- /**
28
- * Imperative verbs that flag a command inside an explanation document.
29
- * Deliberately excludes ambiguous verbs (Use, Go, Make, Type, Enter, Call,
30
- * Import, Export) — each reads naturally as a noun or as descriptive prose
31
- * at the start of a sentence ("Use cases include...", "Type inference..."),
32
- * and including them produced false positives in testing.
33
- */
34
- const IMPERATIVE_VERBS = [
35
- 'Run',
36
- 'Install',
37
- 'Add',
38
- 'Set',
39
- 'Open',
40
- 'Create',
41
- 'Delete',
42
- 'Edit',
43
- 'Copy',
44
- 'Move',
45
- 'Click',
46
- 'Press',
47
- 'Configure',
48
- 'Update',
49
- 'Remove',
50
- 'Execute',
51
- 'Choose',
52
- 'Select',
53
- 'Replace',
54
- 'Rename',
55
- 'Restart',
56
- 'Deploy',
57
- ];
58
- const MAX_IMPERATIVE_ERRORS = 10;
59
- function orderedListPresent(tree) {
60
- let found = false;
61
- const walk = (nodes) => {
62
- for (const node of nodes) {
63
- if (node.type === 'list' && node.ordered === true) {
64
- found = true;
65
- return;
66
- }
67
- if ('children' in node) {
68
- walk(node.children);
69
- }
70
- if (found)
71
- return;
72
- }
73
- };
74
- walk(tree.children);
75
- return found;
76
- }
77
- function outcomeStated(tree) {
78
- for (const node of tree.children) {
79
- if (node.type === 'heading' || node.type === 'paragraph') {
80
- const text = inlineTextOf(node);
81
- if (OUTCOME_RE.test(text))
82
- return true;
83
- }
84
- }
85
- return false;
86
- }
87
- function inlineTextOf(node) {
88
- return node.children
89
- .map((c) => {
90
- if ('value' in c && typeof c.value === 'string')
91
- return c.value;
92
- if ('children' in c)
93
- return inlineTextOf(c);
94
- return '';
95
- })
96
- .join('');
97
- }
98
- /**
99
- * A GFM table that the parser handed back as a paragraph.
100
- *
101
- * `src/parser.ts` runs remark without `remark-gfm`, so a pipe table is never a
102
- * `table` node — its rows arrive as one paragraph and every reference document
103
- * containing a table failed with "this is a second paragraph", while the error
104
- * text told the author to use a table. Adding the plugin would mean editing an
105
- * upstream file and changing what `remarkStringify` emits for the whole tool,
106
- * which `tests/roundtrip` pins; recognising the shape here costs nothing
107
- * outside this check.
108
- *
109
- * The signature is a delimiter row — `| --- | :--: |` — under a line that opens
110
- * a row. Both are required, because a delimiter row is what separates a table
111
- * from prose that merely contains pipes.
112
- */
113
- function isPipeTable(node, lines) {
114
- const start = node.position?.start.line;
115
- const end = node.position?.end.line;
116
- if (!start || !end || end - start < 1)
117
- return false;
118
- const rows = lines.slice(start - 1, end);
119
- return rows.some((row, i) => i > 0 &&
120
- /^\s*\|?(?:\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$/.test(row) &&
121
- rows[i - 1].trimStart().startsWith('|'));
122
- }
123
- /**
124
- * Extra top-level paragraphs found under headings, beyond the one
125
- * `checkSections` already permits as the leading summary. A paragraph
126
- * before the first heading is the document lead, not narrative prose.
127
- *
128
- * A pipe table is not prose and never counts, in either position: it is
129
- * neither the leading summary a reference is allowed nor an offender.
130
- */
131
- function extraParagraphs(tree, lines) {
132
- const offenders = [];
133
- let currentHeading = null;
134
- let seenParagraphUnderHeading = false;
135
- for (const node of tree.children) {
136
- if (node.type === 'heading') {
137
- currentHeading = inlineTextOf(node);
138
- seenParagraphUnderHeading = false;
139
- continue;
140
- }
141
- if (node.type === 'paragraph') {
142
- if (isPipeTable(node, lines))
143
- continue;
144
- if (currentHeading === null) {
145
- // Document lead paragraph before any heading — allowed.
146
- continue;
147
- }
148
- if (!seenParagraphUnderHeading) {
149
- seenParagraphUnderHeading = true;
150
- }
151
- else {
152
- offenders.push({
153
- line: node.position.start.line,
154
- heading: currentHeading,
155
- });
156
- }
157
- }
158
- }
159
- return offenders;
160
- }
161
- /**
162
- * Line numbers (1-indexed, inclusive of both ends) that belong to a `code`
163
- * or `yaml` (frontmatter) node anywhere in the tree. Walking the parsed tree
164
- * — rather than re-scanning raw text with a fence regex — means a fence of
165
- * any marker length or an indented code block is recognized correctly, and
166
- * frontmatter needs no special case: it simply isn't prose in the tree.
167
- */
168
- function nonProseLines(tree) {
169
- const lines = new Set();
170
- const markRange = (node) => {
171
- if (!node.position)
172
- return;
173
- for (let line = node.position.start.line; line <= node.position.end.line; line++) {
174
- lines.add(line);
175
- }
176
- };
177
- const walk = (nodes) => {
178
- for (const node of nodes) {
179
- if (node.type === 'code' || node.type === 'yaml') {
180
- markRange(node);
181
- continue;
182
- }
183
- if ('children' in node) {
184
- walk(node.children);
185
- }
186
- }
187
- };
188
- walk(tree.children);
189
- return lines;
190
- }
191
- function findImperativeHits(tree, content) {
192
- const hits = [];
193
- const lines = content.split('\n');
194
- const skipLines = nonProseLines(tree);
195
- for (let i = 0; i < lines.length; i++) {
196
- const lineNumber = i + 1;
197
- if (skipLines.has(lineNumber))
198
- continue;
199
- const trimmedStart = lines[i].trimStart();
200
- if (trimmedStart.startsWith('#'))
201
- continue;
202
- let text = trimmedStart;
203
- text = text.replace(/^(?:[-*+]\s+|\d+[.)]\s+)/, '');
204
- text = text.replace(/^\*\*/, '');
205
- for (const verb of IMPERATIVE_VERBS) {
206
- if (text.startsWith(verb + ' ')) {
207
- hits.push({ line: lineNumber, verb });
208
- break;
209
- }
210
- }
211
- }
212
- return hits;
213
- }
214
- /**
215
- * The index file a directory is required to carry, e.g. `lat.md` for the root
216
- * and `reference.md` for `reference/`. An index is navigation rather than
217
- * content, so the `require-mode` gate below never asks one to declare a mode.
218
- */
219
- export function indexNameFor(dirName) {
220
- return dirName.endsWith('.md') ? dirName : dirName + '.md';
221
- }
222
- /**
223
- * What the root index says about the mode requirement.
224
- *
225
- * Opt-in, and deliberately read from the tree rather than from a flag or an
226
- * environment variable: the rule belongs to a documentation set, not to a
227
- * machine or a CI job, and a tree that predates the rule has to keep passing.
228
- * `lat init` stamps the flag into the root index it scaffolds, so a project
229
- * set up after this exists is gated from its first commit while an older one
230
- * is untouched until someone adds the line.
231
- *
232
- * The raw value is returned rather than a boolean so the caller can tell an
233
- * absent flag from one that was set to something this never reads. `yes` and
234
- * `1` are strings to a YAML 1.2 parser, and enforcing nothing on them while
235
- * saying nothing about them is how a project ends up believing the gate is on.
236
- */
237
- async function readRequireModeFlag(latticeDir) {
238
- const indexPath = join(latticeDir, indexNameFor(basename(latticeDir)));
239
- try {
240
- const content = await readFile(indexPath, 'utf-8');
241
- return parseFrontmatter(content).raw['require-mode'];
242
- }
243
- catch {
244
- return undefined;
245
- }
246
- }
247
- export async function checkMode(latticeDir, projectRoot = dirname(latticeDir), options = {}) {
248
- const files = await listLatticeFiles(latticeDir);
249
- const errors = [];
250
- const flag = await readRequireModeFlag(latticeDir);
251
- const requireMode = options.requireMode ?? flag === true;
252
- if (flag !== undefined && typeof flag !== 'boolean') {
253
- const indexPath = join(latticeDir, indexNameFor(basename(latticeDir)));
254
- errors.push({
255
- file: relative(process.cwd(), indexPath),
256
- line: 1,
257
- target: toPosix(relative(projectRoot, indexPath)).replace(/\.md$/, ''),
258
- message: `require-mode is ${JSON.stringify(flag)} — it must be true or false.\n Anything else is enforced as off, so the gate this line looks like it turns on is not running.`,
259
- });
260
- }
261
- for (const file of files) {
262
- const content = await readFile(file, 'utf-8');
263
- const relPath = relative(process.cwd(), file);
264
- const target = toPosix(relative(projectRoot, file)).replace(/\.md$/, '');
265
- const fm = parseFrontmatter(content);
266
- const declaredRaw = fm.raw['mode'];
267
- // null (bare `mode:` key) is treated as absent; any other non-string is
268
- // reported below via declaredNonString.
269
- const declared = declaredRaw === null || declaredRaw === undefined
270
- ? undefined
271
- : typeof declaredRaw === 'string'
272
- ? declaredRaw
273
- : undefined;
274
- const declaredNonString = declaredRaw !== null &&
275
- declaredRaw !== undefined &&
276
- typeof declaredRaw !== 'string'
277
- ? declaredRaw
278
- : undefined;
279
- const relFromLattice = toPosix(relative(latticeDir, file));
280
- const firstSegment = relFromLattice.split('/')[0];
281
- const dirMode = firstSegment && firstSegment !== relFromLattice
282
- ? DIR_TO_MODE[firstSegment]
283
- : undefined;
284
- if (declaredNonString !== undefined) {
285
- errors.push({
286
- file: relPath,
287
- line: 1,
288
- target,
289
- message: `document declares unknown mode ${quoteUntrusted(JSON.stringify(declaredNonString), 40)} — use one of: tutorial, how-to, reference, explanation`,
290
- });
291
- continue;
292
- }
293
- if (declared !== undefined &&
294
- !DIATAXIS_MODES.includes(declared)) {
295
- errors.push({
296
- file: relPath,
297
- line: 1,
298
- target,
299
- message: `document declares unknown mode ${quoteUntrusted(declared, 40)} — use one of: tutorial, how-to, reference, explanation`,
300
- });
301
- continue;
302
- }
303
- if (declared !== undefined &&
304
- dirMode !== undefined &&
305
- declared !== dirMode) {
306
- errors.push({
307
- file: relPath,
308
- line: 1,
309
- target,
310
- message: `document is in lat.md/${firstSegment}/ but declares mode: ${declared} — the mode must match the directory that holds it`,
311
- });
312
- continue;
313
- }
314
- const mode = (declared ?? dirMode);
315
- if (mode === undefined) {
316
- const segments = relFromLattice.split('/');
317
- const fileName = segments.pop();
318
- const holder = segments.length === 0 ? basename(latticeDir) : segments.at(-1);
319
- const isIndex = fileName === indexNameFor(holder);
320
- if (requireMode && !isIndex) {
321
- errors.push({
322
- file: relPath,
323
- line: 1,
324
- target,
325
- message: 'document declares no Diátaxis mode and is not in a mode directory — move it under lat.md/tutorials/, lat.md/how-to/, lat.md/reference/ or lat.md/explanation/, or declare one under its `lat:` frontmatter.\n A document that would fail its mode is usually two documents: the lookup half belongs in reference/, the reasoning half in explanation/.\n (The root index sets require-mode: true. Remove that line to turn this off.)',
326
- });
327
- }
328
- continue;
329
- }
330
- const tree = parse(content);
331
- if (mode === 'how-to') {
332
- if (!orderedListPresent(tree)) {
333
- errors.push({
334
- file: relPath,
335
- line: 1,
336
- target,
337
- message: `a how-to must give ordered steps — add a numbered list`,
338
- });
339
- }
340
- continue;
341
- }
342
- if (mode === 'tutorial') {
343
- if (!orderedListPresent(tree)) {
344
- errors.push({
345
- file: relPath,
346
- line: 1,
347
- target,
348
- message: `a tutorial must give ordered steps — add a numbered list`,
349
- });
350
- }
351
- if (!outcomeStated(tree)) {
352
- errors.push({
353
- file: relPath,
354
- line: 1,
355
- target,
356
- message: `a tutorial must state its outcome — add a heading or sentence saying what the reader will have at the end`,
357
- });
358
- }
359
- continue;
360
- }
361
- if (mode === 'reference') {
362
- for (const offender of extraParagraphs(tree, content.split('\n'))) {
363
- errors.push({
364
- file: relPath,
365
- line: offender.line,
366
- target,
367
- message: `a reference must not contain narrative prose — this is a second paragraph under "${offender.heading}"; use a list, a table, or a code block instead`,
368
- });
369
- }
370
- continue;
371
- }
372
- if (mode === 'explanation') {
373
- const hits = findImperativeHits(tree, content);
374
- const capped = hits.slice(0, MAX_IMPERATIVE_ERRORS);
375
- for (const hit of capped) {
376
- errors.push({
377
- file: relPath,
378
- line: hit.line,
379
- target,
380
- message: `an explanation must not give commands — line starts with the imperative "${hit.verb}"; describe what happens instead, or move this to a how-to`,
381
- });
382
- }
383
- if (hits.length > MAX_IMPERATIVE_ERRORS) {
384
- errors.push({
385
- file: relPath,
386
- line: 1,
387
- target,
388
- message: `capped at ${MAX_IMPERATIVE_ERRORS} imperative-command errors for this document — fix these first, then re-run`,
389
- });
390
- }
391
- }
392
- }
393
- return errors;
394
- }
@@ -1,74 +0,0 @@
1
- import type { Styler } from '../context.js';
2
- import type { CheckError } from './check.js';
3
- /**
4
- * Who wrote a document, and whether a person has checked it.
5
- *
6
- * lat.md states that agents write the graph. Without this field an unmarked
7
- * claim written by an agent reaches the model carrying exactly the authority
8
- * of one a person read and approved.
9
- */
10
- export declare const DOC_STATUSES: readonly ["human-reviewed", "agent-extracted"];
11
- export type DocStatus = (typeof DOC_STATUSES)[number];
12
- export type DocProvenance = {
13
- /** Raw `status` value, which may not be one this build understands. */
14
- status?: string;
15
- /** Raw `reviewed-hash` value, which may not be hex. */
16
- reviewedHash?: string;
17
- };
18
- /**
19
- * Read the provenance fields out of a document's frontmatter.
20
- *
21
- * Fields live under the top-level `lat:` mapping, so they are read off
22
- * `LatFrontmatter.raw` rather than being promoted to named fields. Adding a
23
- * named field would mean editing `parseFrontmatter`, and keeping that function
24
- * closed to per-feature edits is the whole reason `raw` exists.
25
- */
26
- export declare function readProvenance(content: string): DocProvenance;
27
- /**
28
- * Hash the prose a review actually covered.
29
- *
30
- * A plain `status` field becomes a lie the moment an agent edits the document:
31
- * the field still says a person checked the text, but the text is no longer
32
- * the text that person read. Capturing the content at review time turns that
33
- * silent failure into a check.
34
- *
35
- * Frontmatter is excluded deliberately: adding an unrelated frontmatter key
36
- * does not change a single claim in the prose, and invalidating a review for
37
- * it would train authors to re-stamp the hash without re-reading — which
38
- * costs the field its meaning.
39
- *
40
- * Heading TEXT is excluded for the same reason — re-titling a section changes
41
- * no claim in the prose. But a heading LINE is not: adding or removing a
42
- * heading changes the document's structure (a new section exists that no one
43
- * has reviewed), and that must flip the hash even though no non-heading line
44
- * changed. So each heading line is reduced to its `#` marker (which carries
45
- * its level and position) rather than dropped outright — retitling a section
46
- * leaves the marker alone, but appending or removing a heading changes the
47
- * sequence of markers and therefore the hash. (Swapping two headings of the
48
- * same level produces the same marker sequence and is not detected — a
49
- * narrower version of the blind spot this fix closes.)
50
- */
51
- export declare function hashReviewedBody(content: string): string;
52
- export type ProvenanceNote = {
53
- kind: 'unreviewed' | 'stale' | 'reviewed';
54
- text: string;
55
- };
56
- /**
57
- * The one-line provenance annotation for `lat section` and `lat search`, or
58
- * null when the document says nothing about its provenance.
59
- *
60
- * A document with no `status` produces nothing at all. Most documents in most
61
- * repositories have no status, and a line on every one of them would be noise
62
- * that readers learn to skip — which would cost the warning its force on the
63
- * documents that do carry one.
64
- */
65
- export declare function provenanceNote(content: string): ProvenanceNote | null;
66
- /** Colourize a note for terminal output. */
67
- export declare function formatProvenanceNote(note: ProvenanceNote, s: Styler): string;
68
- /**
69
- * Report documents whose recorded review no longer describes their text.
70
- *
71
- * This is a static validation. It needs no history and no database, and it
72
- * catches most of what a temporal graph would answer.
73
- */
74
- export declare function checkStatus(latticeDir: string, projectRoot?: string): Promise<CheckError[]>;
@@ -1,217 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { readFile } from 'node:fs/promises';
3
- import { dirname, relative } from 'node:path';
4
- import { listLatticeFiles, parseFrontmatter } from '../lattice.js';
5
- import { toPosix } from '../path.js';
6
- import { quoteUntrusted } from '../untrusted.js';
7
- import { fencedLineMask } from './gen-index.js';
8
- /**
9
- * Who wrote a document, and whether a person has checked it.
10
- *
11
- * lat.md states that agents write the graph. Without this field an unmarked
12
- * claim written by an agent reaches the model carrying exactly the authority
13
- * of one a person read and approved.
14
- */
15
- export const DOC_STATUSES = ['human-reviewed', 'agent-extracted'];
16
- /**
17
- * The shortest `reviewed-hash` prefix that is accepted as a match. A full
18
- * SHA-256 is 64 hex characters and unpleasant to keep in frontmatter, so an
19
- * author may record an abbreviated form. Eight characters is 32 bits — far
20
- * past accidental collision for the handful of revisions one document sees,
21
- * and this value is a staleness signal, not a security boundary.
22
- */
23
- const MIN_HASH_PREFIX = 8;
24
- const HEX = /^[0-9a-f]+$/i;
25
- /**
26
- * Read the provenance fields out of a document's frontmatter.
27
- *
28
- * Fields live under the top-level `lat:` mapping, so they are read off
29
- * `LatFrontmatter.raw` rather than being promoted to named fields. Adding a
30
- * named field would mean editing `parseFrontmatter`, and keeping that function
31
- * closed to per-feature edits is the whole reason `raw` exists.
32
- */
33
- export function readProvenance(content) {
34
- const raw = parseFrontmatter(content).raw;
35
- const result = {};
36
- const status = raw['status'];
37
- if (typeof status === 'string')
38
- result.status = status;
39
- const hash = raw['reviewed-hash'];
40
- if (typeof hash === 'string')
41
- result.reviewedHash = hash;
42
- else if (typeof hash === 'number')
43
- result.reviewedHash = String(hash);
44
- return result;
45
- }
46
- /**
47
- * Hash the prose a review actually covered.
48
- *
49
- * A plain `status` field becomes a lie the moment an agent edits the document:
50
- * the field still says a person checked the text, but the text is no longer
51
- * the text that person read. Capturing the content at review time turns that
52
- * silent failure into a check.
53
- *
54
- * Frontmatter is excluded deliberately: adding an unrelated frontmatter key
55
- * does not change a single claim in the prose, and invalidating a review for
56
- * it would train authors to re-stamp the hash without re-reading — which
57
- * costs the field its meaning.
58
- *
59
- * Heading TEXT is excluded for the same reason — re-titling a section changes
60
- * no claim in the prose. But a heading LINE is not: adding or removing a
61
- * heading changes the document's structure (a new section exists that no one
62
- * has reviewed), and that must flip the hash even though no non-heading line
63
- * changed. So each heading line is reduced to its `#` marker (which carries
64
- * its level and position) rather than dropped outright — retitling a section
65
- * leaves the marker alone, but appending or removing a heading changes the
66
- * sequence of markers and therefore the hash. (Swapping two headings of the
67
- * same level produces the same marker sequence and is not detected — a
68
- * narrower version of the blind spot this fix closes.)
69
- */
70
- export function hashReviewedBody(content) {
71
- // Normalize line endings BEFORE stripping frontmatter: the delimiter regex
72
- // is written with bare \n, so on a CRLF-saved document it would not match
73
- // and the whole frontmatter block — reviewed-hash included — would be
74
- // hashed as body, making every such review spuriously stale.
75
- const normalized = content.replace(/\r\n/g, '\n');
76
- const withoutFrontmatter = normalized.replace(/^---\n[\s\S]*?\n---\n?/, '');
77
- const lines = withoutFrontmatter.split('\n');
78
- // A `#`-prefixed line inside a fenced code block is a shell comment, not a
79
- // heading. Collapsing it would hand an editor a free, unhashed rewrite slot
80
- // in every fenced example. The same mask the index reader and writer share.
81
- const fenced = fencedLineMask(lines);
82
- const body = lines
83
- .map((line, i) => {
84
- if (fenced[i])
85
- return line;
86
- const heading = /^(#{1,6})\s/.exec(line);
87
- return heading ? heading[1] : line;
88
- })
89
- // Trailing whitespace is invisible in a diff and must not flip a review.
90
- .map((line) => line.replace(/[ \t]+$/, ''))
91
- .join('\n')
92
- .trim();
93
- return createHash('sha256').update(body, 'utf-8').digest('hex');
94
- }
95
- function hashMatches(recorded, actual) {
96
- const want = recorded.trim().toLowerCase();
97
- if (want.length < MIN_HASH_PREFIX || !HEX.test(want))
98
- return false;
99
- return actual.startsWith(want);
100
- }
101
- /**
102
- * The one-line provenance annotation for `lat section` and `lat search`, or
103
- * null when the document says nothing about its provenance.
104
- *
105
- * A document with no `status` produces nothing at all. Most documents in most
106
- * repositories have no status, and a line on every one of them would be noise
107
- * that readers learn to skip — which would cost the warning its force on the
108
- * documents that do carry one.
109
- */
110
- export function provenanceNote(content) {
111
- const { status, reviewedHash } = readProvenance(content);
112
- if (status === undefined)
113
- return null;
114
- if (status === 'agent-extracted') {
115
- return {
116
- kind: 'unreviewed',
117
- text: '[unreviewed -- written by an agent, not checked by a person]',
118
- };
119
- }
120
- if (status === 'human-reviewed') {
121
- // No hash means an older tree that predates the field. Saying nothing is
122
- // deliberate: an upgrade must not repaint an existing graph with warnings.
123
- if (reviewedHash === undefined) {
124
- return { kind: 'reviewed', text: '[human-reviewed]' };
125
- }
126
- if (hashMatches(reviewedHash, hashReviewedBody(content))) {
127
- return { kind: 'reviewed', text: '[human-reviewed]' };
128
- }
129
- return {
130
- kind: 'stale',
131
- text: '[stale review -- the text changed after a person checked it]',
132
- };
133
- }
134
- // An unknown value is repository text and is quoted as such. `lat check
135
- // status` reports it; the reader is told only that it means nothing here.
136
- return {
137
- kind: 'unreviewed',
138
- text: `[unrecognized status ${quoteUntrusted(status, 40)} -- treat as unreviewed]`,
139
- };
140
- }
141
- /** Colourize a note for terminal output. */
142
- export function formatProvenanceNote(note, s) {
143
- if (note.kind === 'reviewed')
144
- return s.green(note.text);
145
- return s.yellow(note.text);
146
- }
147
- /**
148
- * Report documents whose recorded review no longer describes their text.
149
- *
150
- * This is a static validation. It needs no history and no database, and it
151
- * catches most of what a temporal graph would answer.
152
- */
153
- export async function checkStatus(latticeDir, projectRoot = dirname(latticeDir)) {
154
- const files = await listLatticeFiles(latticeDir);
155
- const errors = [];
156
- for (const file of files) {
157
- const content = await readFile(file, 'utf-8');
158
- const relPath = relative(process.cwd(), file);
159
- const target = toPosix(relative(projectRoot, file)).replace(/\.md$/, '');
160
- const { status, reviewedHash } = readProvenance(content);
161
- if (status === undefined) {
162
- if (reviewedHash !== undefined) {
163
- errors.push({
164
- file: relPath,
165
- line: 1,
166
- target,
167
- message: `document records a reviewed-hash but no status — add "status: human-reviewed", or drop the hash`,
168
- });
169
- }
170
- continue;
171
- }
172
- if (!DOC_STATUSES.includes(status)) {
173
- errors.push({
174
- file: relPath,
175
- line: 1,
176
- target,
177
- message: `document declares unknown status ${quoteUntrusted(status, 40)} — use one of: human-reviewed, agent-extracted`,
178
- });
179
- continue;
180
- }
181
- if (status === 'agent-extracted') {
182
- if (reviewedHash !== undefined) {
183
- errors.push({
184
- file: relPath,
185
- line: 1,
186
- target,
187
- message: `document is agent-extracted but records a reviewed-hash — a hash records that a person checked the text, so set "status: human-reviewed" or drop the hash`,
188
- });
189
- }
190
- continue;
191
- }
192
- // human-reviewed. No hash is not an error: existing trees must not turn
193
- // red on upgrade. It simply buys no staleness detection.
194
- if (reviewedHash === undefined)
195
- continue;
196
- const actual = hashReviewedBody(content);
197
- const recorded = reviewedHash.trim().toLowerCase();
198
- if (!HEX.test(recorded) || recorded.length < MIN_HASH_PREFIX) {
199
- errors.push({
200
- file: relPath,
201
- line: 1,
202
- target,
203
- message: `reviewed-hash must be at least ${MIN_HASH_PREFIX} hex characters — record "reviewed-hash: ${actual}"`,
204
- });
205
- continue;
206
- }
207
- if (!actual.startsWith(recorded)) {
208
- errors.push({
209
- file: relPath,
210
- line: 1,
211
- target,
212
- message: `stale review — the text changed after it was reviewed. Re-read it, then record "reviewed-hash: ${actual}"`,
213
- });
214
- }
215
- }
216
- return errors;
217
- }