@goflag/cli 0.2.9 → 0.2.11

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/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. */
@@ -509,7 +517,17 @@ interface AdvisoryFinding {
509
517
  ruleId: string;
510
518
  kind: "prose";
511
519
  prose: string;
512
- rigor: Rigor;
520
+ /**
521
+ * `null` where no document supports the question.
522
+ *
523
+ * Every page prose rule carries one. The cross-page ones (`./site-prose.ts`)
524
+ * may not: `hreflang.sitemap-mismatch` asks about a disagreement no
525
+ * specification addresses, and inventing a rigor for it would put back the
526
+ * dishonesty that moving it out of the verdict-bearing registry removed.
527
+ * An agent reads this to weight the question; `null` says "weight it on the
528
+ * evidence alone".
529
+ */
530
+ rigor: Rigor | null;
513
531
  sources: string[];
514
532
  /**
515
533
  * Extraction path → the observed value there, one entry per declared
@@ -742,6 +760,13 @@ interface SiteIssue {
742
760
  why?: string;
743
761
  /** A copy-pasteable fix snippet, when the rule offers one. */
744
762
  fix?: string;
763
+ /**
764
+ * Same two fields as `SeoIssue`, and absent for a different reason: a
765
+ * `SiteRule` may simply not declare a rigor yet. Three of the four do not,
766
+ * and the catalogue reports that gap rather than inventing an authority.
767
+ */
768
+ rigor?: Rigor;
769
+ sources?: string[];
745
770
  }
746
771
  /** How the audit established which locales the site serves. */
