@plebeiantech/lat.md 0.12.3-fork.2 → 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.
- package/README.md +31 -2
- 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/comment-guard.d.ts +14 -0
- package/dist/src/cli/comment-guard.js +10 -33
- package/dist/src/cli/comment-reminder.d.ts +65 -6
- package/dist/src/cli/comment-reminder.js +164 -28
- package/dist/src/cli/fork-instructions.d.ts +20 -0
- package/dist/src/cli/fork-instructions.js +184 -0
- package/dist/src/cli/fork-scaffold.d.ts +106 -0
- package/dist/src/cli/fork-scaffold.js +490 -0
- package/dist/src/cli/graph.d.ts +1 -1
- package/dist/src/cli/hook.d.ts +26 -0
- package/dist/src/cli/hook.js +118 -46
- package/dist/src/cli/index.js +166 -64
- 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 +107 -50
- 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 +116 -100
- 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 +4 -0
- package/dist/src/cli/ui-build.js +13 -0
- package/dist/src/cli/ui.d.ts +1 -1
- package/dist/src/cli/ui.js +18 -2
- 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-cli.js +2 -0
- package/dist/src/fork/upstream-drift.d.ts +26 -0
- package/dist/src/fork/upstream-drift.js +185 -0
- package/dist/src/fork/upstream-guard-cli.d.ts +1 -0
- package/dist/src/fork/upstream-guard-cli.js +2 -0
- package/dist/src/fork/upstream-guard.d.ts +72 -0
- package/dist/src/fork/upstream-guard.js +445 -0
- package/dist/src/graph-export.d.ts +2 -0
- package/dist/src/graph-export.js +38 -19
- package/dist/src/knowledge/claude-memory.d.ts +1 -0
- package/dist/src/knowledge/claude-memory.js +33 -4
- 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 +197 -0
- package/dist/src/search/db.d.ts +28 -16
- package/dist/src/search/db.js +106 -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 -53
- 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 +17 -7
- package/dist/src/search/search.js +164 -14
- package/dist/src/search/types.d.ts +15 -0
- package/dist/src/search/types.js +1 -0
- package/dist/src/view/client/assets/GraphView-Dij8mAA_.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-88Wbt0M1.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-y0DlYa8E.js +1 -0
- package/dist/src/view/client/assets/arc-FlMu8dnO.js +1 -0
- package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-DA_uWZW1.js +36 -0
- package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-BHQ5LU85.js +129 -0
- package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-BRucKCt2.js +38 -0
- package/dist/src/view/client/assets/channel-BgDNA_Ay.js +1 -0
- package/dist/src/view/client/assets/chunk-2Q5K7J3B-BJPcFivt.js +1 -0
- package/dist/src/view/client/assets/chunk-5VM5RSS4-v2q99bGz.js +15 -0
- package/dist/src/view/client/assets/chunk-F27PBJKO-CcHOuJCs.js +1 -0
- package/dist/src/view/client/assets/chunk-IMKFNOWR-DNxYuJNq.js +231 -0
- package/dist/src/view/client/assets/chunk-JWPE2WC7-C0eHKvLW.js +1 -0
- package/dist/src/view/client/assets/chunk-POPQ4Y6H-57CWp2Ve.js +1 -0
- package/dist/src/view/client/assets/chunk-SVP7TREG-BqcoxIJw.js +88 -0
- package/dist/src/view/client/assets/chunk-TICWLB2K-DRUJR5zH.js +206 -0
- package/dist/src/view/client/assets/chunk-XXDRQBXY-Dez0oRoM.js +1 -0
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-CFZ_Sgh5.js +1 -0
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-CFZ_Sgh5.js +1 -0
- package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-BHtud6Hg.js +1 -0
- package/dist/src/view/client/assets/cynefin-OW5HDTMX-BjBW8VGk.js +166 -0
- package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-DwGuD7oF.js +62 -0
- package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
- package/dist/src/view/client/assets/dagre-GXQ25YYZ-C5zSlGK9.js +4 -0
- package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/src/view/client/assets/diagram-S7CK7UJ4-C_NhA3bj.js +30 -0
- package/dist/src/view/client/assets/diagram-UQ7AKVKN-az7QPYNb.js +41 -0
- package/dist/src/view/client/assets/diagram-VSXAHHWV-BRvDuM8_.js +3 -0
- package/dist/src/view/client/assets/diagram-VX7I27RA-BWLgF6S-.js +24 -0
- package/dist/src/view/client/assets/diagram-Z3DM3KII-DV3d6mL5.js +24 -0
- package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-yUXrEAvK.js +1 -0
- package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-D35FN1Pt.js +99 -0
- package/dist/src/view/client/assets/flowDiagram-HODETNUW-BciZqsjT.js +168 -0
- package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-BysPGgXS.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-C3DdJ5xG.js +106 -0
- package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
- package/dist/src/view/client/assets/index-CJokoRSp.js +13 -0
- package/dist/src/view/client/assets/index-DG0Ez15D.css +1 -0
- package/dist/src/view/client/assets/infoDiagram-27XIBGKW-D3HZrDRb.js +2 -0
- package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
- package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-BXyVIXTU.js +70 -0
- package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-D4-1NxVU.js +139 -0
- package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-4Gu_5yBA.js +89 -0
- package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
- package/dist/src/view/client/assets/layout-Jf1K3TMM.js +1 -0
- package/dist/src/view/client/assets/linear-DWfwzUT_.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-DMx_DAl8.js +310 -0
- package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-8pxY_RWi.js +96 -0
- package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-DNAw_O5t.js +1 -0
- package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-uuvRCDkC.js +39 -0
- package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC-BER2O4Aq.js +7 -0
- package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-CxEsJ_0n.js +1 -0
- package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-yS9rspca.js +84 -0
- package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-Bqt70a2j.js +40 -0
- package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-BvOOEJNr.js +162 -0
- package/dist/src/view/client/assets/sizeCapture-INFHLROL-CvO_Pjjo.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-D77RDMKH-KLmVOZkq.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-KCS8Mdzu.js +1 -0
- package/dist/src/view/client/assets/swimlanes-42K2YHIH-DC1r4_sq.js +1 -0
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-B2TcAwis.js +8 -0
- package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
- package/dist/src/view/client/assets/timeline-definition-24CTP7MA-ChXLOuBL.js +120 -0
- package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-D9_h25-Y.js +34 -0
- package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-CsQJGGW0.js +78 -0
- package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-DGMuPMlm.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 +126 -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 +205 -1
- package/dist/src/view/git.js +1 -1
- package/dist/src/view/graph.d.ts +7 -0
- package/dist/src/view/graph.js +267 -0
- package/dist/src/view/highlight.d.ts +10 -2
- package/dist/src/view/highlight.js +4547 -1363
- package/dist/src/view/markdown.d.ts +8 -3
- package/dist/src/view/markdown.js +308 -41
- 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 +95 -6
- package/dist/src/view/protocol.js +1 -1
- package/dist/src/view/publication.d.ts +2 -0
- package/dist/src/view/publication.js +78 -0
- package/dist/src/view/references.d.ts +13 -26
- package/dist/src/view/references.js +150 -119
- package/dist/src/view/repository.d.ts +18 -5
- package/dist/src/view/repository.js +269 -27
- 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 -33
- 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 +94 -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 +14 -2
- package/dist/src/view/server.js +254 -51
- package/dist/src/view/source-target.d.ts +10 -0
- package/dist/src/view/source-target.js +43 -0
- package/dist/src/view/static-build.d.ts +32 -0
- package/dist/src/view/static-build.js +741 -0
- package/dist/src/view/static-protocol.d.ts +47 -0
- package/dist/src/view/static-protocol.js +15 -0
- package/dist/src/view/store.d.ts +26 -3
- package/dist/src/view/store.js +325 -44
- package/dist/src/view/table-of-contents.d.ts +9 -0
- package/dist/src/view/table-of-contents.js +83 -0
- package/dist/src/view/vercel-build.d.ts +24 -0
- package/dist/src/view/vercel-build.js +140 -0
- package/dist/src/view/vercel-server-build.d.ts +10 -0
- package/dist/src/view/vercel-server-build.js +61 -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 +72 -25
- 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 +118 -0
- package/plugins/lat-md/skills/lat-md/SKILL.md +178 -0
- package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +182 -0
- package/templates/AGENTS.md +19 -18
- package/templates/cursor-rules.md +14 -5
- package/templates/fork/conventions.md +173 -0
- package/templates/opencode-plugin.ts +9 -9
- package/templates/pi-extension.ts +11 -11
- package/templates/skill/SKILL.md +28 -17
- 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 -6
- package/dist/src/cli/check-mode.js +0 -303
- 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 -777
- 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 -277
- 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 -108
- package/dist/src/lattice.js +0 -816
- 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 -24
- package/dist/src/untrusted.js +0 -97
- package/dist/src/view/client/assets/index-B0n0u03y.css +0 -1
- package/dist/src/view/client/assets/index-xm2_px4q.js +0 -23
- package/dist/src/walk.d.ts +0 -18
- package/dist/src/walk.js +0 -27
- /package/dist/src/{extensions/wiki-link/types.js → fork/upstream-drift-cli.d.ts} +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
4
|
+
// @lat: [[upstream-guard#The upstream guard]]
|
|
5
|
+
export const SYNC_POINT_FILE = 'fork-upstream-sync-point';
|
|
6
|
+
/**
|
|
7
|
+
* The commit this fork was originally cut from. It is not the guard's
|
|
8
|
+
* baseline — see [[upstream-guard#The upstream guard#The sync point]] for why a frozen
|
|
9
|
+
* one cannot tell the fork's changes from upstream's.
|
|
10
|
+
*/
|
|
11
|
+
export const ORIGINAL_FORK_POINT = '65d2f5b220853b2b10dca3de18d511f575bc12c7';
|
|
12
|
+
// @lat: [[upstream-guard#The upstream guard#The allowlist]]
|
|
13
|
+
export const ALLOWLIST_FILE = 'fork-upstream-allowlist.tsv';
|
|
14
|
+
export const REASON_PLACEHOLDER = 'TODO';
|
|
15
|
+
const ALLOWLIST_HEADER = [
|
|
16
|
+
'# Upstream files this fork is permitted to modify.',
|
|
17
|
+
'#',
|
|
18
|
+
'# Generated by `node dist/src/fork/upstream-guard-cli.js --regenerate`.',
|
|
19
|
+
'# One entry per line: the repo-relative path, a TAB, then the reason the',
|
|
20
|
+
'# fork must diverge in that file. A placeholder reason fails the check —',
|
|
21
|
+
'# regenerate writes the marker, a person replaces it with the real reason.',
|
|
22
|
+
'#',
|
|
23
|
+
'# Paths are relative to the sync point in fork-upstream-sync-point, so an',
|
|
24
|
+
'# upstream merge moves this list. Regenerate in the same commit.',
|
|
25
|
+
'#',
|
|
26
|
+
'# Deleting an upstream file is never allowlistable. See lat.md/fork.md.',
|
|
27
|
+
'',
|
|
28
|
+
];
|
|
29
|
+
export function parseAllowlist(text) {
|
|
30
|
+
const entries = [];
|
|
31
|
+
const errors = [];
|
|
32
|
+
const seen = new Map();
|
|
33
|
+
text.split('\n').forEach((raw, index) => {
|
|
34
|
+
const line = index + 1;
|
|
35
|
+
const stripped = raw.replace(/\r$/, '');
|
|
36
|
+
if (stripped.trim() === '' || stripped.trimStart().startsWith('#'))
|
|
37
|
+
return;
|
|
38
|
+
const tab = stripped.indexOf('\t');
|
|
39
|
+
const split = tab >= 0 ? tab : stripped.search(/\s/);
|
|
40
|
+
if (split < 0) {
|
|
41
|
+
errors.push(`${ALLOWLIST_FILE}:${line}: entry has no reason: ${stripped}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const path = stripped.slice(0, split).trim();
|
|
45
|
+
const reason = stripped.slice(split + 1).trim();
|
|
46
|
+
if (path === '') {
|
|
47
|
+
errors.push(`${ALLOWLIST_FILE}:${line}: entry has no path`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (reason === '') {
|
|
51
|
+
errors.push(`${ALLOWLIST_FILE}:${line}: entry has no reason: ${path}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const first = seen.get(path);
|
|
55
|
+
if (first !== undefined) {
|
|
56
|
+
errors.push(`${ALLOWLIST_FILE}:${line}: duplicate entry for ${path} ` +
|
|
57
|
+
`(first at line ${first})`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
seen.set(path, line);
|
|
61
|
+
entries.push({ path, reason, line });
|
|
62
|
+
});
|
|
63
|
+
return { entries, errors };
|
|
64
|
+
}
|
|
65
|
+
export function renderAllowlist(entries) {
|
|
66
|
+
const sorted = [...entries].sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
67
|
+
const body = sorted.map((e) => `${e.path}\t${e.reason}`);
|
|
68
|
+
return [...ALLOWLIST_HEADER, ...body, ''].join('\n');
|
|
69
|
+
}
|
|
70
|
+
export function needsReason(reason) {
|
|
71
|
+
return reason.trimStart().toUpperCase().startsWith(REASON_PLACEHOLDER);
|
|
72
|
+
}
|
|
73
|
+
export function parseNameStatus(raw) {
|
|
74
|
+
const fields = raw.split('\0').filter((f) => f !== '');
|
|
75
|
+
const out = [];
|
|
76
|
+
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
77
|
+
out.push({ status: fields[i], path: fields[i + 1] });
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function classify(input) {
|
|
82
|
+
const findings = [];
|
|
83
|
+
const touched = [];
|
|
84
|
+
for (const entry of input.diff) {
|
|
85
|
+
if (!input.upstream.has(entry.path))
|
|
86
|
+
continue;
|
|
87
|
+
touched.push(entry.path);
|
|
88
|
+
if (entry.status.startsWith('D')) {
|
|
89
|
+
findings.push({
|
|
90
|
+
kind: 'deletion',
|
|
91
|
+
path: entry.path,
|
|
92
|
+
message: `${entry.path}: deletes an upstream file.\n` +
|
|
93
|
+
' No allowlist entry can permit this. A file this fork deletes ' +
|
|
94
|
+
'conflicts on\n' +
|
|
95
|
+
' every future merge that touches it upstream; a file this fork ' +
|
|
96
|
+
'modifies\n' +
|
|
97
|
+
' conflicts once and stays resolved. Leave the file in place and ' +
|
|
98
|
+
'change\n' +
|
|
99
|
+
' what it does, or turn it off out of band.',
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const reason = input.allowlist.get(entry.path);
|
|
104
|
+
if (reason === undefined) {
|
|
105
|
+
findings.push({
|
|
106
|
+
kind: 'unallowlisted',
|
|
107
|
+
path: entry.path,
|
|
108
|
+
message: `${entry.path}: modifies an upstream file that is not allowlisted.\n` +
|
|
109
|
+
' Move the change into a new fork-owned file, or add the path to ' +
|
|
110
|
+
`${ALLOWLIST_FILE}\n` +
|
|
111
|
+
' with the reason the fork must diverge here.',
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (needsReason(reason)) {
|
|
116
|
+
findings.push({
|
|
117
|
+
kind: 'missing-reason',
|
|
118
|
+
path: entry.path,
|
|
119
|
+
message: `${entry.path}: allowlisted with a placeholder reason.\n` +
|
|
120
|
+
` Replace the ${REASON_PLACEHOLDER} in ${ALLOWLIST_FILE} with why ` +
|
|
121
|
+
'this file must diverge.',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const touchedSet = new Set(touched);
|
|
126
|
+
const warnings = [];
|
|
127
|
+
for (const path of input.allowlist.keys()) {
|
|
128
|
+
if (!touchedSet.has(path)) {
|
|
129
|
+
warnings.push(`${path}: allowlisted but no longer modified — regenerate to drop it.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { findings, warnings, touched };
|
|
133
|
+
}
|
|
134
|
+
function git(repo, args, stdin) {
|
|
135
|
+
const result = spawnSync('git', ['-C', repo, ...args], {
|
|
136
|
+
input: stdin,
|
|
137
|
+
encoding: 'utf-8',
|
|
138
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
139
|
+
});
|
|
140
|
+
if (result.error)
|
|
141
|
+
throw result.error;
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
export function repoRoot(from) {
|
|
145
|
+
const result = git(from, ['rev-parse', '--show-toplevel']);
|
|
146
|
+
if (result.status !== 0) {
|
|
147
|
+
throw new Error(`not a git repository: ${from}`);
|
|
148
|
+
}
|
|
149
|
+
return result.stdout.trim();
|
|
150
|
+
}
|
|
151
|
+
export function hasCommit(repo, rev) {
|
|
152
|
+
return git(repo, ['cat-file', '-e', `${rev}^{commit}`]).status === 0;
|
|
153
|
+
}
|
|
154
|
+
export function diffAgainst(repo, rev) {
|
|
155
|
+
const result = git(repo, [
|
|
156
|
+
'diff',
|
|
157
|
+
'--no-renames',
|
|
158
|
+
'--name-status',
|
|
159
|
+
'-z',
|
|
160
|
+
rev,
|
|
161
|
+
'--',
|
|
162
|
+
]);
|
|
163
|
+
if (result.status !== 0) {
|
|
164
|
+
throw new Error(`git diff against ${rev} failed: ${result.stderr.trim()}`);
|
|
165
|
+
}
|
|
166
|
+
return parseNameStatus(result.stdout);
|
|
167
|
+
}
|
|
168
|
+
export function upstreamPaths(repo, rev, paths) {
|
|
169
|
+
const out = new Set();
|
|
170
|
+
if (paths.length === 0)
|
|
171
|
+
return out;
|
|
172
|
+
const batchable = paths.filter((p) => !p.includes('\n'));
|
|
173
|
+
const input = batchable.map((p) => `${rev}:${p}`).join('\n') + '\n';
|
|
174
|
+
const result = git(repo, ['cat-file', '--batch-check'], input);
|
|
175
|
+
const lines = result.stdout.split('\n').filter((l) => l !== '');
|
|
176
|
+
const aligned = lines.length === batchable.length;
|
|
177
|
+
if (aligned) {
|
|
178
|
+
lines.forEach((line, index) => {
|
|
179
|
+
if (/ blob \d+$/.test(line))
|
|
180
|
+
out.add(batchable[index]);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const leftover = aligned ? paths.filter((p) => p.includes('\n')) : paths;
|
|
184
|
+
for (const path of leftover) {
|
|
185
|
+
const probe = git(repo, ['cat-file', '-t', `${rev}:${path}`]);
|
|
186
|
+
if (probe.status === 0 && probe.stdout.trim() === 'blob')
|
|
187
|
+
out.add(path);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
// @lat: [[upstream-guard#The upstream guard#The sync point]]
|
|
192
|
+
export function parseSyncPoint(text) {
|
|
193
|
+
for (const raw of text.split('\n')) {
|
|
194
|
+
const line = raw.replace(/\r$/, '').trim();
|
|
195
|
+
if (line === '' || line.startsWith('#'))
|
|
196
|
+
continue;
|
|
197
|
+
return line.split(/\s/)[0];
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
export function renderSyncPoint(previous, rev) {
|
|
202
|
+
const kept = previous
|
|
203
|
+
.split('\n')
|
|
204
|
+
.filter((raw) => {
|
|
205
|
+
const line = raw.replace(/\r$/, '').trim();
|
|
206
|
+
return line === '' || line.startsWith('#');
|
|
207
|
+
})
|
|
208
|
+
.join('\n')
|
|
209
|
+
.replace(/\n+$/, '');
|
|
210
|
+
return `${kept}\n\n${rev}\n`;
|
|
211
|
+
}
|
|
212
|
+
export function isAncestorOfHead(repo, rev) {
|
|
213
|
+
return git(repo, ['merge-base', '--is-ancestor', rev, 'HEAD']).status === 0;
|
|
214
|
+
}
|
|
215
|
+
export function isShallow(repo) {
|
|
216
|
+
const result = git(repo, ['rev-parse', '--is-shallow-repository']);
|
|
217
|
+
return result.stdout.trim() === 'true';
|
|
218
|
+
}
|
|
219
|
+
// @lat: [[upstream-guard#The upstream guard#Running it in CI]]
|
|
220
|
+
export function fetchSyncPoint(repo, rev) {
|
|
221
|
+
const result = git(repo, ['fetch', '--no-tags', '--depth=1', 'origin', rev]);
|
|
222
|
+
return result.status === 0 && hasCommit(repo, rev);
|
|
223
|
+
}
|
|
224
|
+
function pathFor(repo, file) {
|
|
225
|
+
return isAbsolute(file) ? file : resolve(repo, file);
|
|
226
|
+
}
|
|
227
|
+
function allowlistPathFor(options) {
|
|
228
|
+
return pathFor(options.repo, options.allowlistFile);
|
|
229
|
+
}
|
|
230
|
+
function syncPointPathFor(options) {
|
|
231
|
+
return pathFor(options.repo, options.syncPointFile ?? SYNC_POINT_FILE);
|
|
232
|
+
}
|
|
233
|
+
function loadAllowlist(file) {
|
|
234
|
+
if (!existsSync(file))
|
|
235
|
+
return { entries: [], errors: [] };
|
|
236
|
+
return parseAllowlist(readFileSync(file, 'utf-8'));
|
|
237
|
+
}
|
|
238
|
+
function fail(report) {
|
|
239
|
+
return { ok: false, report, findings: [], warnings: [] };
|
|
240
|
+
}
|
|
241
|
+
export function runGuard(options) {
|
|
242
|
+
const root = repoRoot(options.repo);
|
|
243
|
+
const opts = { ...options, repo: root };
|
|
244
|
+
const syncFile = syncPointPathFor(opts);
|
|
245
|
+
if (opts.setSyncPoint !== undefined) {
|
|
246
|
+
return recordSyncPoint(root, syncFile, opts.setSyncPoint);
|
|
247
|
+
}
|
|
248
|
+
let syncPoint = opts.syncPoint;
|
|
249
|
+
if (syncPoint === undefined) {
|
|
250
|
+
if (!existsSync(syncFile)) {
|
|
251
|
+
return fail(`upstream-guard: no sync point recorded at ${syncFile}\n` +
|
|
252
|
+
' The guard measures everything from the last upstream commit ' +
|
|
253
|
+
'merged\n' +
|
|
254
|
+
' into this fork. Record it:\n' +
|
|
255
|
+
' node dist/src/fork/upstream-guard-cli.js --set-sync-point ' +
|
|
256
|
+
'<rev>\n');
|
|
257
|
+
}
|
|
258
|
+
syncPoint = parseSyncPoint(readFileSync(syncFile, 'utf-8'));
|
|
259
|
+
if (syncPoint === undefined || syncPoint === '') {
|
|
260
|
+
return fail(`upstream-guard: ${syncFile} holds no revision.\n` +
|
|
261
|
+
' It must contain one commit id on a line of its own.\n');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const present = hasCommit(root, syncPoint) ||
|
|
265
|
+
(opts.fetch && fetchSyncPoint(root, syncPoint));
|
|
266
|
+
if (!present) {
|
|
267
|
+
return fail(`upstream-guard: sync point ${syncPoint} is not in this repository.\n` +
|
|
268
|
+
' A shallow clone does not carry it. Fetch it first:\n' +
|
|
269
|
+
` git fetch --no-tags --depth=1 origin ${syncPoint}\n`);
|
|
270
|
+
}
|
|
271
|
+
// A sync point off the current history makes every number below
|
|
272
|
+
// meaningless — the diff would report upstream's own commits as the fork's.
|
|
273
|
+
// Truncated history cannot answer the question, so shallow clones skip it.
|
|
274
|
+
if (!isShallow(root) && !isAncestorOfHead(root, syncPoint)) {
|
|
275
|
+
return fail(`upstream-guard: sync point ${syncPoint.slice(0, 7)} is not an ancestor ` +
|
|
276
|
+
'of HEAD.\n' +
|
|
277
|
+
' Merge that upstream commit before recording it, or record the ' +
|
|
278
|
+
'one\n' +
|
|
279
|
+
' this branch actually descends from.\n');
|
|
280
|
+
}
|
|
281
|
+
const file = allowlistPathFor(opts);
|
|
282
|
+
const diff = diffAgainst(root, syncPoint);
|
|
283
|
+
const upstream = upstreamPaths(root, syncPoint, diff.map((d) => d.path));
|
|
284
|
+
if (opts.regenerate) {
|
|
285
|
+
return regenerateInto(file, diff, upstream);
|
|
286
|
+
}
|
|
287
|
+
const parsed = loadAllowlist(file);
|
|
288
|
+
if (!existsSync(file)) {
|
|
289
|
+
return fail(`upstream-guard: allowlist not found at ${file}\n`);
|
|
290
|
+
}
|
|
291
|
+
const map = new Map(parsed.entries.map((e) => [e.path, e.reason]));
|
|
292
|
+
const result = classify({ diff, upstream, allowlist: map });
|
|
293
|
+
const findings = [
|
|
294
|
+
...parsed.errors.map((message) => ({ kind: 'allowlist-error', path: file, message })),
|
|
295
|
+
...result.findings,
|
|
296
|
+
];
|
|
297
|
+
const lines = [];
|
|
298
|
+
if (findings.length > 0) {
|
|
299
|
+
lines.push(`upstream-guard: ${findings.length} violation(s) against sync point ` +
|
|
300
|
+
`${syncPoint.slice(0, 7)}`, '');
|
|
301
|
+
for (const finding of findings)
|
|
302
|
+
lines.push(` ${finding.message}`, '');
|
|
303
|
+
lines.push('The fork keeps its diff small: new behaviour goes in new fork-owned ' +
|
|
304
|
+
'files,', 'and an upstream file takes an import plus one call line. See ' +
|
|
305
|
+
'lat.md/fork.md.');
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
lines.push(`upstream-guard: ok — ${result.touched.length} upstream file(s) ` +
|
|
309
|
+
'modified, all allowlisted.');
|
|
310
|
+
}
|
|
311
|
+
for (const warning of result.warnings)
|
|
312
|
+
lines.push(` warning: ${warning}`);
|
|
313
|
+
return {
|
|
314
|
+
ok: findings.length === 0,
|
|
315
|
+
report: lines.join('\n') + '\n',
|
|
316
|
+
findings,
|
|
317
|
+
warnings: result.warnings,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// @lat: [[upstream-guard#The upstream guard#The sync point]]
|
|
321
|
+
function recordSyncPoint(repo, file, rev) {
|
|
322
|
+
const resolved = git(repo, ['rev-parse', `${rev}^{commit}`]);
|
|
323
|
+
if (resolved.status !== 0) {
|
|
324
|
+
return fail(`upstream-guard: ${rev} is not a commit in this repository.\n`);
|
|
325
|
+
}
|
|
326
|
+
const sha = resolved.stdout.trim();
|
|
327
|
+
if (!isShallow(repo) && !isAncestorOfHead(repo, sha)) {
|
|
328
|
+
return fail(`upstream-guard: ${sha.slice(0, 7)} is not an ancestor of HEAD.\n` +
|
|
329
|
+
' Record a sync point only once its merge has landed.\n');
|
|
330
|
+
}
|
|
331
|
+
const previous = existsSync(file) ? readFileSync(file, 'utf-8') : '';
|
|
332
|
+
writeFileSync(file, renderSyncPoint(previous, sha), 'utf-8');
|
|
333
|
+
return {
|
|
334
|
+
ok: true,
|
|
335
|
+
report: `upstream-guard: sync point is now ${sha}\n` +
|
|
336
|
+
' regenerate the allowlist next — the set of files the fork ' +
|
|
337
|
+
'diverges in has moved.\n',
|
|
338
|
+
findings: [],
|
|
339
|
+
warnings: [],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function regenerateInto(file, diff, upstream) {
|
|
343
|
+
const existing = new Map(loadAllowlist(file).entries.map((e) => [e.path, e.reason]));
|
|
344
|
+
const entries = [];
|
|
345
|
+
const deletions = [];
|
|
346
|
+
const fresh = [];
|
|
347
|
+
for (const entry of diff) {
|
|
348
|
+
if (!upstream.has(entry.path))
|
|
349
|
+
continue;
|
|
350
|
+
if (entry.status.startsWith('D')) {
|
|
351
|
+
deletions.push({
|
|
352
|
+
kind: 'deletion',
|
|
353
|
+
path: entry.path,
|
|
354
|
+
message: `${entry.path}: deletes an upstream file, which is never ` +
|
|
355
|
+
'allowlistable — left out of the regenerated list.',
|
|
356
|
+
});
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const reason = existing.get(entry.path) ??
|
|
360
|
+
`${REASON_PLACEHOLDER}: why must this upstream file diverge?`;
|
|
361
|
+
if (!existing.has(entry.path))
|
|
362
|
+
fresh.push(entry.path);
|
|
363
|
+
entries.push({ path: entry.path, reason, line: 0 });
|
|
364
|
+
}
|
|
365
|
+
writeFileSync(file, renderAllowlist(entries), 'utf-8');
|
|
366
|
+
const lines = [
|
|
367
|
+
`upstream-guard: wrote ${entries.length} entr(ies) to ${file}`,
|
|
368
|
+
];
|
|
369
|
+
for (const path of fresh) {
|
|
370
|
+
lines.push(` new entry needs a reason: ${path}`);
|
|
371
|
+
}
|
|
372
|
+
for (const finding of deletions)
|
|
373
|
+
lines.push(` ${finding.message}`);
|
|
374
|
+
return {
|
|
375
|
+
ok: deletions.length === 0,
|
|
376
|
+
report: lines.join('\n') + '\n',
|
|
377
|
+
findings: deletions,
|
|
378
|
+
warnings: [],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const USAGE = `Usage: node dist/src/fork/upstream-guard-cli.js [options]
|
|
382
|
+
|
|
383
|
+
--regenerate rewrite the allowlist from the current diff
|
|
384
|
+
--fetch fetch the sync point from origin if it is absent
|
|
385
|
+
--set-sync-point <rev> record <rev> as the last merged upstream commit
|
|
386
|
+
--sync-point <rev> check against <rev> instead of the recorded one
|
|
387
|
+
--repo <dir> default the current working directory
|
|
388
|
+
--allowlist <file> default <repo>/${ALLOWLIST_FILE}
|
|
389
|
+
--sync-point-file <f> default <repo>/${SYNC_POINT_FILE}
|
|
390
|
+
-h, --help this text
|
|
391
|
+
`;
|
|
392
|
+
export function parseArgs(argv) {
|
|
393
|
+
const options = {
|
|
394
|
+
repo: process.cwd(),
|
|
395
|
+
allowlistFile: ALLOWLIST_FILE,
|
|
396
|
+
regenerate: false,
|
|
397
|
+
fetch: false,
|
|
398
|
+
};
|
|
399
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
400
|
+
const arg = argv[i];
|
|
401
|
+
if (arg === '-h' || arg === '--help')
|
|
402
|
+
return { help: true };
|
|
403
|
+
else if (arg === '--regenerate' || arg === '--write')
|
|
404
|
+
options.regenerate = true;
|
|
405
|
+
else if (arg === '--fetch')
|
|
406
|
+
options.fetch = true;
|
|
407
|
+
else if (arg === '--set-sync-point')
|
|
408
|
+
options.setSyncPoint = argv[++i] ?? '';
|
|
409
|
+
else if (arg === '--sync-point')
|
|
410
|
+
options.syncPoint = argv[++i] ?? '';
|
|
411
|
+
else if (arg === '--sync-point-file')
|
|
412
|
+
options.syncPointFile = argv[++i] ?? '';
|
|
413
|
+
else if (arg === '--repo')
|
|
414
|
+
options.repo = argv[++i] ?? '';
|
|
415
|
+
else if (arg === '--allowlist')
|
|
416
|
+
options.allowlistFile = argv[++i] ?? '';
|
|
417
|
+
else
|
|
418
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
419
|
+
}
|
|
420
|
+
return options;
|
|
421
|
+
}
|
|
422
|
+
export function main(argv) {
|
|
423
|
+
let options;
|
|
424
|
+
try {
|
|
425
|
+
options = parseArgs(argv);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
process.stderr.write(`${error.message}\n\n${USAGE}`);
|
|
429
|
+
return 2;
|
|
430
|
+
}
|
|
431
|
+
if ('help' in options) {
|
|
432
|
+
process.stdout.write(USAGE);
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
let result;
|
|
436
|
+
try {
|
|
437
|
+
result = runGuard(options);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
process.stderr.write(`upstream-guard: ${error.message}\n`);
|
|
441
|
+
return 2;
|
|
442
|
+
}
|
|
443
|
+
process.stdout.write(result.report);
|
|
444
|
+
return result.ok ? 0 : 1;
|
|
445
|
+
}
|
package/dist/src/graph-export.js
CHANGED
|
@@ -8,15 +8,18 @@
|
|
|
8
8
|
// `quoteUntrusted`) is correct here: node ids and labels must not be
|
|
9
9
|
// truncated, and quoting them would corrupt them as identifiers.
|
|
10
10
|
import { execFile } from 'node:child_process';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
11
12
|
import { join, relative } from 'node:path';
|
|
12
13
|
import { promisify } from 'node:util';
|
|
13
|
-
import { extractRefs, buildFileIndex, buildSectionSlugIndex, parseSections, parseFrontmatter, resolveRef, } from '
|
|
14
|
-
import { scanCodeRefs } from '
|
|
15
|
-
import { DIATAXIS_MODES, MODE_DIRS } from '
|
|
16
|
-
import { readProvenance } from '
|
|
17
|
-
import { cleanUntrustedId } from '
|
|
18
|
-
import { toPosix } from '
|
|
14
|
+
import { extractRefs, buildFileIndex, buildSectionSlugIndex, parseSections, parseFrontmatter, resolveRef, } from '@plebeiantech/lat.md-core/lattice';
|
|
15
|
+
import { scanCodeRefs } from '@plebeiantech/lat.md-core/code-refs';
|
|
16
|
+
import { DIATAXIS_MODES, MODE_DIRS, } from '@plebeiantech/lat.md-core/cli/check-mode';
|
|
17
|
+
import { readProvenance } from '@plebeiantech/lat.md-core/cli/check-status';
|
|
18
|
+
import { cleanUntrustedId } from '@plebeiantech/lat.md-core/untrusted';
|
|
19
|
+
import { toPosix } from '@plebeiantech/lat.md-core/path';
|
|
19
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
/** Bytes. Node's `execFile` default is 1 MB, which a large document exceeds. */
|
|
22
|
+
const GIT_MAX_BUFFER = 256 * 1024 * 1024;
|
|
20
23
|
function docId(relPath) {
|
|
21
24
|
return `doc:${relPath}`;
|
|
22
25
|
}
|
|
@@ -41,6 +44,15 @@ function modeForFile(relPath, fm) {
|
|
|
41
44
|
}
|
|
42
45
|
return undefined;
|
|
43
46
|
}
|
|
47
|
+
/** sha256 over lines (startLine, endLine], with trailing blanks normalized. */
|
|
48
|
+
function hashSectionBody(lines, section) {
|
|
49
|
+
const body = lines
|
|
50
|
+
.slice(section.startLine, section.endLine)
|
|
51
|
+
.map((line) => line.replace(/[ \t]+$/, ''))
|
|
52
|
+
.join('\n')
|
|
53
|
+
.trim();
|
|
54
|
+
return createHash('sha256').update(body, 'utf-8').digest('hex');
|
|
55
|
+
}
|
|
44
56
|
function tagsForFile(fm) {
|
|
45
57
|
const raw = fm['tags'];
|
|
46
58
|
if (Array.isArray(raw))
|
|
@@ -107,10 +119,12 @@ export async function buildGraph(files, projectRoot, includeCode) {
|
|
|
107
119
|
addNode({ id: tId, type: 'tag', label: cleanUntrustedId(tag) });
|
|
108
120
|
addEdge({ from: dId, to: tId, type: 'tag' });
|
|
109
121
|
}
|
|
122
|
+
const bodyLines = f.content.replace(/\r\n/g, '\n').split('\n');
|
|
110
123
|
const walkSection = (section, parentId) => {
|
|
111
124
|
const sId = ensureSectionNode(section.id, section.heading);
|
|
112
125
|
const node = nodes.get(sId);
|
|
113
126
|
node.file = f.relPath;
|
|
127
|
+
node.bodyHash = hashSectionBody(bodyLines, section);
|
|
114
128
|
addEdge({ from: parentId, to: sId, type: 'contains' });
|
|
115
129
|
for (const child of section.children) {
|
|
116
130
|
walkSection(child, sId);
|
|
@@ -129,6 +143,13 @@ export async function buildGraph(files, projectRoot, includeCode) {
|
|
|
129
143
|
};
|
|
130
144
|
flatten(allSections);
|
|
131
145
|
const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
|
|
146
|
+
const canonicalIds = new Map();
|
|
147
|
+
for (const s of flat) {
|
|
148
|
+
const key = s.id.toLowerCase();
|
|
149
|
+
if (!canonicalIds.has(key))
|
|
150
|
+
canonicalIds.set(key, s.id);
|
|
151
|
+
}
|
|
152
|
+
const canonicalize = (id) => canonicalIds.get(id.toLowerCase());
|
|
132
153
|
const fileIndex = buildFileIndex(allSections);
|
|
133
154
|
const slugIndex = buildSectionSlugIndex(allSections);
|
|
134
155
|
// Wiki links: section -> section (or section -> code target).
|
|
@@ -137,9 +158,10 @@ export async function buildGraph(files, projectRoot, includeCode) {
|
|
|
137
158
|
const refs = extractRefs(absPath, f.content, projectRoot);
|
|
138
159
|
for (const ref of refs) {
|
|
139
160
|
const { resolved, ambiguous } = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
|
|
140
|
-
const fromId = ensureSectionNode(ref.fromSection);
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
const fromId = ensureSectionNode(canonicalize(ref.fromSection) ?? ref.fromSection);
|
|
162
|
+
const canonicalTarget = ambiguous ? undefined : canonicalize(resolved);
|
|
163
|
+
if (canonicalTarget !== undefined) {
|
|
164
|
+
const toId = ensureSectionNode(canonicalTarget);
|
|
143
165
|
addEdge({ from: fromId, to: toId, type: 'wikilink' });
|
|
144
166
|
}
|
|
145
167
|
else {
|
|
@@ -161,9 +183,8 @@ export async function buildGraph(files, projectRoot, includeCode) {
|
|
|
161
183
|
const label = `${ref.file}:${ref.line}`;
|
|
162
184
|
const cId = codeId(label);
|
|
163
185
|
addNode({ id: cId, type: 'code', label: cleanUntrustedId(label) });
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
: ensureSectionNode(ref.target);
|
|
186
|
+
const canonicalTarget = ambiguous ? undefined : canonicalize(resolved);
|
|
187
|
+
const toId = ensureSectionNode(canonicalTarget ?? ref.target);
|
|
167
188
|
addEdge({ from: cId, to: toId, type: 'code-ref' });
|
|
168
189
|
}
|
|
169
190
|
}
|
|
@@ -171,7 +192,7 @@ export async function buildGraph(files, projectRoot, includeCode) {
|
|
|
171
192
|
}
|
|
172
193
|
/** Read the working tree's lat.md files straight off disk. */
|
|
173
194
|
export async function loadWorkingTreeFiles(latticeDir, projectRoot) {
|
|
174
|
-
const { listLatticeFiles } = await import('
|
|
195
|
+
const { listLatticeFiles } = await import('@plebeiantech/lat.md-core/lattice');
|
|
175
196
|
const { readFile } = await import('node:fs/promises');
|
|
176
197
|
const paths = await listLatticeFiles(latticeDir);
|
|
177
198
|
const files = [];
|
|
@@ -190,20 +211,18 @@ export async function loadRevisionFiles(projectRoot, latticeDir, rev) {
|
|
|
190
211
|
const latRelDir = toPosix(relative(projectRoot, latticeDir));
|
|
191
212
|
let lsOutput;
|
|
192
213
|
try {
|
|
193
|
-
const { stdout } = await execFileAsync('git', ['ls-tree', '-r', '--name-only', rev, '--', latRelDir], { cwd: projectRoot });
|
|
214
|
+
const { stdout } = await execFileAsync('git', ['ls-tree', '-r', '-z', '--name-only', rev, '--', latRelDir], { cwd: projectRoot, maxBuffer: GIT_MAX_BUFFER });
|
|
194
215
|
lsOutput = stdout;
|
|
195
216
|
}
|
|
196
217
|
catch (err) {
|
|
197
218
|
throw new Error(`failed to list "${latRelDir}" at revision "${rev}": ${err instanceof Error ? err.message : String(err)}`);
|
|
198
219
|
}
|
|
199
|
-
const paths = lsOutput
|
|
200
|
-
.split('\n')
|
|
201
|
-
.map((l) => l.trim())
|
|
202
|
-
.filter((l) => l.endsWith('.md'));
|
|
220
|
+
const paths = lsOutput.split('\0').filter((l) => l.endsWith('.md'));
|
|
203
221
|
const files = [];
|
|
204
222
|
for (const p of paths) {
|
|
205
223
|
const { stdout } = await execFileAsync('git', ['show', `${rev}:${p}`], {
|
|
206
224
|
cwd: projectRoot,
|
|
225
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
207
226
|
});
|
|
208
227
|
files.push({ relPath: p, content: stdout });
|
|
209
228
|
}
|
|
@@ -221,7 +240,7 @@ export function diffGraphs(before, after) {
|
|
|
221
240
|
if (!prior) {
|
|
222
241
|
added.push(id);
|
|
223
242
|
}
|
|
224
|
-
else if (prior.label !== node.label) {
|
|
243
|
+
else if (prior.bodyHash !== node.bodyHash || prior.label !== node.label) {
|
|
225
244
|
changed.push(id);
|
|
226
245
|
}
|
|
227
246
|
}
|
|
@@ -84,16 +84,45 @@ function unquote(value) {
|
|
|
84
84
|
}
|
|
85
85
|
return value;
|
|
86
86
|
}
|
|
87
|
+
/** The body of a leading `---` / `---` block, or undefined if there is none. */
|
|
88
|
+
function frontmatterBlock(text) {
|
|
89
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
90
|
+
return match ? match[1] : undefined;
|
|
91
|
+
}
|
|
87
92
|
/** Pull a `key: value` line out of YAML-ish frontmatter, quotes stripped. */
|
|
88
93
|
function frontmatterField(text, key) {
|
|
89
|
-
const
|
|
90
|
-
|
|
94
|
+
const block = frontmatterBlock(text);
|
|
95
|
+
if (block === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
const re = new RegExp(`^${escapeRegExp(key)}:[ \\t]*(.*)$`, 'm');
|
|
98
|
+
const match = re.exec(block);
|
|
91
99
|
return match ? unquote(match[1].trim()) : undefined;
|
|
92
100
|
}
|
|
93
|
-
function
|
|
101
|
+
function severedTailBytes(buf) {
|
|
102
|
+
for (let back = 1; back <= 3 && back <= buf.length; back++) {
|
|
103
|
+
const byte = buf[buf.length - back];
|
|
104
|
+
if ((byte & 0xc0) === 0x80)
|
|
105
|
+
continue;
|
|
106
|
+
let width = 0;
|
|
107
|
+
if ((byte & 0xe0) === 0xc0)
|
|
108
|
+
width = 2;
|
|
109
|
+
else if ((byte & 0xf0) === 0xe0)
|
|
110
|
+
width = 3;
|
|
111
|
+
else if ((byte & 0xf8) === 0xf0)
|
|
112
|
+
width = 4;
|
|
113
|
+
return width > back ? back : 0;
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
export function readFileCapped(path) {
|
|
94
118
|
try {
|
|
95
119
|
const buf = readFileSync(path);
|
|
96
|
-
|
|
120
|
+
if (buf.length <= MAX_FILE_BYTES)
|
|
121
|
+
return buf.toString('utf-8');
|
|
122
|
+
const capped = buf.subarray(0, MAX_FILE_BYTES);
|
|
123
|
+
return capped
|
|
124
|
+
.subarray(0, capped.length - severedTailBytes(capped))
|
|
125
|
+
.toString('utf-8');
|
|
97
126
|
}
|
|
98
127
|
catch {
|
|
99
128
|
return undefined;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { parseFrontmatter } from '
|
|
4
|
-
import { UNTRUSTED_NOTICE, quoteUntrusted, cleanUntrustedId, } from '
|
|
3
|
+
import { parseFrontmatter } from '@plebeiantech/lat.md-core/lattice';
|
|
4
|
+
import { UNTRUSTED_NOTICE, quoteUntrusted, cleanUntrustedId, } from '@plebeiantech/lat.md-core/untrusted';
|
|
5
5
|
import { tagsToTerms, capHits } from './ranking.js';
|
|
6
6
|
const STORE_GROUP_LABELS = {
|
|
7
7
|
cq: 'cq:',
|