@goflag/cli 0.2.9 → 0.2.10

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/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  renderTerminal,
13
13
  runAudit,
14
14
  summarize
15
- } from "./chunk-TAZEAIFI.js";
15
+ } from "./chunk-DKGBLJ4H.js";
16
16
 
17
17
  // src/report/diff.ts
18
18
  function collectFindings(report) {
package/dist/index.d.ts CHANGED
@@ -48,6 +48,16 @@ type TagOrigin = {
48
48
  kind: "computed";
49
49
  };
50
50
  type Severity = "error" | "warning" | "info";
51
+ /**
52
+ * How authoritative the requirement behind a finding is.
53
+ *
54
+ * Lives here rather than in the rule layer for the same reason `Issue` does:
55
+ * it crosses the engine → report → CLI `--json` boundary. An agent reading a
56
+ * report has to be able to tell a `spec-required` from a `heuristic` before it
57
+ * decides what to change, and a type that stopped at the rule registry could
58
+ * not tell it — `../rules/types` re-exports this one.
59
+ */
60
+ type Rigor = "spec-required" | "spec-recommended" | "vendor-spec" | "guideline" | "heuristic";
51
61
 
52
62
  /**
53
63
  * Hreflang reciprocity + matrix derivation (Phase 7.4 / 7.5).
@@ -424,8 +434,6 @@ interface Extraction {
424
434
  * findings into issues at that boundary.
425
435
  */
426
436
 
427
- /** How authoritative the requirement behind a rule is. */
428
- type Rigor = "spec-required" | "spec-recommended" | "vendor-spec" | "guideline" | "heuristic";
429
437
  /** A band a scored measurement can land in. */
430
438
  type Band = "ideal" | "acceptable" | "poor";
431
439
  /** Everything a rule declares about itself, before any evaluation. */
@@ -742,6 +750,13 @@ interface SiteIssue {
742
750
  why?: string;
743
751
  /** A copy-pasteable fix snippet, when the rule offers one. */
744
752
  fix?: string;
753
+ /**
754
+ * Same two fields as `SeoIssue`, and absent for a different reason: a
755
+ * `SiteRule` may simply not declare a rigor yet. Three of the four do not,
756
+ * and the catalogue reports that gap rather than inventing an authority.
757
+ */
758
+ rigor?: Rigor;
759
+ sources?: string[];
745
760
  }
746
761
  /** How the audit established which locales the site serves. */
747
762
  interface LocaleAxisReport {
@@ -774,6 +789,20 @@ interface SeoIssue {
774
789
  why?: string;
775
790
  /** A copy-pasteable fix snippet, when the rule offers one. */
776
791
  fix?: string;
792
+ /**
793
+ * How authoritative the requirement is, and the documents that back it.
794
+ *
795
+ * Carried on the finding because a report is read where the rule registry is
796
+ * not — a JSON file in CI, a PR comment, an agent's context. Without it a
797
+ * consumer cannot tell a `spec-required` from a `heuristic`, which is the one
798
+ * distinction the rigor axis exists to make.
799
+ */
800
+ rigor?: Rigor;
801
+ sources?: string[];
802
+ /** What the page actually said, as the rule saw it. */
803
+ observed?: unknown;
804
+ /** One sentence stating what a passing page looks like. */
805
+ expected?: string;
777
806
  }
778
807
  /**
779
808
  * One prose rule's evidence bundle, attributed to a page. The `id` is a
@@ -1104,6 +1133,12 @@ interface RollupLink {
1104
1133
  interface RollupSeo {
1105
1134
  ruleId: string;
1106
1135
  severity: Severity;
1136
+ /**
1137
+ * How authoritative the rule is. Constant within a group — a rollup is one
1138
+ * rule — which is what makes the summary the right place to show it: one tag
1139
+ * per line instead of one per finding.
1140
+ */
1141
+ rigor?: Rigor;
1107
1142
  why?: string;
1108
1143
  fix?: string;
1109
1144
  /** A representative message (from the first occurrence). */
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  runAudit,
12
12
  summarize,
13
13
  targetKey
14
- } from "./chunk-TAZEAIFI.js";
14
+ } from "./chunk-DKGBLJ4H.js";
15
15
  export {
16
16
  Logger,
17
17
  SAMPLE_LIMIT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goflag/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "type": "module",
5
5
  "description": "Audits a site for broken links, missing translation pages, a robots.txt that contradicts your pages, and missing or misconfigured SEO metadata. JSON-first.",
6
6
  "keywords": [
package/rules.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "counts": {
3
3
  "page": 23,
4
- "site": 4,
4
+ "site": 26,
5
5
  "prose": 4
6
6
  },
7
7
  "sources": {
@@ -33,6 +33,13 @@
33
33
  "rigor": "vendor-spec",
34
34
  "url": "https://developers.google.com/search/docs/specialty/international/localized-versions"
35
35
  },
36
+ "google-robots-intro": {
37
+ "id": "google-robots-intro",
38
+ "publisher": "Google",
39
+ "title": "Introduction to robots.txt",
40
+ "rigor": "vendor-spec",
41
+ "url": "https://developers.google.com/search/docs/crawling-indexing/robots/intro"
42
+ },
36
43
  "google-robots-meta": {
37
44
  "id": "google-robots-meta",
38
45
  "publisher": "Google",
@@ -40,6 +47,13 @@
40
47
  "rigor": "vendor-spec",
41
48
  "url": "https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag"
42
49
  },
50
+ "google-sitemaps": {
51
+ "id": "google-sitemaps",
52
+ "publisher": "Google",
53
+ "title": "Build and submit a sitemap",
54
+ "rigor": "guideline",
55
+ "url": "https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview"
56
+ },
43
57
  "google-snippet": {
44
58
  "id": "google-snippet",
45
59
  "publisher": "Google",
@@ -61,6 +75,13 @@
61
75
  "rigor": "normative",
62
76
  "url": "https://www.rfc-editor.org/rfc/rfc6596"
63
77
  },
78
+ "ietf-rfc9309": {
79
+ "id": "ietf-rfc9309",
80
+ "publisher": "IETF",
81
+ "title": "RFC 9309 — Robots Exclusion Protocol",
82
+ "rigor": "normative",
83
+ "url": "https://www.rfc-editor.org/rfc/rfc9309"
84
+ },
64
85
  "mdn-link-rel": {
65
86
  "id": "mdn-link-rel",
66
87
  "publisher": "MDN / Mozilla",
@@ -103,6 +124,13 @@
103
124
  "rigor": "vendor-spec",
104
125
  "url": "https://ogp.me/"
105
126
  },
127
+ "sitemaps-protocol": {
128
+ "id": "sitemaps-protocol",
129
+ "publisher": "sitemaps.org",
130
+ "title": "Sitemaps XML protocol",
131
+ "rigor": "normative",
132
+ "url": "https://www.sitemaps.org/protocol.html"
133
+ },
106
134
  "w3c-appmanifest": {
107
135
  "id": "w3c-appmanifest",
108
136
  "publisher": "W3C",
@@ -744,14 +772,29 @@
744
772
  ],
745
773
  "expected": "an explicit `og:title`"
746
774
  },
775
+ {
776
+ "id": "robots.blocks-page",
777
+ "scope": "site",
778
+ "severity": "error",
779
+ "kind": "boolean",
780
+ "summary": "A page asking to be indexed must not be disallowed by robots.txt",
781
+ "rigor": "vendor-spec",
782
+ "sources": [
783
+ "ietf-rfc9309",
784
+ "google-robots-intro"
785
+ ]
786
+ },
747
787
  {
748
788
  "id": "robots.blocks-site",
749
789
  "scope": "site",
750
790
  "severity": "error",
751
791
  "kind": "boolean",
752
792
  "summary": "`robots.txt` must not forbid crawling a site that asks to be indexed",
753
- "rigor": null,
754
- "sources": []
793
+ "rigor": "vendor-spec",
794
+ "sources": [
795
+ "ietf-rfc9309",
796
+ "google-robots-intro"
797
+ ]
755
798
  },
756
799
  {
757
800
  "id": "robots.conflict",
@@ -772,6 +815,248 @@
772
815
  ],
773
816
  "expected": "consistent indexing directives across meta tags and headers"
774
817
  },