747
772
  interface LocaleAxisReport {
@@ -774,6 +799,20 @@ interface SeoIssue {
774
799
  why?: string;
775
800
  /** A copy-pasteable fix snippet, when the rule offers one. */
776
801
  fix?: string;
802
+ /**
803
+ * How authoritative the requirement is, and the documents that back it.
804
+ *
805
+ * Carried on the finding because a report is read where the rule registry is
806
+ * not — a JSON file in CI, a PR comment, an agent's context. Without it a
807
+ * consumer cannot tell a `spec-required` from a `heuristic`, which is the one
808
+ * distinction the rigor axis exists to make.
809
+ */
810
+ rigor?: Rigor;
811
+ sources?: string[];
812
+ /** What the page actually said, as the rule saw it. */
813
+ observed?: unknown;
814
+ /** One sentence stating what a passing page looks like. */
815
+ expected?: string;
777
816
  }
778
817
  /**
779
818
  * One prose rule's evidence bundle, attributed to a page. The `id` is a
@@ -1104,6 +1143,12 @@ interface RollupLink {
1104
1143
  interface RollupSeo {
1105
1144
  ruleId: string;
1106
1145
  severity: Severity;
1146
+ /**
1147
+ * How authoritative the rule is. Constant within a group — a rollup is one
1148
+ * rule — which is what makes the summary the right place to show it: one tag
1149
+ * per line instead of one per finding.
1150
+ */
1151
+ rigor?: Rigor;
1107
1152
  why?: string;
1108
1153
  fix?: string;
1109
1154
  /** 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-CWEJ6EXT.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.11",
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,8 +1,8 @@
1
1
  {
2
2
  "counts": {
3
3
  "page": 23,
4
- "site": 4,
5
- "prose": 4
4
+ "site": 28,
5
+ "prose": 5
6
6
  },
7
7
  "sources": {
8
8
  "apple-web-apps": {
@@ -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",
@@ -291,23 +319,39 @@
291
319
  "language": "tsx"
292
320
  }
293
321
  },
322
+ {
323
+ "id": "hreflang.cluster-incomplete",
324
+ "scope": "site",
325
+ "severity": "warning",
326
+ "kind": "boolean",
327
+ "summary": "Every locale the sitemap publishes must appear in the route's `hreflang` cluster",
328
+ "rigor": "vendor-spec",
329
+ "sources": [
330
+ "google-hreflang"
331
+ ]
332
+ },
294
333
  {
295
334
  "id": "hreflang.missing",
296
335
  "scope": "site",
297
336
  "severity": "error",
298
337
  "kind": "boolean",
299
338
  "summary": "Pages on a multilingual site must advertise their locale alternates",
300
- "rigor": null,
301
- "sources": []
339
+ "rigor": "vendor-spec",
340
+ "sources": [
341
+ "google-hreflang",
342
+ "whatwg-html-link-types"
343
+ ]
302
344
  },
303
345
  {
304
346
  "id": "hreflang.sitemap-mismatch",
305
- "scope": "site",
306
- "severity": "warning",
307
- "kind": "boolean",
308
- "summary": "`<head>` alternates and sitemap locale coverage must agree",
347
+ "scope": "prose",
348
+ "severity": null,
349
+ "kind": "prose",
350
+ "summary": "`<head>` alternates advertise a locale the sitemap does not list",
351
+ "why": "The two are derived from one intent by different code paths, so a disagreement means one of them is wrong. No specification says which: the declaration methods are equivalent, and a page can legitimately be cross-linked and kept out of the sitemap.",
309
352
  "rigor": null,
310
- "sources": []
353
+ "sources": [],
354
+ "prose": "Your `<head>` advertises a translation your sitemap does not list. Is the sitemap missing an entry, or is the alternate pointing at a page you did not mean to publish?"
311
355
  },
312
356
  {
313
357
  "id": "icons.apple-touch.missing",
@@ -744,14 +788,29 @@
744
788
  ],
745
789
  "expected": "an explicit `og:title`"
746
790
  },
791
+ {
792
+ "id": "robots.blocks-page",
793
+ "scope": "site",
794
+ "severity": "error",
795
+ "kind": "boolean",
796
+ "summary": "A page asking to be indexed must not be disallowed by robots.txt",
797
+ "rigor": "vendor-spec",
798
+ "sources": [
799
+ "ietf-rfc9309",
800
+ "google-robots-intro"
801
+ ]
802
+ },
747
803
  {
748
804
  "id": "robots.blocks-site",
749
805
  "scope": "site",
750
806
  "severity": "error",
751
807
  "kind": "boolean",
752
808
  "summary": "`robots.txt` must not forbid crawling a site that asks to be indexed",
753
- "rigor": null,
754
- "sources": []
809
+ "rigor": "vendor-spec",
810
+ "sources": [
811
+ "ietf-rfc9309",
812
+ "google-robots-intro"
813
+ ]
755
814
  },
756
815
  {
757
816
  "id": "robots.conflict",
@@ -772,6 +831,270 @@
772
831
  ],
773
832
  "expected": "consistent indexing directives across meta tags and headers"
774
833
  },
834
+ {
835
+ "id": "robotstxt.cross-origin",
836
+ "scope": "site",
837
+ "severity": "warning",
838
+ "kind": "boolean",
839
+ "summary": "`/robots.txt` should not redirect to another origin",
840
+ "rigor": "vendor-spec",
841
+ "sources": [
842
+ "ietf-rfc9309"
843
+ ]
844
+ },
845
+ {
846
+ "id": "robotstxt.invalid-line",
847
+ "scope": "site",
848
+ "severity": "warning",
849
+ "kind": "boolean",
850
+ "summary": "Every line of robots.txt should parse as something",
851
+ "rigor": "spec-required",
852
+ "sources": [
853
+ "ietf-rfc9309"
854
+ ]
855
+ },
856
+ {
857
+ "id": "robotstxt.oversized",
858
+ "scope": "site",
859
+ "severity": "error",
860
+ "kind": "boolean",
861
+ "summary": "Rules past 500 KiB of robots.txt are not guaranteed to be read",
862
+ "rigor": "spec-required",
863
+ "sources": [
864
+ "ietf-rfc9309"
865
+ ]
866
+ },
867
+ {
868
+ "id": "robotstxt.sitemap.relative",
869
+ "scope": "site",
870
+ "severity": "error",
871
+ "kind": "boolean",
872
+ "summary": "A `Sitemap:` declaration must be an absolute URL",
873
+ "rigor": "spec-required",
874
+ "sources": [
875
+ "sitemaps-protocol",
876
+ "ietf-rfc9309"
877
+ ]
878
+ },
879
+ {
880
+ "id": "robotstxt.unknown-directive",
881
+ "scope": "site",
882
+ "severity": "info",
883
+ "kind": "boolean",
884
+ "summary": "Non-standard robots.txt directives are read by some crawlers and ignored by others",
885
+ "rigor": "guideline",
886
+ "sources": [
887
+ "ietf-rfc9309",
888
+ "google-robots-intro"
889
+ ]
890
+ },
891
+ {
892
+ "id": "robotstxt.unreachable",
893
+ "scope": "site",
894
+ "severity": "error",
895
+ "kind": "boolean",
896
+ "summary": "A robots.txt that errors is read as forbidding the whole site",
897
+ "rigor": "spec-required",
898
+ "sources": [
899
+ "ietf-rfc9309"
900
+ ]
901
+ },
902
+ {
903
+ "id": "sitemap.empty",
904
+ "scope": "site",
905
+ "severity": "warning",
906
+ "kind": "boolean",
907
+ "summary": "A sitemap that parses should list something",
908
+ "rigor": "guideline",
909
+ "sources": [
910
+ "sitemaps-protocol"
911
+ ]
912
+ },
913
+ {
914
+ "id": "sitemap.entry.blocked-by-robots",
915
+ "scope": "site",
916
+ "severity": "error",
917
+ "kind": "boolean",
918
+ "summary": "A sitemap must not list URLs that robots.txt forbids fetching",
919
+ "rigor": "vendor-spec",
920
+ "sources": [
921
+ "ietf-rfc9309",
922
+ "sitemaps-protocol"
923
+ ]
924
+ },
925
+ {
926
+ "id": "sitemap.entry.cross-host",
927
+ "scope": "site",
928
+ "severity": "error",
929
+ "kind": "boolean",
930
+ "summary": "A sitemap should only list URLs on its own host",
931
+ "rigor": "spec-required",
932
+ "sources": [
933
+ "sitemaps-protocol"
934
+ ]
935
+ },
936
+ {
937
+ "id": "sitemap.entry.invalid-url",
938
+ "scope": "site",
939
+ "severity": "error",
940
+ "kind": "boolean",
941
+ "summary": "Every `<loc>` must be an absolute, parseable URL",
942
+ "rigor": "spec-required",
943
+ "sources": [
944
+ "sitemaps-protocol"
945
+ ]
946
+ },
947
+ {
948
+ "id": "sitemap.entry.noindex",
949
+ "scope": "site",
950
+ "severity": "warning",
951
+ "kind": "boolean",
952
+ "summary": "A sitemap must not list URLs that ask not to be indexed",
953
+ "rigor": "vendor-spec",
954
+ "sources": [
955
+ "sitemaps-protocol",
956
+ "google-robots-meta"
957
+ ]
958
+ },
959
+ {
960
+ "id": "sitemap.entry.non-canonical",
961
+ "scope": "site",
962
+ "severity": "warning",
963
+ "kind": "boolean",
964
+ "summary": "A sitemap should list canonical URLs",
965
+ "rigor": "vendor-spec",
966
+ "sources": [
967
+ "sitemaps-protocol",
968
+ "google-canonicalization"
969
+ ]
970
+ },
971
+ {
972
+ "id": "sitemap.entry.out-of-scope",
973
+ "scope": "site",
974
+ "severity": "error",
975
+ "kind": "boolean",
976
+ "summary": "A sitemap may only list URLs under its own directory",
977
+ "rigor": "spec-required",
978
+ "sources": [
979
+ "sitemaps-protocol"
980
+ ]
981
+ },
982
+ {
983
+ "id": "sitemap.entry.protocol-mismatch",
984
+ "scope": "site",
985
+ "severity": "warning",
986
+ "kind": "boolean",
987
+ "summary": "A sitemap should not mix `http` and `https` entries",
988
+ "rigor": "spec-required",
989
+ "sources": [
990
+ "sitemaps-protocol"
991
+ ]
992
+ },
993
+ {
994
+ "id": "sitemap.entry.redirects",
995
+ "scope": "site",
996
+ "severity": "warning",
997
+ "kind": "boolean",
998
+ "summary": "A sitemap should list final URLs, not URLs that redirect",
999
+ "rigor": "guideline",
1000
+ "sources": [
1001
+ "sitemaps-protocol",
1002
+ "google-sitemaps"
1003
+ ]
1004
+ },
1005
+ {
1006
+ "id": "sitemap.entry.unreachable",
1007
+ "scope": "site",
1008
+ "severity": "error",
1009
+ "kind": "boolean",
1010
+ "summary": "Every URL a sitemap lists must answer",
1011
+ "rigor": "vendor-spec",
1012
+ "sources": [
1013
+ "sitemaps-protocol",
1014
+ "google-sitemaps"
1015
+ ]
1016
+ },
1017
+ {
1018
+ "id": "sitemap.field.invalid",
1019
+ "scope": "site",
1020
+ "severity": "warning",
1021
+ "kind": "boolean",
1022
+ "summary": "`<changefreq>` and `<priority>` must hold the values the protocol defines",
1023
+ "rigor": "spec-required",
1024
+ "sources": [
1025
+ "sitemaps-protocol",
1026
+ "google-sitemaps"
1027
+ ]
1028
+ },
1029
+ {
1030
+ "id": "sitemap.index.child-error",
1031
+ "scope": "site",
1032
+ "severity": "error",
1033
+ "kind": "boolean",
1034
+ "summary": "Every child of a sitemap index must be reachable and parseable",
1035
+ "rigor": "spec-required",
1036
+ "sources": [
1037
+ "sitemaps-protocol"
1038
+ ]
1039
+ },
1040
+ {
1041
+ "id": "sitemap.lastmod.invalid",
1042
+ "scope": "site",
1043
+ "severity": "warning",
1044
+ "kind": "boolean",
1045
+ "summary": "`<lastmod>` must be a W3C Datetime, and must not be in the future",
1046
+ "rigor": "spec-required",
1047
+ "sources": [
1048
+ "sitemaps-protocol",
1049
+ "google-sitemaps"
1050
+ ]
1051
+ },
1052
+ {
1053
+ "id": "sitemap.limits.exceeded",
1054
+ "scope": "site",
1055
+ "severity": "error",
1056
+ "kind": "boolean",
1057
+ "summary": "No single sitemap document may exceed 50,000 entries or 50 MB",
1058
+ "rigor": "spec-required",
1059
+ "sources": [
1060
+ "sitemaps-protocol"
1061
+ ]
1062
+ },
1063
+ {
1064
+ "id": "sitemap.missing",
1065
+ "scope": "site",
1066
+ "severity": "warning",
1067
+ "kind": "boolean",
1068
+ "summary": "A site should publish a sitemap",
1069
+ "rigor": "guideline",
1070
+ "sources": [
1071
+ "sitemaps-protocol",
1072
+ "google-sitemaps"
1073
+ ]
1074
+ },
1075
+ {
1076
+ "id": "sitemap.orphans",
1077
+ "scope": "site",
1078
+ "severity": "warning",
1079
+ "kind": "boolean",
1080
+ "summary": "Indexable pages the crawl found should be listed in the sitemap",
1081
+ "rigor": "guideline",
1082
+ "sources": [
1083
+ "sitemaps-protocol",
1084
+ "google-sitemaps"
1085
+ ]
1086
+ },
1087
+ {
1088
+ "id": "sitemap.unparsable",
1089
+ "scope": "site",
1090
+ "severity": "error",
1091
+ "kind": "boolean",
1092
+ "summary": "A located sitemap must be a well-formed `<urlset>` or `<sitemapindex>`",
1093
+ "rigor": "spec-required",
1094
+ "sources": [
1095
+ "sitemaps-protocol"
1096
+ ]
1097
+ },
775
1098
  {
776
1099
  "id": "title.descriptive",
777
1100
  "scope": "prose",