@plebeiantech/lat.md 0.12.3-fork.9 → 0.13.4-fork.2
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.
- package/README.md +27 -3
- package/dist/src/cli/agent-invocation.d.ts +6 -0
- package/dist/src/cli/agent-invocation.js +23 -0
- package/dist/src/cli/checklist-menu.d.ts +2 -1
- package/dist/src/cli/checklist-menu.js +3 -2
- package/dist/src/cli/fork-instructions.d.ts +1 -0
- package/dist/src/cli/fork-instructions.js +8 -32
- package/dist/src/cli/fork-scaffold.js +2 -2
- package/dist/src/cli/graph.d.ts +1 -1
- package/dist/src/cli/hook.d.ts +26 -0
- package/dist/src/cli/hook.js +115 -64
- package/dist/src/cli/index.js +178 -70
- package/dist/src/cli/init-preferences.d.ts +4 -0
- package/dist/src/cli/init-preferences.js +36 -0
- package/dist/src/cli/init.d.ts +2 -1
- package/dist/src/cli/init.js +100 -60
- package/dist/src/cli/reindex.d.ts +1 -1
- package/dist/src/cli/reindex.js +7 -10
- package/dist/src/cli/search.d.ts +14 -4
- package/dist/src/cli/search.js +115 -101
- package/dist/src/cli/ui-build-server.d.ts +12 -0
- package/dist/src/cli/ui-build-server.js +26 -0
- package/dist/src/cli/ui-build.d.ts +1 -1
- package/dist/src/cli/ui-build.js +1 -1
- package/dist/src/cli/ui.d.ts +1 -1
- package/dist/src/cli/ui.js +1 -1
- package/dist/src/cli/wiki.d.ts +12 -0
- package/dist/src/cli/wiki.js +85 -0
- package/dist/src/fork/antigravity-hook.d.ts +47 -0
- package/dist/src/fork/antigravity-hook.js +424 -0
- package/dist/src/fork/antigravity-init.d.ts +17 -0
- package/dist/src/fork/antigravity-init.js +124 -0
- package/dist/src/fork/upstream-drift.js +1 -1
- package/dist/src/fork/upstream-guard.d.ts +1 -1
- package/dist/src/fork/upstream-guard.js +8 -7
- package/dist/src/graph-export.js +7 -7
- package/dist/src/knowledge/index.js +2 -2
- package/dist/src/mcp/server.js +34 -18
- package/dist/src/search/cache.d.ts +3 -0
- package/dist/src/search/cache.js +104 -0
- package/dist/src/search/chunks.d.ts +23 -0
- package/dist/src/search/chunks.js +200 -0
- package/dist/src/search/db.d.ts +28 -16
- package/dist/src/search/db.js +100 -40
- package/dist/src/search/embedder.d.ts +2 -1
- package/dist/src/search/embedder.js +7 -6
- package/dist/src/search/index.d.ts +6 -2
- package/dist/src/search/index.js +176 -70
- package/dist/src/search/lexical.d.ts +6 -0
- package/dist/src/search/lexical.js +36 -0
- package/dist/src/search/lock.d.ts +5 -0
- package/dist/src/search/lock.js +45 -0
- package/dist/src/search/query.d.ts +26 -0
- package/dist/src/search/query.js +103 -0
- package/dist/src/search/search.d.ts +16 -7
- package/dist/src/search/search.js +165 -21
- package/dist/src/search/types.d.ts +15 -0
- package/dist/src/view/client/assets/GraphView-D_xnk0bP.js +456 -0
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/src/view/client/assets/MarkdownEditor-bjqt_FoF.js +28 -0
- package/dist/src/view/client/assets/OrbitControls-DwcpQNja.js +1 -0
- package/dist/src/view/client/assets/STLLoader-CLWTKt5i.js +1 -0
- package/dist/src/view/client/assets/_commonjsHelpers-CqkleIqs.js +1 -0
- package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-kWGvtRnL.js +1 -0
- package/dist/src/view/client/assets/arc-BPAFd7cP.js +1 -0
- package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-CsY39oIR.js +36 -0
- package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-DHGBG7BJ.js +129 -0
- package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-vywK4eb7.js +38 -0
- package/dist/src/view/client/assets/channel-CX71L_qV.js +1 -0
- package/dist/src/view/client/assets/chunk-2Q5K7J3B-Do3Hz_ci.js +1 -0
- package/dist/src/view/client/assets/chunk-5VM5RSS4-B5R1lSeY.js +15 -0
- package/dist/src/view/client/assets/chunk-F27PBJKO-Dm55FM_W.js +1 -0
- package/dist/src/view/client/assets/chunk-IMKFNOWR-ChptrvvM.js +231 -0
- package/dist/src/view/client/assets/chunk-JWPE2WC7-BRQBjrKS.js +1 -0
- package/dist/src/view/client/assets/chunk-POPQ4Y6H-CainVZIL.js +1 -0
- package/dist/src/view/client/assets/chunk-SVP7TREG-DsKsNGmM.js +88 -0
- package/dist/src/view/client/assets/chunk-TICWLB2K-BctkMcJc.js +206 -0
- package/dist/src/view/client/assets/chunk-XXDRQBXY-BN7SgOUT.js +1 -0
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-B1VyU6zO.js +1 -0
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-B1VyU6zO.js +1 -0
- package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-BszzwrMe.js +1 -0
- package/dist/src/view/client/assets/cynefin-OW5HDTMX-CdRyAkPs.js +166 -0
- package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-BLvKPZ1u.js +62 -0
- package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
- package/dist/src/view/client/assets/dagre-GXQ25YYZ-V68w_l65.js +4 -0
- package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/src/view/client/assets/diagram-S7CK7UJ4-C2i4eEMg.js +30 -0
- package/dist/src/view/client/assets/diagram-UQ7AKVKN-BiwWh5jC.js +41 -0
- package/dist/src/view/client/assets/diagram-VSXAHHWV-hkKR_vyS.js +3 -0
- package/dist/src/view/client/assets/diagram-VX7I27RA-DzRCSMAc.js +24 -0
- package/dist/src/view/client/assets/diagram-Z3DM3KII-fhSFPxyK.js +24 -0
- package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-CCEUDKSA.js +1 -0
- package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-FJ2IMyTi.js +99 -0
- package/dist/src/view/client/assets/flowDiagram-HODETNUW-BvasdLeK.js +168 -0
- package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-CiwkBZvY.js +292 -0
- package/dist/src/view/client/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/src/view/client/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/src/view/client/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/src/view/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/src/view/client/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/src/view/client/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-DYlP2_js.js +106 -0
- package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
- package/dist/src/view/client/assets/index-CQ7EOox7.css +1 -0
- package/dist/src/view/client/assets/index-CynNDkxt.js +13 -0
- package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DRPB2c48.js +2 -0
- package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
- package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-8VV429Cv.js +70 -0
- package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-Dzmwnf83.js +139 -0
- package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-pYyZZ1i-.js +89 -0
- package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
- package/dist/src/view/client/assets/layout-GF9k6yGo.js +1 -0
- package/dist/src/view/client/assets/linear-Yt2sSOYV.js +1 -0
- package/dist/src/view/client/assets/logo-RR_KBOAT.svg +149 -0
- package/dist/src/view/client/assets/maplibre-gl-CDX6Zuow.js +800 -0
- package/dist/src/view/client/assets/mermaid.core-ChWYCHN0.js +310 -0
- package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-Cc4fQvf1.js +96 -0
- package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-C0SsB8bZ.js +1 -0
- package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-DIJpqERZ.js +39 -0
- package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC--d-gl6j-.js +7 -0
- package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BQxwTt7z.js +1 -0
- package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-B_oTeD2A.js +84 -0
- package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-DLukmE8X.js +40 -0
- package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-BNjVB2sc.js +162 -0
- package/dist/src/view/client/assets/sizeCapture-INFHLROL-BZecQTOw.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Bu1urvXC.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-5pc_697G.js +1 -0
- package/dist/src/view/client/assets/swimlanes-42K2YHIH-bLAlcepB.js +1 -0
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-BbOpZjpp.js +8 -0
- package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
- package/dist/src/view/client/assets/timeline-definition-24CTP7MA-tbwCNvcA.js +120 -0
- package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-D8U8Fk0N.js +34 -0
- package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-LHBVIeNb.js +78 -0
- package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-CN4tfaFV.js +7 -0
- package/dist/src/view/client/index.html +2 -2
- package/dist/src/view/client-shell.d.ts +2 -0
- package/dist/src/view/client-shell.js +5 -0
- package/dist/src/view/diagnostics.d.ts +3 -2
- package/dist/src/view/diagnostics.js +49 -58
- package/dist/src/view/document-edit.d.ts +8 -0
- package/dist/src/view/document-edit.js +25 -0
- package/dist/src/view/document-route.d.ts +14 -0
- package/dist/src/view/document-route.js +137 -0
- package/dist/src/view/document-tree.d.ts +18 -0
- package/dist/src/view/document-tree.js +139 -0
- package/dist/src/view/external-document-tree.d.ts +3 -0
- package/dist/src/view/external-document-tree.js +674 -0
- package/dist/src/view/git-diff.js +202 -2
- package/dist/src/view/git.js +1 -1
- package/dist/src/view/graph.d.ts +4 -3
- package/dist/src/view/graph.js +100 -9
- package/dist/src/view/highlight.d.ts +10 -2
- package/dist/src/view/highlight.js +50648 -3166
- package/dist/src/view/markdown.d.ts +3 -2
- package/dist/src/view/markdown.js +256 -54
- package/dist/src/view/preindexed-search.d.ts +15 -0
- package/dist/src/view/preindexed-search.js +25 -0
- package/dist/src/view/protocol.d.ts +53 -7
- package/dist/src/view/publication.d.ts +2 -0
- package/dist/src/view/publication.js +78 -0
- package/dist/src/view/references.d.ts +11 -26
- package/dist/src/view/references.js +121 -112
- package/dist/src/view/repository.d.ts +14 -4
- package/dist/src/view/repository.js +260 -19
- package/dist/src/view/request-security.d.ts +5 -0
- package/dist/src/view/request-security.js +46 -0
- package/dist/src/view/search-result.d.ts +4 -0
- package/dist/src/view/search-result.js +25 -0
- package/dist/src/view/search.d.ts +7 -2
- package/dist/src/view/search.js +23 -34
- package/dist/src/view/server-build.d.ts +21 -0
- package/dist/src/view/server-build.js +145 -0
- package/dist/src/view/server-deployment.d.ts +31 -0
- package/dist/src/view/server-deployment.js +123 -0
- package/dist/src/view/server-index-worker.d.ts +6 -0
- package/dist/src/view/server-index-worker.js +14 -0
- package/dist/src/view/server.d.ts +11 -2
- package/dist/src/view/server.js +245 -73
- package/dist/src/view/source-target.d.ts +2 -0
- package/dist/src/view/source-target.js +49 -8
- package/dist/src/view/static-build.d.ts +18 -2
- package/dist/src/view/static-build.js +449 -110
- package/dist/src/view/static-protocol.d.ts +26 -2
- package/dist/src/view/static-protocol.js +5 -0
- package/dist/src/view/store.d.ts +20 -3
- package/dist/src/view/store.js +304 -48
- package/dist/src/view/table-of-contents.d.ts +2 -2
- package/dist/src/view/table-of-contents.js +2 -22
- package/dist/src/view/vercel-build.d.ts +24 -0
- package/dist/src/view/vercel-build.js +152 -0
- package/dist/src/view/vercel-server-build.d.ts +10 -0
- package/dist/src/view/vercel-server-build.js +61 -0
- package/dist/src/wiki/catalog.d.ts +21 -0
- package/dist/src/wiki/catalog.js +256 -0
- package/dist/src/wiki/publish.d.ts +27 -0
- package/dist/src/wiki/publish.js +265 -0
- package/dist/src/wiki/render.d.ts +20 -0
- package/dist/src/wiki/render.js +163 -0
- package/dist/src/wiki/transform.d.ts +7 -0
- package/dist/src/wiki/transform.js +264 -0
- package/dist/vendor/lat.md-server/cli.d.ts +2 -0
- package/dist/vendor/lat.md-server/cli.js +12 -0
- package/dist/vendor/lat.md-server/index.d.ts +38 -0
- package/dist/vendor/lat.md-server/index.js +128 -0
- package/package.json +68 -22
- package/plugins/lat-md/README.md +61 -0
- package/plugins/lat-md/hooks.json +38 -0
- package/plugins/lat-md/plugin.json +9 -0
- package/plugins/lat-md/rules/AGENTS.md +344 -0
- package/plugins/lat-md/skills/lat-md/SKILL.md +210 -0
- package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +226 -0
- package/templates/AGENTS.md +26 -7
- package/templates/cursor-rules.md +23 -7
- package/templates/fork/conventions.md +133 -11
- package/templates/opencode-plugin.ts +9 -9
- package/templates/pi-extension.ts +11 -11
- package/templates/skill/SKILL.md +35 -3
- package/dist/src/cli/check-coverage.d.ts +0 -2
- package/dist/src/cli/check-coverage.js +0 -71
- package/dist/src/cli/check-frontmatter.d.ts +0 -12
- package/dist/src/cli/check-frontmatter.js +0 -44
- package/dist/src/cli/check-mode.d.ts +0 -21
- package/dist/src/cli/check-mode.js +0 -364
- package/dist/src/cli/check-status.d.ts +0 -74
- package/dist/src/cli/check-status.js +0 -217
- package/dist/src/cli/check.d.ts +0 -45
- package/dist/src/cli/check.js +0 -781
- package/dist/src/cli/context.d.ts +0 -11
- package/dist/src/cli/context.js +0 -58
- package/dist/src/cli/expand.d.ts +0 -8
- package/dist/src/cli/expand.js +0 -94
- package/dist/src/cli/gen-index.d.ts +0 -45
- package/dist/src/cli/gen-index.js +0 -235
- package/dist/src/cli/link-scheme.d.ts +0 -54
- package/dist/src/cli/link-scheme.js +0 -101
- package/dist/src/cli/locate.d.ts +0 -2
- package/dist/src/cli/locate.js +0 -17
- package/dist/src/cli/refs.d.ts +0 -22
- package/dist/src/cli/refs.js +0 -229
- package/dist/src/cli/section.d.ts +0 -43
- package/dist/src/cli/section.js +0 -252
- package/dist/src/cli/select-menu.d.ts +0 -13
- package/dist/src/cli/select-menu.js +0 -100
- package/dist/src/code-refs.d.ts +0 -17
- package/dist/src/code-refs.js +0 -298
- package/dist/src/config.d.ts +0 -29
- package/dist/src/config.js +0 -83
- package/dist/src/context.d.ts +0 -23
- package/dist/src/context.js +0 -11
- package/dist/src/extensions/wiki-link/from-markdown.d.ts +0 -5
- package/dist/src/extensions/wiki-link/from-markdown.js +0 -34
- package/dist/src/extensions/wiki-link/index.d.ts +0 -4
- package/dist/src/extensions/wiki-link/index.js +0 -3
- package/dist/src/extensions/wiki-link/syntax.d.ts +0 -14
- package/dist/src/extensions/wiki-link/syntax.js +0 -121
- package/dist/src/extensions/wiki-link/to-markdown.d.ts +0 -5
- package/dist/src/extensions/wiki-link/to-markdown.js +0 -28
- package/dist/src/extensions/wiki-link/types.d.ts +0 -34
- package/dist/src/format.d.ts +0 -8
- package/dist/src/format.js +0 -43
- package/dist/src/init-version.d.ts +0 -10
- package/dist/src/init-version.js +0 -49
- package/dist/src/lattice.d.ts +0 -109
- package/dist/src/lattice.js +0 -817
- package/dist/src/parser.d.ts +0 -3
- package/dist/src/parser.js +0 -18
- package/dist/src/source-parser.d.ts +0 -27
- package/dist/src/source-parser.js +0 -916
- package/dist/src/untrusted.d.ts +0 -18
- package/dist/src/untrusted.js +0 -93
- package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
- package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
- package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
- package/dist/src/walk.d.ts +0 -18
- package/dist/src/walk.js +0 -27
- /package/dist/src/{extensions/wiki-link → search}/types.js +0 -0
|
@@ -1,364 +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 '../walk.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
|
-
* Extra top-level paragraphs found under headings, beyond the one
|
|
100
|
-
* `checkSections` already permits as the leading summary. A paragraph
|
|
101
|
-
* before the first heading is the document lead, not narrative prose.
|
|
102
|
-
*/
|
|
103
|
-
function extraParagraphs(tree) {
|
|
104
|
-
const offenders = [];
|
|
105
|
-
let currentHeading = null;
|
|
106
|
-
let seenParagraphUnderHeading = false;
|
|
107
|
-
for (const node of tree.children) {
|
|
108
|
-
if (node.type === 'heading') {
|
|
109
|
-
currentHeading = inlineTextOf(node);
|
|
110
|
-
seenParagraphUnderHeading = false;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (node.type === 'paragraph') {
|
|
114
|
-
if (currentHeading === null) {
|
|
115
|
-
// Document lead paragraph before any heading — allowed.
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (!seenParagraphUnderHeading) {
|
|
119
|
-
seenParagraphUnderHeading = true;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
offenders.push({
|
|
123
|
-
line: node.position.start.line,
|
|
124
|
-
heading: currentHeading,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return offenders;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Line numbers (1-indexed, inclusive of both ends) that belong to a `code`
|
|
133
|
-
* or `yaml` (frontmatter) node anywhere in the tree. Walking the parsed tree
|
|
134
|
-
* — rather than re-scanning raw text with a fence regex — means a fence of
|
|
135
|
-
* any marker length or an indented code block is recognized correctly, and
|
|
136
|
-
* frontmatter needs no special case: it simply isn't prose in the tree.
|
|
137
|
-
*/
|
|
138
|
-
function nonProseLines(tree) {
|
|
139
|
-
const lines = new Set();
|
|
140
|
-
const markRange = (node) => {
|
|
141
|
-
if (!node.position)
|
|
142
|
-
return;
|
|
143
|
-
for (let line = node.position.start.line; line <= node.position.end.line; line++) {
|
|
144
|
-
lines.add(line);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
const walk = (nodes) => {
|
|
148
|
-
for (const node of nodes) {
|
|
149
|
-
if (node.type === 'code' || node.type === 'yaml') {
|
|
150
|
-
markRange(node);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if ('children' in node) {
|
|
154
|
-
walk(node.children);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
walk(tree.children);
|
|
159
|
-
return lines;
|
|
160
|
-
}
|
|
161
|
-
function findImperativeHits(tree, content) {
|
|
162
|
-
const hits = [];
|
|
163
|
-
const lines = content.split('\n');
|
|
164
|
-
const skipLines = nonProseLines(tree);
|
|
165
|
-
for (let i = 0; i < lines.length; i++) {
|
|
166
|
-
const lineNumber = i + 1;
|
|
167
|
-
if (skipLines.has(lineNumber))
|
|
168
|
-
continue;
|
|
169
|
-
const trimmedStart = lines[i].trimStart();
|
|
170
|
-
if (trimmedStart.startsWith('#'))
|
|
171
|
-
continue;
|
|
172
|
-
let text = trimmedStart;
|
|
173
|
-
text = text.replace(/^(?:[-*+]\s+|\d+[.)]\s+)/, '');
|
|
174
|
-
text = text.replace(/^\*\*/, '');
|
|
175
|
-
for (const verb of IMPERATIVE_VERBS) {
|
|
176
|
-
if (text.startsWith(verb + ' ')) {
|
|
177
|
-
hits.push({ line: lineNumber, verb });
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return hits;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* The index file a directory is required to carry, e.g. `lat.md` for the root
|
|
186
|
-
* and `reference.md` for `reference/`. An index is navigation rather than
|
|
187
|
-
* content, so the `require-mode` gate below never asks one to declare a mode.
|
|
188
|
-
*/
|
|
189
|
-
export function indexNameFor(dirName) {
|
|
190
|
-
return dirName.endsWith('.md') ? dirName : dirName + '.md';
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* What the root index says about the mode requirement.
|
|
194
|
-
*
|
|
195
|
-
* Opt-in, and deliberately read from the tree rather than from a flag or an
|
|
196
|
-
* environment variable: the rule belongs to a documentation set, not to a
|
|
197
|
-
* machine or a CI job, and a tree that predates the rule has to keep passing.
|
|
198
|
-
* `lat init` stamps the flag into the root index it scaffolds, so a project
|
|
199
|
-
* set up after this exists is gated from its first commit while an older one
|
|
200
|
-
* is untouched until someone adds the line.
|
|
201
|
-
*
|
|
202
|
-
* The raw value is returned rather than a boolean so the caller can tell an
|
|
203
|
-
* absent flag from one that was set to something this never reads. `yes` and
|
|
204
|
-
* `1` are strings to a YAML 1.2 parser, and enforcing nothing on them while
|
|
205
|
-
* saying nothing about them is how a project ends up believing the gate is on.
|
|
206
|
-
*/
|
|
207
|
-
async function readRequireModeFlag(latticeDir) {
|
|
208
|
-
const indexPath = join(latticeDir, indexNameFor(basename(latticeDir)));
|
|
209
|
-
try {
|
|
210
|
-
const content = await readFile(indexPath, 'utf-8');
|
|
211
|
-
return parseFrontmatter(content).raw['require-mode'];
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
export async function checkMode(latticeDir, projectRoot = dirname(latticeDir), options = {}) {
|
|
218
|
-
const files = await listLatticeFiles(latticeDir);
|
|
219
|
-
const errors = [];
|
|
220
|
-
const flag = await readRequireModeFlag(latticeDir);
|
|
221
|
-
const requireMode = options.requireMode ?? flag === true;
|
|
222
|
-
if (flag !== undefined && typeof flag !== 'boolean') {
|
|
223
|
-
const indexPath = join(latticeDir, indexNameFor(basename(latticeDir)));
|
|
224
|
-
errors.push({
|
|
225
|
-
file: relative(process.cwd(), indexPath),
|
|
226
|
-
line: 1,
|
|
227
|
-
target: toPosix(relative(projectRoot, indexPath)).replace(/\.md$/, ''),
|
|
228
|
-
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.`,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
for (const file of files) {
|
|
232
|
-
const content = await readFile(file, 'utf-8');
|
|
233
|
-
const relPath = relative(process.cwd(), file);
|
|
234
|
-
const target = toPosix(relative(projectRoot, file)).replace(/\.md$/, '');
|
|
235
|
-
const fm = parseFrontmatter(content);
|
|
236
|
-
const declaredRaw = fm.raw['mode'];
|
|
237
|
-
// null (bare `mode:` key) is treated as absent; any other non-string is
|
|
238
|
-
// reported below via declaredNonString.
|
|
239
|
-
const declared = declaredRaw === null || declaredRaw === undefined
|
|
240
|
-
? undefined
|
|
241
|
-
: typeof declaredRaw === 'string'
|
|
242
|
-
? declaredRaw
|
|
243
|
-
: undefined;
|
|
244
|
-
const declaredNonString = declaredRaw !== null &&
|
|
245
|
-
declaredRaw !== undefined &&
|
|
246
|
-
typeof declaredRaw !== 'string'
|
|
247
|
-
? declaredRaw
|
|
248
|
-
: undefined;
|
|
249
|
-
const relFromLattice = toPosix(relative(latticeDir, file));
|
|
250
|
-
const firstSegment = relFromLattice.split('/')[0];
|
|
251
|
-
const dirMode = firstSegment && firstSegment !== relFromLattice
|
|
252
|
-
? DIR_TO_MODE[firstSegment]
|
|
253
|
-
: undefined;
|
|
254
|
-
if (declaredNonString !== undefined) {
|
|
255
|
-
errors.push({
|
|
256
|
-
file: relPath,
|
|
257
|
-
line: 1,
|
|
258
|
-
target,
|
|
259
|
-
message: `document declares unknown mode ${quoteUntrusted(JSON.stringify(declaredNonString), 40)} — use one of: tutorial, how-to, reference, explanation`,
|
|
260
|
-
});
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (declared !== undefined &&
|
|
264
|
-
!DIATAXIS_MODES.includes(declared)) {
|
|
265
|
-
errors.push({
|
|
266
|
-
file: relPath,
|
|
267
|
-
line: 1,
|
|
268
|
-
target,
|
|
269
|
-
message: `document declares unknown mode ${quoteUntrusted(declared, 40)} — use one of: tutorial, how-to, reference, explanation`,
|
|
270
|
-
});
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (declared !== undefined &&
|
|
274
|
-
dirMode !== undefined &&
|
|
275
|
-
declared !== dirMode) {
|
|
276
|
-
errors.push({
|
|
277
|
-
file: relPath,
|
|
278
|
-
line: 1,
|
|
279
|
-
target,
|
|
280
|
-
message: `document is in lat.md/${firstSegment}/ but declares mode: ${declared} — the mode must match the directory that holds it`,
|
|
281
|
-
});
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const mode = (declared ?? dirMode);
|
|
285
|
-
if (mode === undefined) {
|
|
286
|
-
const segments = relFromLattice.split('/');
|
|
287
|
-
const fileName = segments.pop();
|
|
288
|
-
const holder = segments.length === 0 ? basename(latticeDir) : segments.at(-1);
|
|
289
|
-
const isIndex = fileName === indexNameFor(holder);
|
|
290
|
-
if (requireMode && !isIndex) {
|
|
291
|
-
errors.push({
|
|
292
|
-
file: relPath,
|
|
293
|
-
line: 1,
|
|
294
|
-
target,
|
|
295
|
-
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.)',
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
const tree = parse(content);
|
|
301
|
-
if (mode === 'how-to') {
|
|
302
|
-
if (!orderedListPresent(tree)) {
|
|
303
|
-
errors.push({
|
|
304
|
-
file: relPath,
|
|
305
|
-
line: 1,
|
|
306
|
-
target,
|
|
307
|
-
message: `a how-to must give ordered steps — add a numbered list`,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (mode === 'tutorial') {
|
|
313
|
-
if (!orderedListPresent(tree)) {
|
|
314
|
-
errors.push({
|
|
315
|
-
file: relPath,
|
|
316
|
-
line: 1,
|
|
317
|
-
target,
|
|
318
|
-
message: `a tutorial must give ordered steps — add a numbered list`,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
if (!outcomeStated(tree)) {
|
|
322
|
-
errors.push({
|
|
323
|
-
file: relPath,
|
|
324
|
-
line: 1,
|
|
325
|
-
target,
|
|
326
|
-
message: `a tutorial must state its outcome — add a heading or sentence saying what the reader will have at the end`,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (mode === 'reference') {
|
|
332
|
-
for (const offender of extraParagraphs(tree)) {
|
|
333
|
-
errors.push({
|
|
334
|
-
file: relPath,
|
|
335
|
-
line: offender.line,
|
|
336
|
-
target,
|
|
337
|
-
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`,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if (mode === 'explanation') {
|
|
343
|
-
const hits = findImperativeHits(tree, content);
|
|
344
|
-
const capped = hits.slice(0, MAX_IMPERATIVE_ERRORS);
|
|
345
|
-
for (const hit of capped) {
|
|
346
|
-
errors.push({
|
|
347
|
-
file: relPath,
|
|
348
|
-
line: hit.line,
|
|
349
|
-
target,
|
|
350
|
-
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`,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
if (hits.length > MAX_IMPERATIVE_ERRORS) {
|
|
354
|
-
errors.push({
|
|
355
|
-
file: relPath,
|
|
356
|
-
line: 1,
|
|
357
|
-
target,
|
|
358
|
-
message: `capped at ${MAX_IMPERATIVE_ERRORS} imperative-command errors for this document — fix these first, then re-run`,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return errors;
|
|
364
|
-
}
|
|
@@ -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 '../walk.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
|
-
}
|