turva-llms-txt-validator 0.3.18 → 0.3.19
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 +2 -2
- package/package.json +1 -1
- package/src/index.mjs +35 -2
package/README.md
CHANGED
|
@@ -137,10 +137,10 @@ For vulnerability reporting and supported versions, see [SECURITY.md](https://gi
|
|
|
137
137
|
GitHub Actions publishes every version from 0.1.3 on with npm trusted publishing, and each one carries a provenance attestation. To check one, install it in an empty directory and ask npm to verify the signatures. Replace the version with the one you want to check.
|
|
138
138
|
|
|
139
139
|
```sh
|
|
140
|
-
mkdir verify-llms && cd verify-llms && npm init -y && npm install turva-llms-txt-validator@0.3.
|
|
140
|
+
mkdir verify-llms && cd verify-llms && npm init -y && npm install turva-llms-txt-validator@0.3.19 --ignore-scripts && npm audit signatures
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
`npm audit signatures` checks the registry signature and the provenance attestation of each installed package that has one. The attestation of this package names three things to compare with what you expect: the repository `github.com/erekola/llms-txt-validator`, the workflow file `.github/workflows/publish.yml` and the commit that produced the tarball. The npm version page shows them under Provenance. Tag `v0.3.
|
|
143
|
+
`npm audit signatures` checks the registry signature and the provenance attestation of each installed package that has one. The attestation of this package names three things to compare with what you expect: the repository `github.com/erekola/llms-txt-validator`, the workflow file `.github/workflows/publish.yml` and the commit that produced the tarball. The npm version page shows them under Provenance. Tag `v0.3.19` must point at that same commit, and `gh api repos/erekola/llms-txt-validator/commits/v0.3.19 --jq .sha` prints it. A mismatch is a reason not to use that version. Provenance proves where a release was built and from which commit. It does not prove that the code is safe, so reading the source and the dependencies stays your job.
|
|
144
144
|
|
|
145
145
|
## Hosted version and source
|
|
146
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turva-llms-txt-validator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "Validate a site's llms.txt structure: eight checks as pass/warn/fail plus two llms.txt v2 discovery checks that read pass or info and never move the summary, JSON output. Same checks as the hosted validator at turva.dev/llms-txt-validator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
package/src/index.mjs
CHANGED
|
@@ -539,6 +539,38 @@ function isSetextH1(line, next) {
|
|
|
539
539
|
return isSetextText(line);
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
// Lines that open a block of their own in CommonMark, so they are never setext text after the
|
|
543
|
+
// title: a thematic break, the start of an HTML block (an autolink such as <https://...> is not
|
|
544
|
+
// one, it stays paragraph text) and a link reference definition. Only the later-H1 reading skips
|
|
545
|
+
// them; the title reading keeps its Tek-560 predicate. QA 2 of Tek-564 measured "---", "<div>" and
|
|
546
|
+
// "[a]: url" over a run of "=" warning as a second H1 although 0.3.18 passed them.
|
|
547
|
+
function startsOtherBlock(line) {
|
|
548
|
+
return /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/.test(line)
|
|
549
|
+
|| /^ {0,3}<(?:[A-Za-z][A-Za-z0-9-]*(?:[\s\/>]|$)|\/[A-Za-z][A-Za-z0-9-]*(?:[\s>]|$)|!--|\?|![A-Za-z]|!\[CDATA\[)/.test(line)
|
|
550
|
+
|| /^ {0,3}\[[^\]]+\]:/.test(line);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Setext H1 lines after the title (Tek-564). A second H1 written as one text line over a run of "="
|
|
554
|
+
// is out of place exactly like "# Second title" is, and ends the section before it the same way.
|
|
555
|
+
// The text line is read by isSetextH1, the predicate the first-title reading uses. It must start
|
|
556
|
+
// a paragraph: a line that follows other paragraph text, a list item or a blockquote without a
|
|
557
|
+
// blank line is a continuation line, so a title over several lines is not read as a heading, and
|
|
558
|
+
// text that starts with "#" is not an H1 (isSetextText). The underline is returned separately
|
|
559
|
+
// because it is not a heading and not content.
|
|
560
|
+
function laterSetextH1(lines, fenced) {
|
|
561
|
+
const text = new Array(lines.length).fill(false);
|
|
562
|
+
const under = new Array(lines.length).fill(false);
|
|
563
|
+
let open = false;
|
|
564
|
+
for (let i = 0; i < lines.length; i++) {
|
|
565
|
+
const l = lines[i];
|
|
566
|
+
if (fenced[i] || l.trim() === "") { open = false; continue; }
|
|
567
|
+
if (!open && !fenced[i + 1] && !startsOtherBlock(l) && isSetextH1(l, lines[i + 1])) { text[i] = true; under[i + 1] = true; i++; continue; }
|
|
568
|
+
if (open && /^ {0,3}=+[ \t]*$/.test(l)) { open = false; continue; }
|
|
569
|
+
open = !(/^ {0,3}#{1,6}(?:[ \t]|$)/.test(l) || /^ {0,3}(?:-+|([-*_])(?: *\1){2,})[ \t]*$/.test(l));
|
|
570
|
+
}
|
|
571
|
+
return { text, under };
|
|
572
|
+
}
|
|
573
|
+
|
|
542
574
|
// Which lines are H2 headings, outside fences. One reader for every place that asks "is this an
|
|
543
575
|
// H2": the section count, the section membership and the place where the late-summary scan stops.
|
|
544
576
|
// An ATX H2 is "##" followed by a space or a tab (a bare "##" is not one, and "###" is deeper). A
|
|
@@ -735,6 +767,7 @@ export function validateLlmsTxt(f) {
|
|
|
735
767
|
}
|
|
736
768
|
}
|
|
737
769
|
const hm = h2Lines(lines, fenced);
|
|
770
|
+
const sx = laterSetextH1(lines, fenced);
|
|
738
771
|
const h2Count = hm.h2.filter(Boolean).length;
|
|
739
772
|
// A section counts when it carries a file list. An H2 followed by a paragraph satisfied
|
|
740
773
|
// this check until 2026-08-29, and the format puts each section's links in a list.
|
|
@@ -758,9 +791,9 @@ export function validateLlmsTxt(f) {
|
|
|
758
791
|
if (itemOpen) afterFence = true;
|
|
759
792
|
continue;
|
|
760
793
|
}
|
|
761
|
-
if (hm.under[i]) continue;
|
|
794
|
+
if (hm.under[i] || sx.under[i]) continue;
|
|
762
795
|
if (hm.h2[i]) { inSection = true; counted = false; seenH2 = true; itemOpen = false; continue; }
|
|
763
|
-
const h1 = /^ {0,3}#(?:[ \t]|$)/.test(l);
|
|
796
|
+
const h1 = /^ {0,3}#(?:[ \t]|$)/.test(l) || sx.text[i];
|
|
764
797
|
if (misplaced === -1 && i > firstIdx && (h1 || (!seenH2 && /^ {0,3}#{3,6}(?:[ \t]|$)/.test(l)))) misplaced = i;
|
|
765
798
|
if (h1) { inSection = false; itemOpen = false; continue; }
|
|
766
799
|
// A link on a line that continues an open list item counts as that item's link (Tek-560):
|