@plebeiantech/lat.md 0.12.3-fork.13 → 0.12.3-fork.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/src/cli/check-context.d.ts +56 -0
- package/dist/src/cli/check-context.js +215 -0
- package/dist/src/cli/check-coverage.js +9 -5
- package/dist/src/cli/check-frontmatter.js +1 -1
- package/dist/src/cli/check-mode.js +1 -1
- package/dist/src/cli/check-status.js +1 -1
- package/dist/src/cli/check.d.ts +19 -12
- package/dist/src/cli/check.js +259 -206
- package/dist/src/cli/context.js +1 -1
- package/dist/src/cli/expand.d.ts +1 -2
- package/dist/src/cli/expand.js +52 -5
- package/dist/src/cli/external.d.ts +11 -0
- package/dist/src/cli/external.js +185 -0
- package/dist/src/cli/fork-instructions.d.ts +1 -0
- package/dist/src/cli/fork-instructions.js +3 -2
- package/dist/src/cli/hook.d.ts +26 -0
- package/dist/src/cli/hook.js +112 -40
- package/dist/src/cli/index.js +55 -3
- package/dist/src/cli/init.d.ts +1 -0
- package/dist/src/cli/init.js +40 -2
- package/dist/src/cli/locate.js +4 -3
- package/dist/src/cli/refs.d.ts +2 -1
- package/dist/src/cli/refs.js +88 -43
- package/dist/src/cli/reindex.js +2 -1
- package/dist/src/cli/search.d.ts +12 -3
- package/dist/src/cli/search.js +45 -49
- package/dist/src/cli/section.d.ts +3 -1
- package/dist/src/cli/section.js +114 -38
- package/dist/src/code-refs.d.ts +17 -5
- package/dist/src/code-refs.js +246 -65
- package/dist/src/context.d.ts +3 -0
- package/dist/src/document-formats.d.ts +7 -0
- package/dist/src/document-formats.js +39 -0
- package/dist/src/extensions/alert-marker.d.ts +25 -0
- package/dist/src/extensions/alert-marker.js +55 -0
- package/dist/src/external-documents.d.ts +56 -0
- package/dist/src/external-documents.js +404 -0
- package/dist/src/external-sources.d.ts +136 -0
- package/dist/src/external-sources.js +1098 -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/frontmatter-fields.d.ts +36 -0
- package/dist/src/fork/frontmatter-fields.js +23 -0
- package/dist/src/format.d.ts +5 -2
- package/dist/src/format.js +8 -3
- package/dist/src/graph-export.js +1 -1
- package/dist/src/lattice-model.d.ts +77 -0
- package/dist/src/lattice-model.js +464 -0
- package/dist/src/lattice.d.ts +9 -104
- package/dist/src/lattice.js +69 -516
- package/dist/src/markdown-analysis-cache.d.ts +20 -0
- package/dist/src/markdown-analysis-cache.js +139 -0
- package/dist/src/markdown-analysis-worker.d.ts +16 -0
- package/dist/src/markdown-analysis-worker.js +33 -0
- package/dist/src/markdown-analysis.d.ts +50 -0
- package/dist/src/markdown-analysis.js +139 -0
- package/dist/src/markdown-analyzer-loader.d.ts +4 -0
- package/dist/src/markdown-analyzer-loader.js +13 -0
- package/dist/src/markdown-validation.d.ts +25 -0
- package/dist/src/markdown-validation.js +104 -0
- package/dist/src/mcp/server.js +27 -11
- package/dist/src/parser-cache.d.ts +17 -0
- package/dist/src/parser-cache.js +65 -0
- package/dist/src/parser-import.d.ts +8 -0
- package/dist/src/parser-import.js +1 -0
- package/dist/src/parser.js +80 -5
- package/dist/src/path.d.ts +2 -0
- package/dist/src/path.js +4 -0
- package/dist/src/profiler.d.ts +16 -0
- package/dist/src/profiler.js +81 -0
- package/dist/src/project-analysis.d.ts +59 -0
- package/dist/src/project-analysis.js +233 -0
- package/dist/src/project-discovery.d.ts +6 -0
- package/dist/src/project-discovery.js +30 -0
- package/dist/src/repository-path.d.ts +11 -0
- package/dist/src/repository-path.js +52 -0
- package/dist/src/search/db.d.ts +1 -1
- package/dist/src/search/db.js +2 -2
- package/dist/src/search/embedder.d.ts +2 -1
- package/dist/src/search/embedder.js +6 -5
- package/dist/src/search/index.d.ts +2 -1
- package/dist/src/search/index.js +10 -8
- package/dist/src/search/query.d.ts +20 -0
- package/dist/src/search/query.js +67 -0
- package/dist/src/search/search.d.ts +3 -1
- package/dist/src/search/search.js +5 -2
- package/dist/src/source-formats.d.ts +7 -0
- package/dist/src/source-formats.js +26 -0
- package/dist/src/source-parser.d.ts +44 -4
- package/dist/src/source-parser.js +494 -57
- 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-B7quqPb8.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/abnfDiagram-VCTEODGH-V7-T6qsl.js +1 -0
- package/dist/src/view/client/assets/arc-BnwR8Bu7.js +1 -0
- package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +36 -0
- package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-DETl8e_H.js +129 -0
- package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-DJRAT1Ve.js +38 -0
- package/dist/src/view/client/assets/channel-BwQbnWCN.js +1 -0
- package/dist/src/view/client/assets/chunk-2Q5K7J3B-C0TmOC1Q.js +1 -0
- package/dist/src/view/client/assets/chunk-5VM5RSS4-dfBOrYE6.js +15 -0
- package/dist/src/view/client/assets/chunk-F27PBJKO-DAnks99K.js +1 -0
- package/dist/src/view/client/assets/chunk-IMKFNOWR-CPjB07if.js +231 -0
- package/dist/src/view/client/assets/chunk-JWPE2WC7-VvU_gZI6.js +1 -0
- package/dist/src/view/client/assets/chunk-POPQ4Y6H-DDUbVF55.js +1 -0
- package/dist/src/view/client/assets/chunk-SVP7TREG-BHROkr6D.js +88 -0
- package/dist/src/view/client/assets/chunk-TICWLB2K-95PzGIOg.js +206 -0
- package/dist/src/view/client/assets/chunk-XXDRQBXY-MdmuLhIs.js +1 -0
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +1 -0
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +1 -0
- package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +1 -0
- package/dist/src/view/client/assets/cynefin-OW5HDTMX-CeA8IKWo.js +166 -0
- package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +62 -0
- package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
- package/dist/src/view/client/assets/dagre-GXQ25YYZ-DLQwTa3H.js +4 -0
- package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/src/view/client/assets/diagram-S7CK7UJ4-kbKdIyXG.js +30 -0
- package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +41 -0
- package/dist/src/view/client/assets/diagram-VSXAHHWV-EXJTU8qq.js +3 -0
- package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +24 -0
- package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +24 -0
- package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +1 -0
- package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-C925J5A3.js +99 -0
- package/dist/src/view/client/assets/flowDiagram-HODETNUW-DzoCa-Nq.js +168 -0
- package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +292 -0
- package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +106 -0
- package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
- package/dist/src/view/client/assets/index-D0ajr_Ja.css +1 -0
- package/dist/src/view/client/assets/index-KZcnpXJH.js +321 -0
- package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +2 -0
- package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
- package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-CW4mto2d.js +70 -0
- package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-8R4eacFW.js +139 -0
- package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-Cy9ioXEJ.js +89 -0
- package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
- package/dist/src/view/client/assets/layout-C0MFuhth.js +1 -0
- package/dist/src/view/client/assets/linear-Dv3N4dQR.js +1 -0
- package/dist/src/view/client/assets/logo-small-RR_KBOAT.svg +149 -0
- package/dist/src/view/client/assets/maplibre-gl-DnqZgfUS.js +800 -0
- package/dist/src/view/client/assets/mermaid.core-bds9A7e2.js +310 -0
- package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-DjmGW355.js +96 -0
- package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +1 -0
- package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-CCH2gGR-.js +39 -0
- package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC-CjocPJFz.js +7 -0
- package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +1 -0
- package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-Ct6AoXRr.js +84 -0
- package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-CIpgGN_w.js +40 -0
- package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-DrmLFj6w.js +162 -0
- package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +1 -0
- package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +1 -0
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +8 -0
- package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
- package/dist/src/view/client/assets/timeline-definition-24CTP7MA-BExPu6X5.js +120 -0
- package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-PC1MU3si.js +34 -0
- package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-DNWqU3fa.js +78 -0
- package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-E1uA28_D.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 +12 -0
- package/dist/src/view/document-route.js +93 -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 +187 -0
- package/dist/src/view/git.js +1 -1
- package/dist/src/view/graph.d.ts +3 -2
- package/dist/src/view/graph.js +94 -7
- package/dist/src/view/highlight.d.ts +10 -2
- package/dist/src/view/highlight.js +4540 -1360
- package/dist/src/view/markdown.d.ts +3 -2
- package/dist/src/view/markdown.js +214 -53
- package/dist/src/view/protocol.d.ts +59 -6
- package/dist/src/view/references.d.ts +10 -25
- package/dist/src/view/references.js +117 -107
- package/dist/src/view/repository.d.ts +13 -3
- package/dist/src/view/repository.js +234 -15
- package/dist/src/view/search.d.ts +1 -1
- package/dist/src/view/search.js +3 -7
- package/dist/src/view/server.d.ts +11 -2
- package/dist/src/view/server.js +212 -70
- package/dist/src/view/source-target.js +5 -8
- package/dist/src/view/static-build.d.ts +1 -0
- package/dist/src/view/static-build.js +245 -42
- package/dist/src/view/static-protocol.d.ts +19 -2
- package/dist/src/view/store.d.ts +16 -3
- package/dist/src/view/store.js +228 -36
- package/dist/src/view/table-of-contents.d.ts +2 -2
- package/dist/src/view/table-of-contents.js +2 -22
- package/dist/src/walk.d.ts +5 -3
- package/dist/src/walk.js +102 -8
- package/package.json +46 -8
- 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 +15 -3
- package/templates/cursor-rules.md +12 -3
- package/templates/skill/SKILL.md +22 -1
- 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/cli/expand.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type Section } from '../lattice.js';
|
|
2
1
|
import type { CmdContext, CmdResult } from '../context.js';
|
|
3
2
|
/**
|
|
4
3
|
* Resolve [[refs]] in text and return the expanded output.
|
|
5
4
|
* Returns null if there are no wiki links, or if resolution fails.
|
|
6
5
|
*/
|
|
7
|
-
export declare function expandPrompt(ctx: CmdContext, text: string
|
|
6
|
+
export declare function expandPrompt(ctx: CmdContext, text: string): Promise<string | null>;
|
|
8
7
|
export declare function expandCommand(ctx: CmdContext, text: string): Promise<CmdResult>;
|
package/dist/src/cli/expand.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, relative } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { findSections, } from '../lattice-model.js';
|
|
3
3
|
import { UNTRUSTED_NOTICE, quoteUntrusted, cleanUntrustedId, } from '../untrusted.js';
|
|
4
|
+
import { commandProjectAnalysis, commandProjectSession, } from '../project-analysis.js';
|
|
4
5
|
const WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
|
|
5
6
|
function formatLocation(section, projectRoot) {
|
|
6
7
|
const relPath = relative(process.cwd(), join(projectRoot, section.filePath));
|
|
@@ -10,17 +11,34 @@ function formatLocation(section, projectRoot) {
|
|
|
10
11
|
* Resolve [[refs]] in text and return the expanded output.
|
|
11
12
|
* Returns null if there are no wiki links, or if resolution fails.
|
|
12
13
|
*/
|
|
13
|
-
export async function expandPrompt(ctx, text
|
|
14
|
+
export async function expandPrompt(ctx, text) {
|
|
14
15
|
const refs = [...text.matchAll(WIKI_LINK_RE)];
|
|
15
16
|
if (refs.length === 0)
|
|
16
17
|
return null;
|
|
17
|
-
const allSections =
|
|
18
|
+
const { allSections } = await commandProjectAnalysis(ctx);
|
|
18
19
|
const resolved = new Map();
|
|
20
|
+
const externalResolved = new Map();
|
|
21
|
+
const external = await commandProjectSession(ctx).external();
|
|
19
22
|
const errors = [];
|
|
20
23
|
for (const match of refs) {
|
|
21
24
|
const target = match[1];
|
|
22
|
-
if (resolved.has(target))
|
|
25
|
+
if (resolved.has(target) || externalResolved.has(target))
|
|
23
26
|
continue;
|
|
27
|
+
try {
|
|
28
|
+
if (external.parse(target)) {
|
|
29
|
+
externalResolved.set(target, await external.resolve(target));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
errors.push(error.message);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const unknownExternal = external.unknownTargetMessage(target);
|
|
38
|
+
if (unknownExternal) {
|
|
39
|
+
errors.push(unknownExternal);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
24
42
|
const matches = findSections(allSections, target);
|
|
25
43
|
if (matches.length >= 1) {
|
|
26
44
|
resolved.set(target, {
|
|
@@ -37,12 +55,21 @@ export async function expandPrompt(ctx, text, preloadedSections) {
|
|
|
37
55
|
return null;
|
|
38
56
|
// Replace [[refs]] inline
|
|
39
57
|
let output = text.replace(WIKI_LINK_RE, (_match, target) => {
|
|
58
|
+
if (externalResolved.has(target))
|
|
59
|
+
return `[[${target}]]`;
|
|
40
60
|
const ref = resolved.get(target);
|
|
41
61
|
return `[[${cleanUntrustedId(ref.best.section.id)}]]`;
|
|
42
62
|
});
|
|
43
63
|
// Append context block as nested outliner
|
|
44
64
|
output += '\n\n<lat-context>\n';
|
|
45
65
|
output += UNTRUSTED_NOTICE + '\n';
|
|
66
|
+
for (const [target, value] of externalResolved) {
|
|
67
|
+
output += `* \`[[${target}]]\` is referring to:\n`;
|
|
68
|
+
output += ` * ${value.source.repo} @ ${value.source.commit} via ${value.provider}\n`;
|
|
69
|
+
output += ` * ${value.target.repositoryPath}:${value.startLine}-${value.endLine}\n`;
|
|
70
|
+
for (const line of value.content.split('\n'))
|
|
71
|
+
output += ` | ${line}\n`;
|
|
72
|
+
}
|
|
46
73
|
for (const ref of resolved.values()) {
|
|
47
74
|
const isExact = ref.best.reason === 'exact match' ||
|
|
48
75
|
ref.best.reason.startsWith('file stem expanded');
|
|
@@ -73,9 +100,29 @@ export async function expandCommand(ctx, text) {
|
|
|
73
100
|
return { output: text };
|
|
74
101
|
}
|
|
75
102
|
// Resolution failed — find which ref is broken
|
|
76
|
-
const allSections = await
|
|
103
|
+
const { allSections } = await commandProjectAnalysis(ctx);
|
|
104
|
+
const external = await commandProjectSession(ctx).external();
|
|
77
105
|
for (const match of refs) {
|
|
78
106
|
const target = match[1];
|
|
107
|
+
try {
|
|
108
|
+
if (external.parse(target)) {
|
|
109
|
+
await external.resolve(target);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
output: ctx.styler.red(error.message),
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const unknownExternal = external.unknownTargetMessage(target);
|
|
120
|
+
if (unknownExternal) {
|
|
121
|
+
return {
|
|
122
|
+
output: ctx.styler.red(unknownExternal),
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
79
126
|
const matches = findSections(allSections, target);
|
|
80
127
|
if (matches.length === 0) {
|
|
81
128
|
const s = ctx.styler;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CmdContext, CmdResult } from '../context.js';
|
|
2
|
+
export type ExternalAddOptions = {
|
|
3
|
+
commit?: string;
|
|
4
|
+
prefix?: string;
|
|
5
|
+
defaultFileExtension?: string;
|
|
6
|
+
strategy?: string;
|
|
7
|
+
fetchUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function externalListCommand(ctx: CmdContext, json?: boolean): Promise<CmdResult>;
|
|
10
|
+
export declare function externalShowCommand(ctx: CmdContext, query: string, json?: boolean): Promise<CmdResult>;
|
|
11
|
+
export declare function externalAddCommand(ctx: CmdContext, handleValue: string | undefined, repoValue: string | undefined, options: ExternalAddOptions): Promise<CmdResult>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { addCanonicalExternalSource, describeExternalSources, inferExternalFetchUrl, loadExternalSources, normalizeExternalDefaultFileExtension, normalizeExternalRepoUrl, parseExternalTarget, resolveExternalCommit, } from '../external-sources.js';
|
|
3
|
+
import { selectMenu } from './select-menu.js';
|
|
4
|
+
function shellArg(value) {
|
|
5
|
+
return /^[a-zA-Z0-9_./:@=-]+$/.test(value)
|
|
6
|
+
? value
|
|
7
|
+
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
8
|
+
}
|
|
9
|
+
function formatDescription(value) {
|
|
10
|
+
const lines = [
|
|
11
|
+
`${value.handle}`,
|
|
12
|
+
` Repository: ${value.repo}`,
|
|
13
|
+
` Commit: ${value.effectiveCommit}${value.effectiveCommit !== value.canonicalCommit ? ` (canonical: ${value.canonicalCommit})` : ''}`,
|
|
14
|
+
` Strategy: ${value.effectiveStrategy}${value.effectiveStrategy !== value.strategy ? ` (canonical: ${value.strategy})` : ''}`,
|
|
15
|
+
` Prefix: ${value.prefix || '(none)'}`,
|
|
16
|
+
` Default file extension: ${value.defaultFileExtension || '(none)'}`,
|
|
17
|
+
` Cache: ${value.cache ? `${value.cache.strategy} @ ${value.cache.commit}` : '(empty)'}`,
|
|
18
|
+
];
|
|
19
|
+
if (value.fetchUrl)
|
|
20
|
+
lines.push(` Fetch URL: ${value.fetchUrl}`);
|
|
21
|
+
if (value.localPath)
|
|
22
|
+
lines.push(` Local path: ${value.localPath}`);
|
|
23
|
+
if (value.localError)
|
|
24
|
+
lines.push(` Local error: ${value.localError}`);
|
|
25
|
+
lines.push(' Suggested checkout:');
|
|
26
|
+
for (const command of value.checkout) {
|
|
27
|
+
lines.push(` ${[command.command, ...command.args].map(shellArg).join(' ')}`);
|
|
28
|
+
}
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
export async function externalListCommand(ctx, json = false) {
|
|
32
|
+
const snapshot = await loadExternalSources(ctx.latDir, ctx.projectRoot);
|
|
33
|
+
if (!snapshot.validCanonical) {
|
|
34
|
+
return {
|
|
35
|
+
output: snapshot.errors
|
|
36
|
+
.map((error) => `${error.file}: ${error.message}`)
|
|
37
|
+
.join('\n'),
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const descriptions = describeExternalSources(ctx.latDir, snapshot);
|
|
42
|
+
return {
|
|
43
|
+
output: json
|
|
44
|
+
? JSON.stringify(descriptions, null, 2)
|
|
45
|
+
: descriptions.length
|
|
46
|
+
? descriptions.map(formatDescription).join('\n\n')
|
|
47
|
+
: 'No external sources configured',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function externalShowCommand(ctx, query, json = false) {
|
|
51
|
+
const snapshot = await loadExternalSources(ctx.latDir, ctx.projectRoot);
|
|
52
|
+
const colon = query.indexOf(':');
|
|
53
|
+
const handle = colon === -1 ? query : query.slice(0, colon);
|
|
54
|
+
const source = describeExternalSources(ctx.latDir, snapshot).find((item) => item.handle === handle);
|
|
55
|
+
if (!source) {
|
|
56
|
+
return {
|
|
57
|
+
output: `External source "${handle}" is not configured`,
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
let target = null;
|
|
62
|
+
if (colon !== -1) {
|
|
63
|
+
try {
|
|
64
|
+
target = parseExternalTarget(query, snapshot);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return { output: error.message, isError: true };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const value = target
|
|
71
|
+
? {
|
|
72
|
+
...source,
|
|
73
|
+
target: target.identity,
|
|
74
|
+
repositoryPath: target.repositoryPath,
|
|
75
|
+
}
|
|
76
|
+
: source;
|
|
77
|
+
return {
|
|
78
|
+
output: json
|
|
79
|
+
? JSON.stringify(value, null, 2)
|
|
80
|
+
: formatDescription(source) +
|
|
81
|
+
(target
|
|
82
|
+
? `\n Target: ${target.identity}\n Repository path: ${target.repositoryPath}`
|
|
83
|
+
: ''),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function question(rl, label, current) {
|
|
87
|
+
if (current)
|
|
88
|
+
return current;
|
|
89
|
+
const answer = (await rl.question(`${label}: `)).trim();
|
|
90
|
+
if (!answer)
|
|
91
|
+
throw new Error(`${label} is required`);
|
|
92
|
+
return answer;
|
|
93
|
+
}
|
|
94
|
+
export async function externalAddCommand(ctx, handleValue, repoValue, options) {
|
|
95
|
+
const interactive = !!process.stdin.isTTY;
|
|
96
|
+
if (!interactive &&
|
|
97
|
+
(!handleValue || !repoValue || !options.commit || !options.strategy)) {
|
|
98
|
+
return {
|
|
99
|
+
output: 'Non-interactive use requires handle, repo, --commit, and --strategy',
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
let rl = null;
|
|
104
|
+
const getReadline = () => {
|
|
105
|
+
rl ??= createInterface({ input: process.stdin, output: process.stdout });
|
|
106
|
+
return rl;
|
|
107
|
+
};
|
|
108
|
+
const closeReadline = () => {
|
|
109
|
+
rl?.close();
|
|
110
|
+
rl = null;
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
const handle = await question(getReadline(), 'Handle', handleValue);
|
|
114
|
+
const repo = normalizeExternalRepoUrl(await question(getReadline(), 'Repository', repoValue));
|
|
115
|
+
const ref = await question(getReadline(), 'Commit or ref', options.commit);
|
|
116
|
+
const prefix = options.prefix ??
|
|
117
|
+
(interactive
|
|
118
|
+
? (await getReadline().question('Prefix (optional): ')).trim()
|
|
119
|
+
: '');
|
|
120
|
+
const defaultFileExtensionValue = options.defaultFileExtension ??
|
|
121
|
+
(interactive
|
|
122
|
+
? (await getReadline().question('Default file extension (optional): ')).trim()
|
|
123
|
+
: '');
|
|
124
|
+
const defaultFileExtension = defaultFileExtensionValue
|
|
125
|
+
? normalizeExternalDefaultFileExtension(defaultFileExtensionValue)
|
|
126
|
+
: '';
|
|
127
|
+
let strategy = options.strategy;
|
|
128
|
+
if (!strategy && interactive) {
|
|
129
|
+
const inferred = inferExternalFetchUrl(repo);
|
|
130
|
+
// selectMenu reads raw keypresses directly. Detach readline first so it
|
|
131
|
+
// cannot consume the selection's Enter key or remain paused afterward.
|
|
132
|
+
closeReadline();
|
|
133
|
+
strategy =
|
|
134
|
+
(await selectMenu(inferred
|
|
135
|
+
? [
|
|
136
|
+
{
|
|
137
|
+
label: 'Fetch individual files (recommended)',
|
|
138
|
+
value: 'fetch',
|
|
139
|
+
},
|
|
140
|
+
{ label: 'Use a Lat-managed checkout', value: 'checkout' },
|
|
141
|
+
]
|
|
142
|
+
: [
|
|
143
|
+
{
|
|
144
|
+
label: 'Use a Lat-managed checkout (recommended)',
|
|
145
|
+
value: 'checkout',
|
|
146
|
+
},
|
|
147
|
+
{ label: 'Fetch individual files', value: 'fetch' },
|
|
148
|
+
], 'How should Lat read this source?')) ?? undefined;
|
|
149
|
+
}
|
|
150
|
+
if (strategy !== 'fetch' && strategy !== 'checkout') {
|
|
151
|
+
return { output: 'strategy must be fetch or checkout', isError: true };
|
|
152
|
+
}
|
|
153
|
+
let fetchUrl = options.fetchUrl;
|
|
154
|
+
if (strategy === 'fetch' &&
|
|
155
|
+
!fetchUrl &&
|
|
156
|
+
!inferExternalFetchUrl(repo) &&
|
|
157
|
+
interactive) {
|
|
158
|
+
fetchUrl = await question(getReadline(), 'Fetch URL template');
|
|
159
|
+
}
|
|
160
|
+
const commit = await resolveExternalCommit(repo, ref);
|
|
161
|
+
if (interactive) {
|
|
162
|
+
const confirmation = (await getReadline().question(`Add external source "${handle}" at commit "${commit}" using "${strategy}"? [Y/n] `))
|
|
163
|
+
.trim()
|
|
164
|
+
.toLowerCase();
|
|
165
|
+
if (confirmation === 'n' || confirmation === 'no')
|
|
166
|
+
return { output: 'Aborted' };
|
|
167
|
+
}
|
|
168
|
+
await addCanonicalExternalSource(ctx.latDir, {
|
|
169
|
+
handle,
|
|
170
|
+
repo,
|
|
171
|
+
commit,
|
|
172
|
+
...(prefix ? { prefix } : {}),
|
|
173
|
+
...(defaultFileExtension ? { defaultFileExtension } : {}),
|
|
174
|
+
strategy: strategy,
|
|
175
|
+
...(fetchUrl ? { fetchUrl } : {}),
|
|
176
|
+
});
|
|
177
|
+
return { output: `Added external source ${handle} at ${commit}` };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return { output: error.message, isError: true };
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
closeReadline();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const SKILL_FRONTMATTER = "---\nname: lat-md-conventions\ndescription: >-\n Rules lat check enforces in this project: Di\u00E1taxis mode placement for every\n lat.md/ document, mandatory @lat: code refs starting at the application\n entrypoint, and test specs wired to their tests. Load alongside lat-md\n whenever creating or editing files under lat.md/.\n---\n\n";
|
|
1
2
|
export declare function readForkConventions(): string;
|
|
2
3
|
/**
|
|
3
4
|
* Replace the fork block in `content`, or append one when absent.
|
|
@@ -17,7 +17,7 @@ import { findTemplatesDir } from './templates.js';
|
|
|
17
17
|
*
|
|
18
18
|
* Two shapes, because the destinations behave differently:
|
|
19
19
|
*
|
|
20
|
-
* - Always-loaded instruction files (`CLAUDE.md`, `AGENTS.md`,
|
|
20
|
+
* - Always-loaded instruction files (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`,
|
|
21
21
|
* `.github/copilot-instructions.md`) already carry upstream's
|
|
22
22
|
* `%% lat:begin %%` block, so this appends a *second* block under its own
|
|
23
23
|
* `%% lat-fork:begin %%` markers. Upstream's `appendTemplateSection` slices
|
|
@@ -42,6 +42,7 @@ function forkHashKey(relPath) {
|
|
|
42
42
|
const BLOCK_TARGETS = [
|
|
43
43
|
'CLAUDE.md',
|
|
44
44
|
'AGENTS.md',
|
|
45
|
+
'GEMINI.md',
|
|
45
46
|
'.github/copilot-instructions.md',
|
|
46
47
|
];
|
|
47
48
|
/**
|
|
@@ -56,7 +57,7 @@ const SKILL_ROOTS = [
|
|
|
56
57
|
'.codex/skills',
|
|
57
58
|
];
|
|
58
59
|
const SKILL_NAME = 'lat-md-conventions';
|
|
59
|
-
const SKILL_FRONTMATTER = `---
|
|
60
|
+
export const SKILL_FRONTMATTER = `---
|
|
60
61
|
name: ${SKILL_NAME}
|
|
61
62
|
description: >-
|
|
62
63
|
Rules lat check enforces in this project: Diátaxis mode placement for every
|
package/dist/src/cli/hook.d.ts
CHANGED
|
@@ -1 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measure code vs `lat.md/` churn since HEAD, in lines. Combines tracked
|
|
3
|
+
* changes (`git diff HEAD --numstat`) with untracked files
|
|
4
|
+
* (`git ls-files --others --exclude-standard -z`). Counting untracked files is
|
|
5
|
+
* what makes a freshly scaffolded, never-committed `lat.md/` register as
|
|
6
|
+
* updated — otherwise its edits are invisible to `git diff HEAD` and the sync
|
|
7
|
+
* reminder fires on every turn until `lat.md/` is committed (issue #61).
|
|
8
|
+
* Both scans are scoped and made relative to `projectRoot`, so a Lat project
|
|
9
|
+
* nested in a larger worktree neither misses its own `lat.md/` paths nor counts
|
|
10
|
+
* changes from sibling projects.
|
|
11
|
+
* Outside a Git worktree both scans contribute zero churn by design: Git is
|
|
12
|
+
* optional, so the hook still validates the project but skips the sync ratio.
|
|
13
|
+
*/
|
|
14
|
+
export declare function analyzeDiff(projectRoot: string): {
|
|
15
|
+
codeLines: number;
|
|
16
|
+
latMdLines: number;
|
|
17
|
+
};
|
|
18
|
+
export type StopStatus = {
|
|
19
|
+
checkFailed: boolean;
|
|
20
|
+
totalErrors: number;
|
|
21
|
+
needsSync: boolean;
|
|
22
|
+
codeLines: number;
|
|
23
|
+
latMdLines: number;
|
|
24
|
+
};
|
|
25
|
+
export declare function getStopStatus(latDir: string): Promise<StopStatus>;
|
|
26
|
+
export declare function formatStopReason({ checkFailed, totalErrors, needsSync, codeLines, latMdLines, }: StopStatus): string | null;
|
|
1
27
|
export declare function hookCmd(agent: string, event: string): Promise<void>;
|
package/dist/src/cli/hook.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { lstatSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, extname, join } from 'node:path';
|
|
4
|
+
import { findLatticeDir } from '../project-discovery.js';
|
|
5
|
+
import { findSections } from '../lattice-model.js';
|
|
4
6
|
import { plainStyler } from '../context.js';
|
|
5
7
|
import { expandPrompt } from './expand.js';
|
|
6
8
|
import { runSearch } from './search.js';
|
|
9
|
+
import { DEFAULT_SEARCH_LIMIT } from '../search/search.js';
|
|
7
10
|
import { getSection, formatSectionOutput } from './section.js';
|
|
8
11
|
import { checkMd, checkCodeRefs, checkIndex, checkSections } from './check.js';
|
|
12
|
+
import { CheckRunContext } from './check-context.js';
|
|
13
|
+
import { isSourceFileExtension } from '../source-formats.js';
|
|
14
|
+
import { commandProjectAnalysis } from '../project-analysis.js';
|
|
9
15
|
import { checkMode } from './check-mode.js';
|
|
10
|
-
import { SOURCE_EXTENSIONS } from '../source-parser.js';
|
|
11
16
|
import { federateTags, taggedDocsForFiles } from '../knowledge/index.js';
|
|
12
17
|
import { loadSessionMarkers, saveSessionMarkers, } from '../knowledge/session.js';
|
|
13
18
|
import { computeCommentReminder, } from './comment-reminder.js';
|
|
14
19
|
import { handlePreToolUse } from './comment-guard.js';
|
|
20
|
+
import { handleAntigravityHook } from '../fork/antigravity-hook.js';
|
|
15
21
|
function outputPromptSubmit(context) {
|
|
16
22
|
process.stdout.write(JSON.stringify({
|
|
17
23
|
hookSpecificOutput: {
|
|
@@ -64,15 +70,15 @@ function makeHookCtx(latDir) {
|
|
|
64
70
|
mode: 'cli',
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
|
-
async function searchAndExpand(ctx, userPrompt
|
|
73
|
+
async function searchAndExpand(ctx, userPrompt) {
|
|
68
74
|
let result;
|
|
69
75
|
try {
|
|
70
76
|
// Read-only: search an existing index but never build/update it here. A fresh
|
|
71
77
|
// repo's first prompt must not trigger a full local embed pass — that's what
|
|
72
78
|
// `lat search` / `lat reindex` are for. Returns no matches until then.
|
|
73
|
-
result = await runSearch(ctx.latDir, userPrompt,
|
|
79
|
+
result = await runSearch(ctx.latDir, userPrompt, DEFAULT_SEARCH_LIMIT, undefined, {
|
|
74
80
|
buildIndex: false,
|
|
75
|
-
|
|
81
|
+
project: await commandProjectAnalysis(ctx),
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
catch {
|
|
@@ -113,13 +119,14 @@ async function handleUserPromptSubmit() {
|
|
|
113
119
|
const latDir = findLatticeDir();
|
|
114
120
|
if (latDir && userPrompt) {
|
|
115
121
|
const ctx = makeHookCtx(latDir);
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
122
|
+
// Parsed once for expansion, search, and federation below. The analysis is
|
|
123
|
+
// memoised on `ctx`, so passing that one object everywhere is what keeps
|
|
124
|
+
// the three of them from walking and parsing the tree three times.
|
|
125
|
+
const { allSections } = await commandProjectAnalysis(ctx);
|
|
119
126
|
// If the user prompt contains [[refs]], resolve them inline
|
|
120
127
|
if (hasWikiLinks(userPrompt)) {
|
|
121
128
|
try {
|
|
122
|
-
const expanded = await expandPrompt(ctx, userPrompt
|
|
129
|
+
const expanded = await expandPrompt(ctx, userPrompt);
|
|
123
130
|
if (expanded) {
|
|
124
131
|
parts.push('', 'Expanded user prompt with resolved [[refs]]:', expanded);
|
|
125
132
|
}
|
|
@@ -134,7 +141,7 @@ async function handleUserPromptSubmit() {
|
|
|
134
141
|
// Search for relevant sections and include their full content
|
|
135
142
|
let searchFilePaths = [];
|
|
136
143
|
try {
|
|
137
|
-
const searchContext = await searchAndExpand(ctx, userPrompt
|
|
144
|
+
const searchContext = await searchAndExpand(ctx, userPrompt);
|
|
138
145
|
searchFilePaths = searchContext.filePaths;
|
|
139
146
|
if (searchContext.text) {
|
|
140
147
|
parts.push('', searchContext.text);
|
|
@@ -252,55 +259,116 @@ const DIFF_THRESHOLD = 5;
|
|
|
252
259
|
const LATMD_RATIO = 0.05;
|
|
253
260
|
/** If lat.md/ changes exceed this many lines, skip the ratio check entirely. */
|
|
254
261
|
const LATMD_UPPER_THRESHOLD = 50;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
function diffFileKind(file) {
|
|
263
|
+
if (file.startsWith('lat.md/'))
|
|
264
|
+
return 'latMd';
|
|
265
|
+
if (isSourceFileExtension(extname(file)))
|
|
266
|
+
return 'code';
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
/** Count a regular text file's lines as additions, matching Git numstat. */
|
|
270
|
+
function countUntrackedFileLines(projectRoot, file) {
|
|
258
271
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
const path = join(projectRoot, file);
|
|
273
|
+
if (!lstatSync(path).isFile())
|
|
274
|
+
return 0;
|
|
275
|
+
const text = readFileSync(path, 'utf-8');
|
|
276
|
+
if (text.length === 0)
|
|
277
|
+
return 0;
|
|
278
|
+
return text.split('\n').length - (text.endsWith('\n') ? 1 : 0);
|
|
264
279
|
}
|
|
265
280
|
catch {
|
|
266
|
-
return
|
|
281
|
+
return 0;
|
|
267
282
|
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Measure code vs `lat.md/` churn since HEAD, in lines. Combines tracked
|
|
286
|
+
* changes (`git diff HEAD --numstat`) with untracked files
|
|
287
|
+
* (`git ls-files --others --exclude-standard -z`). Counting untracked files is
|
|
288
|
+
* what makes a freshly scaffolded, never-committed `lat.md/` register as
|
|
289
|
+
* updated — otherwise its edits are invisible to `git diff HEAD` and the sync
|
|
290
|
+
* reminder fires on every turn until `lat.md/` is committed (issue #61).
|
|
291
|
+
* Both scans are scoped and made relative to `projectRoot`, so a Lat project
|
|
292
|
+
* nested in a larger worktree neither misses its own `lat.md/` paths nor counts
|
|
293
|
+
* changes from sibling projects.
|
|
294
|
+
* Outside a Git worktree both scans contribute zero churn by design: Git is
|
|
295
|
+
* optional, so the hook still validates the project but skips the sync ratio.
|
|
296
|
+
*/
|
|
297
|
+
export function analyzeDiff(projectRoot) {
|
|
268
298
|
let codeLines = 0;
|
|
269
299
|
let latMdLines = 0;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const parts = line.split('\t');
|
|
273
|
-
if (parts.length < 3)
|
|
274
|
-
continue;
|
|
275
|
-
const added = parseInt(parts[0], 10) || 0;
|
|
276
|
-
const removed = parseInt(parts[1], 10) || 0;
|
|
277
|
-
const file = parts[2];
|
|
278
|
-
const changed = added + removed;
|
|
279
|
-
if (file.startsWith('lat.md/')) {
|
|
300
|
+
const tally = (kind, changed) => {
|
|
301
|
+
if (kind === 'latMd') {
|
|
280
302
|
latMdLines += changed;
|
|
281
303
|
}
|
|
282
|
-
else
|
|
304
|
+
else {
|
|
283
305
|
codeLines += changed;
|
|
284
306
|
}
|
|
307
|
+
};
|
|
308
|
+
// Tracked changes vs HEAD. Throws when there is no HEAD yet (a repo with no
|
|
309
|
+
// commits) or no repo at all; the untracked scan below still runs.
|
|
310
|
+
try {
|
|
311
|
+
const output = execSync('git diff HEAD --numstat --relative -- .', {
|
|
312
|
+
cwd: projectRoot,
|
|
313
|
+
encoding: 'utf-8',
|
|
314
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
315
|
+
});
|
|
316
|
+
// Each line: "added\tremoved\tfile" (e.g. "42\t11\tsrc/cli/hook.ts")
|
|
317
|
+
for (const line of output.split('\n')) {
|
|
318
|
+
const parts = line.split('\t');
|
|
319
|
+
if (parts.length < 3)
|
|
320
|
+
continue;
|
|
321
|
+
const added = parseInt(parts[0], 10) || 0;
|
|
322
|
+
const removed = parseInt(parts[1], 10) || 0;
|
|
323
|
+
const kind = diffFileKind(parts[2]);
|
|
324
|
+
if (kind)
|
|
325
|
+
tally(kind, added + removed);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// Not a git repo, or no HEAD — fall through to the untracked scan.
|
|
330
|
+
}
|
|
331
|
+
// NUL-delimited output preserves spaces, non-ASCII names, and newlines.
|
|
332
|
+
// Classify paths before reading so unrelated untracked files are never read.
|
|
333
|
+
try {
|
|
334
|
+
const output = execSync('git ls-files --others --exclude-standard -z -- .', {
|
|
335
|
+
cwd: projectRoot,
|
|
336
|
+
encoding: 'utf-8',
|
|
337
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
338
|
+
});
|
|
339
|
+
for (const file of output.split('\0')) {
|
|
340
|
+
if (!file)
|
|
341
|
+
continue;
|
|
342
|
+
const kind = diffFileKind(file);
|
|
343
|
+
if (!kind)
|
|
344
|
+
continue;
|
|
345
|
+
tally(kind, countUntrackedFileLines(projectRoot, file));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// Not a Git repo — diff-based sync analysis is intentionally disabled.
|
|
285
350
|
}
|
|
286
351
|
return { codeLines, latMdLines };
|
|
287
352
|
}
|
|
288
|
-
async function getStopStatus(latDir) {
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
const indexErrors = await checkIndex(latDir);
|
|
292
|
-
const sectionErrors = await checkSections(latDir);
|
|
353
|
+
export async function getStopStatus(latDir) {
|
|
354
|
+
const projectRoot = dirname(latDir);
|
|
355
|
+
const run = new CheckRunContext(latDir, projectRoot);
|
|
293
356
|
// Must stay in step with `checkAllCommand` in check.ts. A check counted
|
|
294
357
|
// there but not here lets the Stop hook call a failing tree clean, which
|
|
295
358
|
// silently disables the very enforcement the check was added for.
|
|
296
|
-
const modeErrors = await
|
|
359
|
+
const [md, code, indexErrors, sectionErrors, modeErrors] = await Promise.all([
|
|
360
|
+
checkMd(latDir, projectRoot, run),
|
|
361
|
+
checkCodeRefs(latDir, projectRoot, run),
|
|
362
|
+
checkIndex(latDir, run),
|
|
363
|
+
checkSections(latDir, projectRoot, run),
|
|
364
|
+
checkMode(latDir, projectRoot),
|
|
365
|
+
]);
|
|
297
366
|
const totalErrors = md.errors.length +
|
|
298
367
|
code.errors.length +
|
|
299
368
|
indexErrors.length +
|
|
300
369
|
sectionErrors.length +
|
|
301
370
|
modeErrors.length;
|
|
302
371
|
const checkFailed = totalErrors > 0;
|
|
303
|
-
const projectRoot = dirname(latDir);
|
|
304
372
|
const { codeLines, latMdLines } = analyzeDiff(projectRoot);
|
|
305
373
|
let needsSync = false;
|
|
306
374
|
if (codeLines >= DIFF_THRESHOLD && latMdLines < LATMD_UPPER_THRESHOLD) {
|
|
@@ -315,7 +383,7 @@ async function getStopStatus(latDir) {
|
|
|
315
383
|
latMdLines,
|
|
316
384
|
};
|
|
317
385
|
}
|
|
318
|
-
function formatStopReason({ checkFailed, totalErrors, needsSync, codeLines, latMdLines, }) {
|
|
386
|
+
export function formatStopReason({ checkFailed, totalErrors, needsSync, codeLines, latMdLines, }) {
|
|
319
387
|
if (!checkFailed && !needsSync)
|
|
320
388
|
return null;
|
|
321
389
|
const parts = [];
|
|
@@ -425,8 +493,12 @@ export async function hookCmd(agent, event) {
|
|
|
425
493
|
console.error(`Unknown hook event for cursor: ${event}. Supported: stop, postToolUse`);
|
|
426
494
|
process.exit(1);
|
|
427
495
|
}
|
|
496
|
+
case 'antigravity':
|
|
497
|
+
case 'gemini':
|
|
498
|
+
await handleAntigravityHook(event);
|
|
499
|
+
return;
|
|
428
500
|
default:
|
|
429
|
-
console.error(`Unknown agent: ${agent}. Supported: claude, codex, cursor`);
|
|
501
|
+
console.error(`Unknown agent: ${agent}. Supported: claude, codex, cursor, antigravity, gemini`);
|
|
430
502
|
process.exit(1);
|
|
431
503
|
}
|
|
432
504
|
}
|