@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/check.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lstat, rename, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { performance } from 'node:perf_hooks';
|
|
5
|
+
import { flattenSections, resolveRef } from '../lattice-model.js';
|
|
6
|
+
import { isSourceFileExtension, SOURCE_FILE_EXTENSIONS, } from '../source-formats.js';
|
|
7
|
+
import { inspectRepositoryPath, normalizeRepositoryPath, } from '../repository-path.js';
|
|
8
|
+
import { toPosix } from '../path.js';
|
|
9
|
+
import { TimingProfiler } from '../profiler.js';
|
|
6
10
|
import { checkMode } from './check-mode.js';
|
|
7
11
|
import { checkStatus } from './check-status.js';
|
|
8
12
|
import { checkFrontmatter } from './check-frontmatter.js';
|
|
9
13
|
import { checkCoverage } from './check-coverage.js';
|
|
10
14
|
import { indexEntryNameFromDest } from './link-scheme.js';
|
|
11
15
|
import { fencedLineMask } from './gen-index.js';
|
|
12
|
-
import { SOURCE_EXTENSIONS, clearSymbolCache } from '../source-parser.js';
|
|
13
|
-
import { toPosix, walkEntries } from '../walk.js';
|
|
14
16
|
import { INIT_VERSION, readInitVersion } from '../init-version.js';
|
|
17
|
+
import { CheckRunContext } from './check-context.js';
|
|
18
|
+
import { parseLocalMarkdownTarget } from '../markdown-validation.js';
|
|
15
19
|
function filePart(id) {
|
|
16
20
|
const h = id.indexOf('#');
|
|
17
21
|
return h === -1 ? id : id.slice(0, h);
|
|
@@ -31,50 +35,66 @@ export function ambiguousRefMessage(target, candidates, suggested) {
|
|
|
31
35
|
lines.push(` The short path "${shortName}" is ambiguous — ${candidates.length} files match:`, fileList, ` Please fix the link to use a fully qualified path.`);
|
|
32
36
|
return lines.join('\n');
|
|
33
37
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
for (const p of paths) {
|
|
37
|
-
const ext = extname(p) || '(no ext)';
|
|
38
|
-
stats[ext] = (stats[ext] || 0) + 1;
|
|
39
|
-
}
|
|
40
|
-
return stats;
|
|
38
|
+
async function profileTime(profile, label, work, detail) {
|
|
39
|
+
return profile ? profile.time(label, work, detail) : work();
|
|
41
40
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
|
|
45
|
-
const ext = extname(filePart);
|
|
46
|
-
return SOURCE_EXTENSIONS.has(ext);
|
|
41
|
+
function profileTimeSync(profile, label, work, detail) {
|
|
42
|
+
return profile ? profile.timeSync(label, work, detail) : work();
|
|
47
43
|
}
|
|
48
44
|
/**
|
|
49
|
-
*
|
|
50
|
-
* Returns null
|
|
45
|
+
* Validate an unresolved wiki link as a repository path or source symbol.
|
|
46
|
+
* Returns null when valid, otherwise a user-facing error message.
|
|
51
47
|
*/
|
|
52
|
-
export async function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
|
|
57
|
-
const ext = extname(filePart);
|
|
58
|
-
if (ext && hashIdx !== -1) {
|
|
59
|
-
const supported = [...SOURCE_EXTENSIONS].sort().join(', ');
|
|
60
|
-
return `broken link [[${target}]] — unsupported file extension "${ext}". Supported: ${supported}`;
|
|
61
|
-
}
|
|
48
|
+
export async function repositoryRefError(target, projectRoot, sourceOptions = {}) {
|
|
49
|
+
const hashIdx = target.indexOf('#');
|
|
50
|
+
const authoredPath = hashIdx === -1 ? target : target.slice(0, hashIdx);
|
|
51
|
+
if (!authoredPath) {
|
|
62
52
|
return `broken link [[${target}]] — no matching section found`;
|
|
63
53
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
54
|
+
const filePart = normalizeRepositoryPath(authoredPath);
|
|
55
|
+
if (!filePart) {
|
|
56
|
+
return `broken link [[${target}]] — repository path "${authoredPath}" must stay within the project root`;
|
|
57
|
+
}
|
|
66
58
|
const symbolPart = hashIdx === -1 ? '' : target.slice(hashIdx + 1);
|
|
67
|
-
const
|
|
68
|
-
|
|
59
|
+
const ext = extname(filePart);
|
|
60
|
+
const sourcePath = isSourceFileExtension(ext);
|
|
61
|
+
const inspected = await inspectRepositoryPath(projectRoot, filePart);
|
|
62
|
+
if (inspected.kind === 'outside') {
|
|
63
|
+
return `broken link [[${target}]] — repository path "${filePart}" resolves outside the project root`;
|
|
64
|
+
}
|
|
65
|
+
if (hashIdx === -1) {
|
|
66
|
+
if (inspected.kind === 'missing') {
|
|
67
|
+
return `broken link [[${target}]] — repository file or directory "${filePart}" not found`;
|
|
68
|
+
}
|
|
69
|
+
if (inspected.kind === 'other') {
|
|
70
|
+
return `broken link [[${target}]] — repository path "${filePart}" is not a regular file or directory`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (inspected.kind === 'directory') {
|
|
75
|
+
return `broken link [[${target}]] — directory "${filePart}" cannot have a fragment`;
|
|
76
|
+
}
|
|
77
|
+
if (inspected.kind === 'other') {
|
|
78
|
+
return `broken link [[${target}]] — repository path "${filePart}" is not a regular file or directory`;
|
|
79
|
+
}
|
|
80
|
+
if (!sourcePath) {
|
|
81
|
+
if (ext || inspected.kind === 'file') {
|
|
82
|
+
const shownExtension = ext || '(none)';
|
|
83
|
+
const supported = SOURCE_FILE_EXTENSIONS.join(', ');
|
|
84
|
+
return `broken link [[${target}]] — unsupported file extension "${shownExtension}" for fragment reference. Supported: ${supported}`;
|
|
85
|
+
}
|
|
86
|
+
return `broken link [[${target}]] — no matching section found`;
|
|
87
|
+
}
|
|
88
|
+
if (inspected.kind === 'missing') {
|
|
69
89
|
return `broken link [[${target}]] — file "${filePart}" not found`;
|
|
70
90
|
}
|
|
71
91
|
if (!symbolPart) {
|
|
72
|
-
//
|
|
92
|
+
// Preserve the existing file-level behavior for an empty fragment.
|
|
73
93
|
return null;
|
|
74
94
|
}
|
|
75
95
|
try {
|
|
76
96
|
const { resolveSourceSymbol } = await import('../source-parser.js');
|
|
77
|
-
const { found, error } = await resolveSourceSymbol(filePart, symbolPart, projectRoot);
|
|
97
|
+
const { found, error } = await resolveSourceSymbol(filePart, symbolPart, projectRoot, sourceOptions);
|
|
78
98
|
if (error) {
|
|
79
99
|
return `broken link [[${target}]] — ${error}`;
|
|
80
100
|
}
|
|
@@ -87,20 +107,50 @@ export async function sourceRefError(target, projectRoot) {
|
|
|
87
107
|
return `broken link [[${target}]] — failed to parse "${filePart}": ${err instanceof Error ? err.message : String(err)}`;
|
|
88
108
|
}
|
|
89
109
|
}
|
|
90
|
-
export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
|
|
96
|
-
const fileIndex = buildFileIndex(allSections);
|
|
97
|
-
const slugIndex = buildSectionSlugIndex(allSections);
|
|
110
|
+
export async function checkMd(latticeDir, projectRoot = dirname(latticeDir), context) {
|
|
111
|
+
const run = context ?? new CheckRunContext(latticeDir, projectRoot);
|
|
112
|
+
run.clearSourceSymbolCache();
|
|
113
|
+
const files = await run.markdownFiles();
|
|
114
|
+
const { sectionIds, fileIndex, slugIndex } = await run.sectionIndex();
|
|
98
115
|
const errors = [];
|
|
116
|
+
const external = await run.externalResolver();
|
|
117
|
+
for (const error of external.snapshot.errors) {
|
|
118
|
+
errors.push({
|
|
119
|
+
file: relative(process.cwd(), error.file),
|
|
120
|
+
line: 1,
|
|
121
|
+
target: '',
|
|
122
|
+
message: error.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
99
125
|
for (const file of files) {
|
|
100
|
-
const
|
|
101
|
-
const refs = extractRefs(file, content, projectRoot);
|
|
126
|
+
const refs = await run.refs(file);
|
|
102
127
|
const relPath = relative(process.cwd(), file);
|
|
103
128
|
for (const ref of refs) {
|
|
129
|
+
try {
|
|
130
|
+
if (external.parse(ref.target)) {
|
|
131
|
+
await run.resolveExternal(ref.target);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
errors.push({
|
|
137
|
+
file: relPath,
|
|
138
|
+
line: ref.line,
|
|
139
|
+
target: ref.target,
|
|
140
|
+
message: `broken external link [[${ref.target}]] — ${error.message}`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const unknownExternal = external.unknownTargetMessage(ref.target);
|
|
145
|
+
if (unknownExternal) {
|
|
146
|
+
errors.push({
|
|
147
|
+
file: relPath,
|
|
148
|
+
line: ref.line,
|
|
149
|
+
target: ref.target,
|
|
150
|
+
message: unknownExternal,
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
104
154
|
const { resolved, ambiguous, suggested } = resolveRef(ref.target, sectionIds, fileIndex, slugIndex);
|
|
105
155
|
if (ambiguous) {
|
|
106
156
|
errors.push({
|
|
@@ -111,8 +161,8 @@ export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
|
|
|
111
161
|
});
|
|
112
162
|
}
|
|
113
163
|
else if (!sectionIds.has(resolved.toLowerCase())) {
|
|
114
|
-
// Try resolving as a
|
|
115
|
-
const sourceErr = await
|
|
164
|
+
// Try resolving as a repository path or source symbol.
|
|
165
|
+
const sourceErr = await run.resolveRepositoryLink(ref.target, () => repositoryRefError(ref.target, projectRoot, run.sourceSymbolOptions()));
|
|
116
166
|
if (sourceErr !== null) {
|
|
117
167
|
errors.push({
|
|
118
168
|
file: relPath,
|
|
@@ -124,84 +174,36 @@ export async function checkMd(latticeDir, projectRoot = dirname(latticeDir)) {
|
|
|
124
174
|
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
|
-
return { errors
|
|
177
|
+
return { errors };
|
|
128
178
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
catch {
|
|
134
|
-
return value;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/** Parse a local markdown destination without confusing escaped #/? in paths. */
|
|
138
|
-
function localLinkTarget(url) {
|
|
139
|
-
const u = url.trim();
|
|
140
|
-
if (u.startsWith('/'))
|
|
141
|
-
return null;
|
|
142
|
-
const windowsDrivePath = /^[a-zA-Z]:\\/.test(u);
|
|
143
|
-
if (!windowsDrivePath && /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(u)) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
// Split before decoding: `%23` and `%3F` decode to `#` and `?`, which would
|
|
147
|
-
// then truncate a filename that legitimately contains them.
|
|
148
|
-
const queryAt = u.indexOf('?');
|
|
149
|
-
const fragmentAt = u.indexOf('#');
|
|
150
|
-
const pathEnd = Math.min(queryAt === -1 ? u.length : queryAt, fragmentAt === -1 ? u.length : fragmentAt);
|
|
151
|
-
const rawPath = u.slice(0, pathEnd);
|
|
152
|
-
const path = rawPath === '' ? null : decodeLinkPart(rawPath);
|
|
153
|
-
const fragment = fragmentAt === -1 ? null : decodeLinkPart(u.slice(fragmentAt + 1));
|
|
154
|
-
if (rawPath === '' && fragment === null)
|
|
155
|
-
return null;
|
|
156
|
-
if (path?.includes('\\'))
|
|
157
|
-
return { kind: 'invalid-backslash' };
|
|
158
|
-
return {
|
|
159
|
-
kind: 'target',
|
|
160
|
-
path,
|
|
161
|
-
fragment,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
export async function checkLinks(latticeDir) {
|
|
165
|
-
const files = await listLatticeFiles(latticeDir);
|
|
179
|
+
// --- Relative link validation ---
|
|
180
|
+
export async function checkLinks(latticeDir, context) {
|
|
181
|
+
const run = context ?? new CheckRunContext(latticeDir, dirname(latticeDir));
|
|
182
|
+
const files = await run.markdownFiles();
|
|
166
183
|
const errors = [];
|
|
167
|
-
const headingCache = new Map();
|
|
168
|
-
const headingsFor = async (file) => {
|
|
169
|
-
const cached = headingCache.get(file);
|
|
170
|
-
if (cached)
|
|
171
|
-
return cached;
|
|
172
|
-
const content = await readFile(file, 'utf-8');
|
|
173
|
-
const headings = new Set(flattenSections(parseSections(file, content)).map((section) => section.githubSlug));
|
|
174
|
-
headingCache.set(file, headings);
|
|
175
|
-
return headings;
|
|
176
|
-
};
|
|
177
184
|
for (const file of files) {
|
|
178
|
-
const
|
|
185
|
+
const links = await run.links(file);
|
|
179
186
|
const relPath = toPosix(relative(process.cwd(), file));
|
|
180
|
-
for (const
|
|
181
|
-
if ('
|
|
182
|
-
|
|
183
|
-
errors.push({
|
|
184
|
-
file: relPath,
|
|
185
|
-
line: link.line,
|
|
186
|
-
target: link.identifier,
|
|
187
|
-
message: `undefined ${kind} reference (${link.source}) — definition "[${link.identifier}]" not found`,
|
|
188
|
-
});
|
|
187
|
+
for (const diagnostic of await run.diagnostics(file)) {
|
|
188
|
+
if (diagnostic.rule !== 'markdown-reference-definition' &&
|
|
189
|
+
diagnostic.rule !== 'markdown-path-separator') {
|
|
189
190
|
continue;
|
|
190
191
|
}
|
|
191
|
-
|
|
192
|
+
errors.push({
|
|
193
|
+
file: relPath,
|
|
194
|
+
line: diagnostic.line,
|
|
195
|
+
target: diagnostic.target,
|
|
196
|
+
message: diagnostic.message,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
for (const link of links) {
|
|
200
|
+
if ('identifier' in link)
|
|
201
|
+
continue;
|
|
202
|
+
const target = parseLocalMarkdownTarget(link.url);
|
|
192
203
|
if (target === null)
|
|
193
204
|
continue;
|
|
194
|
-
if (target.kind === 'invalid-backslash')
|
|
195
|
-
const kind = link.kind === 'image' ? 'image' : 'link';
|
|
196
|
-
errors.push({
|
|
197
|
-
file: relPath,
|
|
198
|
-
line: link.line,
|
|
199
|
-
target: link.url,
|
|
200
|
-
message: `invalid ${kind} (${link.url}) — backslashes are not path ` +
|
|
201
|
-
'separators in Markdown; use "/" instead',
|
|
202
|
-
});
|
|
205
|
+
if (target.kind === 'invalid-backslash')
|
|
203
206
|
continue;
|
|
204
|
-
}
|
|
205
207
|
const abs = target.path ? resolve(dirname(file), target.path) : file;
|
|
206
208
|
if (!existsSync(abs)) {
|
|
207
209
|
const kind = link.kind === 'image' ? 'image' : 'link';
|
|
@@ -217,7 +219,7 @@ export async function checkLinks(latticeDir) {
|
|
|
217
219
|
if (target.fragment &&
|
|
218
220
|
extname(abs).toLowerCase() === '.md' &&
|
|
219
221
|
link.kind !== 'image') {
|
|
220
|
-
const headings = await
|
|
222
|
+
const headings = await run.headings(abs);
|
|
221
223
|
if (!headings.has(target.fragment)) {
|
|
222
224
|
const shown = toPosix(relative(process.cwd(), abs));
|
|
223
225
|
errors.push({
|
|
@@ -232,17 +234,51 @@ export async function checkLinks(latticeDir) {
|
|
|
232
234
|
}
|
|
233
235
|
return errors;
|
|
234
236
|
}
|
|
235
|
-
export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir)) {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir), context) {
|
|
238
|
+
const run = context ?? new CheckRunContext(latticeDir, projectRoot);
|
|
239
|
+
const [{ sectionIds, fileIndex, slugIndex }, scan, external] = await Promise.all([
|
|
240
|
+
run.sectionIndex(),
|
|
241
|
+
run.codeRefs(),
|
|
242
|
+
run.externalResolver(),
|
|
243
|
+
]);
|
|
242
244
|
const errors = [];
|
|
245
|
+
for (const error of external.snapshot.errors) {
|
|
246
|
+
errors.push({
|
|
247
|
+
file: relative(process.cwd(), error.file),
|
|
248
|
+
line: 1,
|
|
249
|
+
target: '',
|
|
250
|
+
message: error.message,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
243
253
|
const mentionedSections = new Set();
|
|
244
254
|
for (const ref of scan.refs) {
|
|
245
|
-
|
|
255
|
+
try {
|
|
256
|
+
const externalTarget = profileTimeSync(run.profile, 'classify code-reference target', () => external.parse(ref.target), ref.target);
|
|
257
|
+
if (externalTarget) {
|
|
258
|
+
await run.resolveExternal(ref.target);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
errors.push({
|
|
264
|
+
file: relative(process.cwd(), join(projectRoot, ref.file)),
|
|
265
|
+
line: ref.line,
|
|
266
|
+
target: ref.target,
|
|
267
|
+
message: `@lat: [[${ref.target}]] — ${error.message}`,
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const unknownExternal = profileTimeSync(run.profile, 'check unknown external handle', () => external.unknownTargetMessage(ref.target), ref.target);
|
|
272
|
+
if (unknownExternal) {
|
|
273
|
+
errors.push({
|
|
274
|
+
file: relative(process.cwd(), join(projectRoot, ref.file)),
|
|
275
|
+
line: ref.line,
|
|
276
|
+
target: ref.target,
|
|
277
|
+
message: `@lat: [[${ref.target}]] — ${unknownExternal}`,
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const { resolved, ambiguous, suggested } = profileTimeSync(run.profile, 'resolve internal code reference', () => resolveRef(ref.target, sectionIds, fileIndex, slugIndex), ref.target);
|
|
246
282
|
mentionedSections.add(resolved.toLowerCase());
|
|
247
283
|
const displayPath = relative(process.cwd(), join(projectRoot, ref.file));
|
|
248
284
|
if (ambiguous) {
|
|
@@ -262,15 +298,13 @@ export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir
|
|
|
262
298
|
});
|
|
263
299
|
}
|
|
264
300
|
}
|
|
265
|
-
const files = await
|
|
301
|
+
const files = await run.markdownFiles();
|
|
266
302
|
for (const file of files) {
|
|
267
|
-
const
|
|
268
|
-
const fm = parseFrontmatter(content);
|
|
303
|
+
const fm = await run.frontmatter(file);
|
|
269
304
|
if (!fm.requireCodeMention)
|
|
270
305
|
continue;
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const leafSections = fileSections.filter((s) => s.children.length === 0);
|
|
306
|
+
const fileSections = flattenSections(await run.sections(file));
|
|
307
|
+
const leafSections = fileSections.filter((section) => section.children.length === 0);
|
|
274
308
|
const relPath = relative(process.cwd(), file);
|
|
275
309
|
for (const leaf of leafSections) {
|
|
276
310
|
if (!mentionedSections.has(leaf.id.toLowerCase())) {
|
|
@@ -283,7 +317,7 @@ export async function checkCodeRefs(latticeDir, projectRoot = dirname(latticeDir
|
|
|
283
317
|
}
|
|
284
318
|
}
|
|
285
319
|
}
|
|
286
|
-
return { errors
|
|
320
|
+
return { errors };
|
|
287
321
|
}
|
|
288
322
|
/**
|
|
289
323
|
* Extract the immediate (first-level) entries from walkEntries results.
|
|
@@ -335,17 +369,41 @@ function entryToStem(name) {
|
|
|
335
369
|
function indexSnippet(entries) {
|
|
336
370
|
return entries.map((e) => `- [[${entryToStem(e)}]] — <describe>`).join('\n');
|
|
337
371
|
}
|
|
338
|
-
export async function checkIndex(latticeDir, opts = {}) {
|
|
372
|
+
export async function checkIndex(latticeDir, context, opts = {}) {
|
|
373
|
+
const run = context ?? new CheckRunContext(latticeDir, dirname(latticeDir));
|
|
339
374
|
const errors = [];
|
|
340
|
-
const allPaths = await
|
|
341
|
-
|
|
375
|
+
const allPaths = await run.entries();
|
|
376
|
+
const referencedResources = new Set();
|
|
377
|
+
for (const file of await run.markdownFiles()) {
|
|
378
|
+
for (const link of await run.links(file)) {
|
|
379
|
+
if ('identifier' in link)
|
|
380
|
+
continue;
|
|
381
|
+
const target = parseLocalMarkdownTarget(link.url);
|
|
382
|
+
if (!target || target.kind === 'invalid-backslash' || !target.path) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const absolutePath = resolve(dirname(file), target.path);
|
|
386
|
+
const resourcePath = toPosix(relative(latticeDir, absolutePath));
|
|
387
|
+
if (resourcePath &&
|
|
388
|
+
resourcePath !== '..' &&
|
|
389
|
+
!resourcePath.startsWith('../') &&
|
|
390
|
+
!resourcePath.toLowerCase().endsWith('.md')) {
|
|
391
|
+
referencedResources.add(resourcePath);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Flag non-.md files unless Markdown reaches them as local resources. The
|
|
396
|
+
// machine-local external override is the other intentional non-Markdown
|
|
397
|
+
// configuration file in the vault.
|
|
342
398
|
for (const p of allPaths) {
|
|
343
399
|
const name = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
|
|
344
|
-
if (!name.endsWith('.md')
|
|
400
|
+
if (!name.endsWith('.md') &&
|
|
401
|
+
p !== 'config.local.yaml' &&
|
|
402
|
+
!referencedResources.has(p)) {
|
|
345
403
|
const relDir = basename(latticeDir) + '/';
|
|
346
404
|
errors.push({
|
|
347
405
|
dir: relDir,
|
|
348
|
-
message: `"${p}" is not a .md file —
|
|
406
|
+
message: `"${p}" is not a .md file or a referenced local resource — remove it or link to it from Markdown`,
|
|
349
407
|
});
|
|
350
408
|
}
|
|
351
409
|
}
|
|
@@ -382,7 +440,7 @@ export async function checkIndex(latticeDir, opts = {}) {
|
|
|
382
440
|
const indexFullPath = join(latticeDir, indexRelPath);
|
|
383
441
|
let content;
|
|
384
442
|
try {
|
|
385
|
-
content = await
|
|
443
|
+
content = await run.content(indexFullPath);
|
|
386
444
|
}
|
|
387
445
|
catch {
|
|
388
446
|
content = null;
|
|
@@ -533,53 +591,26 @@ async function writeGeneratedIndex(latticeDir, dir, indexFullPath, dirName, exis
|
|
|
533
591
|
return null;
|
|
534
592
|
}
|
|
535
593
|
// --- Section structure validation ---
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
function bodyTextLength(body) {
|
|
540
|
-
return body.replace(/\[\[[^\]]*\]\]/g, '').length;
|
|
541
|
-
}
|
|
542
|
-
export async function checkSections(latticeDir, projectRoot = dirname(latticeDir)) {
|
|
543
|
-
const files = await listLatticeFiles(latticeDir);
|
|
594
|
+
export async function checkSections(latticeDir, projectRoot = dirname(latticeDir), context) {
|
|
595
|
+
const run = context ?? new CheckRunContext(latticeDir, projectRoot);
|
|
596
|
+
const files = await run.markdownFiles();
|
|
544
597
|
const errors = [];
|
|
545
598
|
for (const file of files) {
|
|
546
|
-
const content = await readFile(file, 'utf-8');
|
|
547
|
-
const sections = parseSections(file, content, projectRoot);
|
|
548
|
-
const flat = flattenSections(sections);
|
|
549
599
|
const relPath = relative(process.cwd(), file);
|
|
550
|
-
for (const
|
|
551
|
-
if (
|
|
552
|
-
errors.push({
|
|
553
|
-
file: relPath,
|
|
554
|
-
line: section.startLine,
|
|
555
|
-
target: section.id,
|
|
556
|
-
message: `section "${section.id}" has no leading paragraph. ` +
|
|
557
|
-
`Every section must start with a brief overview (≤${MAX_BODY_LENGTH} chars) ` +
|
|
558
|
-
`summarizing what it documents — this powers search snippets and command output.`,
|
|
559
|
-
});
|
|
600
|
+
for (const diagnostic of await run.diagnostics(file)) {
|
|
601
|
+
if (diagnostic.rule !== 'section-leading-paragraph')
|
|
560
602
|
continue;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
target: section.id,
|
|
568
|
-
message: `section "${section.id}" leading paragraph is ${len} characters ` +
|
|
569
|
-
`(max ${MAX_BODY_LENGTH}, excluding [[wiki links]]). ` +
|
|
570
|
-
`Keep the first paragraph brief — it serves as the section's summary ` +
|
|
571
|
-
`in search results and command output. Use subsequent paragraphs for details.`,
|
|
572
|
-
});
|
|
573
|
-
}
|
|
603
|
+
errors.push({
|
|
604
|
+
file: relPath,
|
|
605
|
+
line: diagnostic.line,
|
|
606
|
+
target: diagnostic.target,
|
|
607
|
+
message: diagnostic.message,
|
|
608
|
+
});
|
|
574
609
|
}
|
|
575
610
|
}
|
|
576
611
|
return errors;
|
|
577
612
|
}
|
|
578
613
|
// --- Formatting helpers (shared by all check commands) ---
|
|
579
|
-
function formatFileStats(files, s) {
|
|
580
|
-
const entries = Object.entries(files).sort(([a], [b]) => a.localeCompare(b));
|
|
581
|
-
return s.dim(`Scanned ${entries.map(([ext, n]) => `${n} ${ext}`).join(', ')}`);
|
|
582
|
-
}
|
|
583
614
|
function formatCheckErrors(errors, s) {
|
|
584
615
|
const lines = [];
|
|
585
616
|
for (const err of errors) {
|
|
@@ -609,42 +640,64 @@ function formatCheckIndexErrors(errors, s) {
|
|
|
609
640
|
function formatErrorCount(count, s) {
|
|
610
641
|
return s.red(`\n${count} error${count === 1 ? '' : 's'} found`);
|
|
611
642
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
const md = await checkMd(ctx.latDir, ctx.projectRoot);
|
|
616
|
-
const linkErrors = await checkLinks(ctx.latDir);
|
|
617
|
-
const code = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
|
|
643
|
+
export async function checkAllCommand(ctx, options = {}) {
|
|
644
|
+
const startTime = performance.now();
|
|
645
|
+
const profile = options.profile ? new TimingProfiler() : undefined;
|
|
618
646
|
// `--fix` is index-only: an index is derived from frontmatter, so it can be
|
|
619
647
|
// regenerated. Nothing else `lat check` reports has a mechanical fix.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
648
|
+
//
|
|
649
|
+
// A fixing run also has to finish before any other check starts. The checks
|
|
650
|
+
// below run concurrently over one shared parse cache, and --fix rewrites the
|
|
651
|
+
// very files they read; interleaving that write with those reads would make
|
|
652
|
+
// the run's verdict depend on scheduling. So it gets its own context, and
|
|
653
|
+
// the readers get a fresh one built after the writing is done.
|
|
654
|
+
const fixIndexErrors = options.fix
|
|
655
|
+
? await profileTime(profile, 'generate directory indexes', () => checkIndex(ctx.latDir, new CheckRunContext(ctx.latDir, ctx.projectRoot, profile), { fix: true }))
|
|
656
|
+
: null;
|
|
657
|
+
const run = new CheckRunContext(ctx.latDir, ctx.projectRoot, profile);
|
|
658
|
+
const [md, linkErrors, code, checkedIndexErrors, sectionErrors, modeErrors, statusErrors, fmErrors, coverageErrors,] = await Promise.all([
|
|
659
|
+
profileTime(profile, 'check Markdown wiki links', () => checkMd(ctx.latDir, ctx.projectRoot, run)),
|
|
660
|
+
profileTime(profile, 'check relative Markdown links', () => checkLinks(ctx.latDir, run)),
|
|
661
|
+
profileTime(profile, 'check @lat code references', () => checkCodeRefs(ctx.latDir, ctx.projectRoot, run)),
|
|
662
|
+
// Already generated above when --fix is set; re-reporting it would only
|
|
663
|
+
// describe the state the fix just left behind.
|
|
664
|
+
fixIndexErrors
|
|
665
|
+
? Promise.resolve([])
|
|
666
|
+
: profileTime(profile, 'check directory indexes', () => checkIndex(ctx.latDir, run)),
|
|
667
|
+
profileTime(profile, 'check section structure', () => checkSections(ctx.latDir, ctx.projectRoot, run)),
|
|
668
|
+
profileTime(profile, 'check Diátaxis modes', () => checkMode(ctx.latDir, ctx.projectRoot)),
|
|
669
|
+
profileTime(profile, 'check review status', () => checkStatus(ctx.latDir, ctx.projectRoot)),
|
|
670
|
+
profileTime(profile, 'check frontmatter placement', () => checkFrontmatter(ctx.latDir, ctx.projectRoot)),
|
|
671
|
+
profileTime(profile, 'check documentation coverage', () => checkCoverage(ctx.latDir, ctx.projectRoot)),
|
|
672
|
+
]);
|
|
673
|
+
const indexErrors = fixIndexErrors ?? checkedIndexErrors;
|
|
674
|
+
const elapsed = performance.now() - startTime;
|
|
675
|
+
const allErrors = [
|
|
676
|
+
...new Map([...md.errors, ...linkErrors, ...code.errors].map((error) => [
|
|
677
|
+
`${error.file}\0${error.line}\0${error.target}\0${error.message}`,
|
|
678
|
+
error,
|
|
679
|
+
])).values(),
|
|
636
680
|
];
|
|
681
|
+
const s = ctx.styler;
|
|
682
|
+
const elapsedStr = elapsed < 1000
|
|
683
|
+
? `${Math.round(elapsed)}ms`
|
|
684
|
+
: `${(elapsed / 1000).toFixed(1)}s`;
|
|
685
|
+
const lines = profile ? profile.format(elapsed) : [];
|
|
637
686
|
// Init version warning first — user should fix setup before addressing errors
|
|
638
687
|
if (!ctx.headless) {
|
|
639
688
|
const storedVersion = readInitVersion(ctx.latDir);
|
|
640
689
|
if (storedVersion === null) {
|
|
641
|
-
lines.
|
|
690
|
+
if (lines.length > 0)
|
|
691
|
+
lines.push('');
|
|
692
|
+
lines.push(s.yellow('Warning:') +
|
|
642
693
|
' No init version recorded — run ' +
|
|
643
694
|
s.cyan('lat init') +
|
|
644
695
|
' to set up agent hooks and configuration.');
|
|
645
696
|
}
|
|
646
697
|
else if (storedVersion < INIT_VERSION) {
|
|
647
|
-
lines.
|
|
698
|
+
if (lines.length > 0)
|
|
699
|
+
lines.push('');
|
|
700
|
+
lines.push(s.yellow('Warning:') +
|
|
648
701
|
' Your setup is outdated (v' +
|
|
649
702
|
storedVersion +
|
|
650
703
|
' → v' +
|
|
@@ -672,7 +725,7 @@ export async function checkAllCommand(ctx, opts = {}) {
|
|
|
672
725
|
lines.push(formatErrorCount(totalErrors, s));
|
|
673
726
|
return { output: lines.join('\n'), isError: true };
|
|
674
727
|
}
|
|
675
|
-
lines.push(s.green(
|
|
728
|
+
lines.push(s.green(`All checks passed in ${elapsedStr}`));
|
|
676
729
|
// Suggest ripgrep if check was slow (>1s) and rg is not available
|
|
677
730
|
if (elapsed > 1000) {
|
|
678
731
|
const { hasRipgrep } = await import('../code-refs.js');
|
|
@@ -687,9 +740,9 @@ export async function checkAllCommand(ctx, opts = {}) {
|
|
|
687
740
|
return { output: lines.join('\n') };
|
|
688
741
|
}
|
|
689
742
|
export async function checkMdCommand(ctx) {
|
|
690
|
-
const { errors
|
|
743
|
+
const { errors } = await checkMd(ctx.latDir, ctx.projectRoot);
|
|
691
744
|
const s = ctx.styler;
|
|
692
|
-
const lines = [
|
|
745
|
+
const lines = [];
|
|
693
746
|
lines.push(...formatCheckErrors(errors, s));
|
|
694
747
|
if (errors.length > 0) {
|
|
695
748
|
lines.push(formatErrorCount(errors.length, s));
|
|
@@ -711,9 +764,9 @@ export async function checkLinksCommand(ctx) {
|
|
|
711
764
|
return { output: lines.join('\n') };
|
|
712
765
|
}
|
|
713
766
|
export async function checkCodeRefsCommand(ctx) {
|
|
714
|
-
const { errors
|
|
767
|
+
const { errors } = await checkCodeRefs(ctx.latDir, ctx.projectRoot);
|
|
715
768
|
const s = ctx.styler;
|
|
716
|
-
const lines = [
|
|
769
|
+
const lines = [];
|
|
717
770
|
lines.push(...formatCheckErrors(errors, s));
|
|
718
771
|
if (errors.length > 0) {
|
|
719
772
|
lines.push(formatErrorCount(errors.length, s));
|
|
@@ -723,7 +776,7 @@ export async function checkCodeRefsCommand(ctx) {
|
|
|
723
776
|
return { output: lines.join('\n') };
|
|
724
777
|
}
|
|
725
778
|
export async function checkIndexCommand(ctx, opts = {}) {
|
|
726
|
-
const errors = await checkIndex(ctx.latDir, opts);
|
|
779
|
+
const errors = await checkIndex(ctx.latDir, undefined, opts);
|
|
727
780
|
const s = ctx.styler;
|
|
728
781
|
const lines = [];
|
|
729
782
|
if (opts.fix) {
|
package/dist/src/cli/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { statSync } from 'node:fs';
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import { styleText } from 'node:util';
|
|
4
|
-
import { findLatticeDir } from '../
|
|
4
|
+
import { findLatticeDir } from '../project-discovery.js';
|
|
5
5
|
export function makeStyler() {
|
|
6
6
|
return {
|
|
7
7
|
bold: (s) => styleText('bold', s),
|