818
+ {
819
+ "id": "robotstxt.cross-origin",
820
+ "scope": "site",
821
+ "severity": "warning",
822
+ "kind": "boolean",
823
+ "summary": "`/robots.txt` should not redirect to another origin",
824
+ "rigor": "vendor-spec",
825
+ "sources": [
826
+ "ietf-rfc9309"
827
+ ]
828
+ },
829
+ {
830
+ "id": "robotstxt.invalid-line",
831
+ "scope": "site",
832
+ "severity": "warning",
833
+ "kind": "boolean",
834
+ "summary": "Every line of robots.txt should parse as something",
835
+ "rigor": "spec-required",
836
+ "sources": [
837
+ "ietf-rfc9309"
838
+ ]
839
+ },
840
+ {
841
+ "id": "robotstxt.oversized",
842
+ "scope": "site",
843
+ "severity": "error",
844
+ "kind": "boolean",
845
+ "summary": "Rules past 500 KiB of robots.txt are not guaranteed to be read",
846
+ "rigor": "spec-required",
847
+ "sources": [
848
+ "ietf-rfc9309"
849
+ ]
850
+ },
851
+ {
852
+ "id": "robotstxt.sitemap.relative",
853
+ "scope": "site",
854
+ "severity": "error",
855
+ "kind": "boolean",
856
+ "summary": "A `Sitemap:` declaration must be an absolute URL",
857
+ "rigor": "spec-required",
858
+ "sources": [
859
+ "sitemaps-protocol",
860
+ "ietf-rfc9309"
861
+ ]
862
+ },
863
+ {
864
+ "id": "robotstxt.unknown-directive",
865
+ "scope": "site",
866
+ "severity": "info",
867
+ "kind": "boolean",
868
+ "summary": "Non-standard robots.txt directives are read by some crawlers and ignored by others",
869
+ "rigor": "guideline",
870
+ "sources": [
871
+ "ietf-rfc9309",
872
+ "google-robots-intro"
873
+ ]
874
+ },
875
+ {
876
+ "id": "robotstxt.unreachable",
877
+ "scope": "site",
878
+ "severity": "error",
879
+ "kind": "boolean",
880
+ "summary": "A robots.txt that errors is read as forbidding the whole site",
881
+ "rigor": "spec-required",
882
+ "sources": [
883
+ "ietf-rfc9309"
884
+ ]
885
+ },
886
+ {
887
+ "id": "sitemap.empty",
888
+ "scope": "site",
889
+ "severity": "warning",
890
+ "kind": "boolean",
891
+ "summary": "A sitemap that parses should list something",
892
+ "rigor": "guideline",
893
+ "sources": [
894
+ "sitemaps-protocol"
895
+ ]
896
+ },
897
+ {
898
+ "id": "sitemap.entry.blocked-by-robots",
899
+ "scope": "site",
900
+ "severity": "error",
901
+ "kind": "boolean",
902
+ "summary": "A sitemap must not list URLs that robots.txt forbids fetching",
903
+ "rigor": "vendor-spec",
904
+ "sources": [
905
+ "ietf-rfc9309",
906
+ "sitemaps-protocol"
907
+ ]
908
+ },
909
+ {
910
+ "id": "sitemap.entry.cross-host",
911
+ "scope": "site",
912
+ "severity": "error",
913
+ "kind": "boolean",
914
+ "summary": "A sitemap should only list URLs on its own host",
915
+ "rigor": "spec-required",
916
+ "sources": [
917
+ "sitemaps-protocol"
918
+ ]
919
+ },
920
+ {
921
+ "id": "sitemap.entry.invalid-url",
922
+ "scope": "site",
923
+ "severity": "error",
924
+ "kind": "boolean",
925
+ "summary": "Every `<loc>` must be an absolute, parseable URL",
926
+ "rigor": "spec-required",
927
+ "sources": [
928
+ "sitemaps-protocol"
929
+ ]
930
+ },
931
+ {
932
+ "id": "sitemap.entry.noindex",
933
+ "scope": "site",
934
+ "severity": "warning",
935
+ "kind": "boolean",
936
+ "summary": "A sitemap must not list URLs that ask not to be indexed",
937
+ "rigor": "vendor-spec",
938
+ "sources": [
939
+ "sitemaps-protocol",
940
+ "google-robots-meta"
941
+ ]
942
+ },
943
+ {
944
+ "id": "sitemap.entry.non-canonical",
945
+ "scope": "site",
946
+ "severity": "warning",
947
+ "kind": "boolean",
948
+ "summary": "A sitemap should list canonical URLs",
949
+ "rigor": "vendor-spec",
950
+ "sources": [
951
+ "sitemaps-protocol",
952
+ "google-canonicalization"
953
+ ]
954
+ },
955
+ {
956
+ "id": "sitemap.entry.protocol-mismatch",
957
+ "scope": "site",
958
+ "severity": "warning",
959
+ "kind": "boolean",
960
+ "summary": "A sitemap should not mix `http` and `https` entries",
961
+ "rigor": "spec-required",
962
+ "sources": [
963
+ "sitemaps-protocol"
964
+ ]
965
+ },
966
+ {
967
+ "id": "sitemap.entry.redirects",
968
+ "scope": "site",
969
+ "severity": "warning",
970
+ "kind": "boolean",
971
+ "summary": "A sitemap should list final URLs, not URLs that redirect",
972
+ "rigor": "guideline",
973
+ "sources": [
974
+ "sitemaps-protocol",
975
+ "google-sitemaps"
976
+ ]
977
+ },
978
+ {
979
+ "id": "sitemap.entry.unreachable",
980
+ "scope": "site",
981
+ "severity": "error",
982
+ "kind": "boolean",
983
+ "summary": "Every URL a sitemap lists must answer",
984
+ "rigor": "vendor-spec",
985
+ "sources": [
986
+ "sitemaps-protocol",
987
+ "google-sitemaps"
988
+ ]
989
+ },
990
+ {
991
+ "id": "sitemap.field.invalid",
992
+ "scope": "site",
993
+ "severity": "warning",
994
+ "kind": "boolean",
995
+ "summary": "`<changefreq>` and `<priority>` must hold the values the protocol defines",
996
+ "rigor": "spec-required",
997
+ "sources": [
998
+ "sitemaps-protocol",
999
+ "google-sitemaps"
1000
+ ]
1001
+ },
1002
+ {
1003
+ "id": "sitemap.index.child-error",
1004
+ "scope": "site",
1005
+ "severity": "error",
1006
+ "kind": "boolean",
1007
+ "summary": "Every child of a sitemap index must be reachable and parseable",
1008
+ "rigor": "spec-required",
1009
+ "sources": [
1010
+ "sitemaps-protocol"
1011
+ ]
1012
+ },
1013
+ {
1014
+ "id": "sitemap.lastmod.invalid",
1015
+ "scope": "site",
1016
+ "severity": "warning",
1017
+ "kind": "boolean",
1018
+ "summary": "`<lastmod>` must be a W3C Datetime, and must not be in the future",
1019
+ "rigor": "spec-required",
1020
+ "sources": [
1021
+ "sitemaps-protocol",
1022
+ "google-sitemaps"
1023
+ ]
1024
+ },
1025
+ {
1026
+ "id": "sitemap.missing",
1027
+ "scope": "site",
1028
+ "severity": "warning",
1029
+ "kind": "boolean",
1030
+ "summary": "A site should publish a sitemap",
1031
+ "rigor": "guideline",
1032
+ "sources": [
1033
+ "sitemaps-protocol",
1034
+ "google-sitemaps"
1035
+ ]
1036
+ },
1037
+ {
1038
+ "id": "sitemap.orphans",
1039
+ "scope": "site",
1040
+ "severity": "warning",
1041
+ "kind": "boolean",
1042
+ "summary": "Indexable pages the crawl found should be listed in the sitemap",
1043
+ "rigor": "guideline",
1044
+ "sources": [
1045
+ "sitemaps-protocol",
1046
+ "google-sitemaps"
1047
+ ]
1048
+ },
1049
+ {
1050
+ "id": "sitemap.unparsable",
1051
+ "scope": "site",
1052
+ "severity": "error",
1053
+ "kind": "boolean",
1054
+ "summary": "A located sitemap must be a well-formed `<urlset>` or `<sitemapindex>`",
1055
+ "rigor": "spec-required",
1056
+ "sources": [
1057
+ "sitemaps-protocol"
1058
+ ]
1059
+ },
775
1060
  {
776
1061
  "id": "title.descriptive",
777
1062
  "scope": "prose",