kaloko 0.18.0 → 0.20.0

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.
Files changed (44) hide show
  1. package/package.json +1 -1
  2. package/schemas/config.v1.json +47 -1
  3. package/schemas/run.v1.json +57 -1
  4. package/schemas/scenario.v1.json +100 -14
  5. package/skills/kaloko/reference/acceptance.md +18 -4
  6. package/skills/kaloko/reference/evaluation.md +44 -1
  7. package/skills/kaloko/reference/integrations.md +30 -1
  8. package/skills/kaloko/reference/mcp.md +3 -0
  9. package/skills/kaloko/reference/stacks.md +181 -16
  10. package/src/cli.mjs +191 -37
  11. package/src/lib/build.mjs +37 -7
  12. package/src/lib/capture.mjs +31 -9
  13. package/src/lib/config.mjs +15 -1
  14. package/src/lib/crawler.mjs +242 -0
  15. package/src/lib/draft.mjs +2 -0
  16. package/src/lib/evaluate.mjs +124 -38
  17. package/src/lib/findings.mjs +98 -0
  18. package/src/lib/health-core.mjs +28 -5
  19. package/src/lib/health-findings.mjs +116 -0
  20. package/src/lib/health-index.mjs +202 -0
  21. package/src/lib/health.mjs +4 -0
  22. package/src/lib/integrations-cli.mjs +6 -4
  23. package/src/lib/jev.mjs +34 -14
  24. package/src/lib/linkcheck.mjs +59 -22
  25. package/src/lib/local-evaluator.mjs +270 -0
  26. package/src/lib/manifest-core.mjs +115 -14
  27. package/src/lib/pack-help.mjs +26 -0
  28. package/src/lib/packs.mjs +41 -3
  29. package/src/lib/pii.mjs +2 -0
  30. package/src/lib/pr.mjs +5 -1
  31. package/src/lib/probe.mjs +62 -1
  32. package/src/lib/recheck.mjs +5 -1
  33. package/src/lib/scenario.mjs +16 -3
  34. package/src/lib/seo-core.mjs +400 -0
  35. package/src/lib/seo-init.mjs +272 -0
  36. package/src/lib/service.mjs +31 -2
  37. package/src/lib/site.mjs +362 -0
  38. package/src/lib/stack-core.mjs +244 -0
  39. package/src/lib/stack.mjs +291 -51
  40. package/src/lib/traffic-core.mjs +289 -0
  41. package/src/lib/traffic.mjs +149 -0
  42. package/src/lib/upload.mjs +3 -2
  43. package/src/lib/variants-core.mjs +198 -0
  44. package/viewer/index.html +49 -24
@@ -17,6 +17,18 @@ sorting, thumbnails and CSV export, and each page opens in the lightbox.
17
17
  per page, and export the failing ones for the content team.
18
18
  - A redesign or migration: walk the old URL list against the new site.
19
19
 
20
+ For a weekly SEO and answer-engine monitor of a public site, start with
21
+ `npx kaloko init --seo <site>`: it reads robots.txt and the sitemaps, finds
22
+ the languages and the biggest content families and writes
23
+ `qa/flows/seo-<site>.yml` with one rotating stack per family (`match` per
24
+ language, packs seo and aeo). Read the comment at the top of the file:
25
+ families it paired across languages by size are guesses to check. Then
26
+ `kaloko validate`, `kaloko walk --limit 20`, and
27
+ `kaloko schedule export github`. See help/how-to/seo-monitor.md. On
28
+ Business, `kaloko schedule add` runs urls, sitemap and robots stacks on
29
+ Kaloko's side without screenshots (DOM checks and index fields only; see
30
+ reference/integrations.md → Hosted runs).
31
+
20
32
  Not for a flow of different screens: those stay separate steps. A stack
21
33
  step does not click through anything. It opens each page by URL. Use a
22
34
  step script only to prepare each page, for example to dismiss a dialog
@@ -31,10 +43,14 @@ step script only to prepare each page, for example to dismiss a dialog
31
43
  purpose: Every article reads well and is findable.
32
44
  packs: [seo] # health checks of every page (advisory)
33
45
  items:
34
- sitemap: /sitemap.xml # or urls / file / crawl / script (exactly one)
46
+ sitemap: /sitemap.xml # or robots: true / urls / file / crawl / script (exactly one)
35
47
  match: '^/clanky/' # regex on the path with its query; exclude: '…' drops pages
48
+ # or a map by locale: { default: '^/blog/', fi: '^/fi/blogi/' }
36
49
  limit: 800 # default 500, at most 10000
37
- semantic_sample: 20 # semantic criteria on the first 20 pages only
50
+ # rotate: week # each ISO week the next `limit` pages (run: each run)
51
+ # variants: hreflang # translations as variants of one page (see below)
52
+ semantic_sample: 20 # semantic criteria on 20 pages only
53
+ # semantic_order: priority # failing and least recently checked pages first
38
54
  # viewports: [desktop] # default: the first viewport of the scenario
39
55
  fields: # index columns (max 12); default title, h1, words, http
40
56
  - { id: title, label: Title, from: title }
@@ -55,12 +71,43 @@ Item sources:
55
71
  - `file: qa/data/products.csv` is a list next to the scenario. Accepted
56
72
  formats are text with one path per line, CSV (first column), or JSON:
57
73
  `["/a"]` or `[{ path, title, key, purpose }]`, where `key` gives a stable id.
58
- - `sitemap: /sitemap.xml` also follows a sitemap index (up to 50 sitemaps).
74
+ - `sitemap: /sitemap.xml` also follows a sitemap index, two levels deep
75
+ (up to 50 sitemaps each). Gzipped sitemaps work: a `.gz` URL or gzip
76
+ bytes behind any URL are unpacked.
77
+ - `sitemap: robots` (or `robots: true`) reads the `Sitemap:` lines of
78
+ `/robots.txt` on the environment's origin. When their URLs name the
79
+ walked locale as a segment (`/sitemaps/de/sitemap.xml.gz`,
80
+ `sitemap-de.xml`; `en_US` finds `en-us`, `en` does not), only that
81
+ locale's sitemaps are read; otherwise all of them. Use it when a site
82
+ lists one sitemap per language, so the scenario does not hard-code paths.
59
83
  - `crawl: { from: /clanky, links: "a.card", next: "a.next", pages: 20 }`
60
84
  collects links from a listing and follows its pagination.
61
85
  - `script: qa/steps/items.mjs` uses the default export
62
- `({ page, env, locale, url }) => [paths | { path, title, key, purpose }]` for
63
- anything else, such as an API or a search.
86
+ `({ page, env, locale, url, node, params }) => [paths | { path, title, key, purpose }]`
87
+ for anything else, such as an API or a search. `node` is
88
+ `{ id, title, lane, lane_title, session }` of the step and `params` is
89
+ `items.params`, so one script can serve several stacks.
90
+
91
+ Fix handoff: a script or a JSON list may give each page `edit_url` (the
92
+ page in the CMS, http(s) only) and `content_ref` (its record id for a
93
+ content API). `resolve: steps/resolve.mjs` (default export
94
+ `async (item, { env, locale, node, params, url }) => ({ edit_url, content_ref })`)
95
+ looks them up for the walked pages only, 15 s per page; a page it fails on is walked
96
+ without them and the walk says how many. They show in the page detail
97
+ ("Edit in CMS"), the stack CSV, `get_stack_items` and
98
+ `kaloko export findings`.
99
+
100
+ `match` and `exclude` apply to every source before `limit` (and before
101
+ `rotate`). Either is a regex or a map by locale; the walked locale, then its
102
+ language (`en` for `en_US`), then `default` is used. A `match` map without
103
+ the locale and without `default` is an error.
104
+
105
+ `rotate: week` (or `rotate: run`) walks a big list in turns: each ISO week
106
+ (or each run) takes the next `limit` pages, wrapping around, so the list is
107
+ covered in `pages ÷ limit` rounds. `run` keeps its count and each page's last
108
+ check in `.cache/stack/` of the output folder, so use `week` on CI with a
109
+ fresh checkout. The stack index gets `rotation` per locale (window, covered,
110
+ never checked) and each page `last_checked` (its previous check, or null).
64
111
 
65
112
  An item's `purpose` (for example its title and meta description) is what the
66
113
  semantic evaluator reads as `screen_purpose` for that page instead of the
@@ -73,32 +120,144 @@ in the wrong language.
73
120
  Pages are deduplicated. External URLs are allowed (e.g. an old domain).
74
121
  `{locale}` in any source string is replaced by the walked locale. This is
75
122
  how one sitemap with every language is split:
76
- `match: '^/guides/.*lang={locale}'`, or `sitemap: /{locale}/sitemap.xml`.
123
+ `match: '^/guides/.*lang={locale}'`, or `sitemap: /{locale}/sitemap.xml`,
124
+ or `sitemap: /sitemaps/{locale}/sitemap.xml.gz`. One content family per
125
+ stack: the same per-locale sitemap with `match: '/eshop/'` in one step and
126
+ `match: '/(magazin|magazine|revista)/'` in another.
77
127
 
78
128
  Field sources: `title`, `h1`, `description`, `canonical`, `lang`, `url`,
79
129
  `path`, `words` (words in main/article/body), `http` (the navigation
80
- status), `count:<selector>`, `<selector>` (text) and `<selector>@<attr>`.
130
+ status), `jsonld_types` (the schema.org types of the page's JSON-LD, for
131
+ example `Product, Offer, BreadcrumbList`; `invalid×1` when a block does not
132
+ parse), a meta tag by name (`og:title`, `og:image`, `twitter:card`,
133
+ `meta:robots`), `count:<selector>`, `<selector>` (text) and
134
+ `<selector>@<attr>`.
135
+
136
+ Structured data is also summarised without a field: every capture records
137
+ `structured_data` (blocks, invalid blocks, types), and `items.json` carries
138
+ `structured_data` per page and, at the top, pages per type, pages without
139
+ structured data and pages with invalid JSON-LD. The `seo` pack checks it
140
+ (HC112 present, HC113 valid); `packs: [aeo]` adds the answer-engine signals
141
+ (HC901–HC905; HC905 `nojs_parity` fetches each page's raw HTML as GPTBot,
142
+ respecting robots.txt, and fails when the title, canonical, H1, JSON-LD or
143
+ most of the text appear only after JavaScript). See
144
+ help/reference/checks-and-packs.md.
145
+
146
+ With the `seo` pack a stack also compares its pages with each other after the
147
+ walk (HC121–HC126, advisory): shared titles, descriptions and H1s within a
148
+ language, canonicals pointing elsewhere or to error/noindex pages, hreflang
149
+ that the other walked version does not answer, and (sitemap or robots
150
+ sources) listed pages that answer non-200, redirect or say noindex. They are
151
+ health entries of the stack node (`cross_page: true`, `counts` in pages,
152
+ `urls`); `items.json` has them under `cross_page` with the groups, and each
153
+ affected page lists the ids in `cross_fails`. They cannot be adopted.
154
+ `packs: [locale]` (scenarios with `locales`) checks html lang, the own
155
+ hreflang, the text's language and price decimals against the walked locale
156
+ (HC1001–HC1004). `health: { site: true }` on the scenario adds site checks
157
+ once per run (robots.txt, sitemaps, llms.txt, http→https and www redirects;
158
+ HC1101–HC1107, `site.json` in the run, `site` in the manifest). Off the
159
+ `production` environment HC1102 is a warning and HC1103 is skipped;
160
+ `health.site.environments: [production]` runs them there only.
161
+
162
+ Traffic data (#157) is opt-in: `traffic:` in kaloko.config.yml or the
163
+ scenario, with `file` (a Search Console or GA4 CSV export, English or Czech
164
+ headers), `gsc: { property }` and `ga4: { property }` (APIs through a service
165
+ account key named by `credentials_env`, default
166
+ GOOGLE_APPLICATION_CREDENTIALS; nothing is requested without them; answers
167
+ cached per run in <output>/.cache/traffic). Each page in `items.json` then has
168
+ `traffic: { clicks, impressions, ctr, position, sessions, conversions }`, the
169
+ node `stack.traffic` counts the pages with data, `kaloko export findings`
170
+ adds the numbers and `impact` (weight 3 for errors and failed acceptance, 1
171
+ for warnings, × impressions + 10 × (clicks + sessions) + 100 × conversions)
172
+ and sorts by it, and the `seo` pack adds HC127 (CTR below half the site's
173
+ median at similar positions, pages with 100+ impressions). Sort a fix list
174
+ by impact before handing it over; never put the key in the scenario.
81
175
 
82
176
  A step `execution.script` still works. It is called with `item` in the
83
177
  context, opens `item.url` itself and may call `capture()`. Without a script,
84
178
  the walk opens each page and captures it.
85
179
 
180
+ ## One page in every language (`variants: hreflang`)
181
+
182
+ One item is one page; its locales are variants of it. Without more setup,
183
+ each locale reads its own list, so a Czech product and its Slovak
184
+ translation become two items with different ids, the locales sample
185
+ different pages and a missing translation is invisible. For a multilingual
186
+ stack, set:
187
+
188
+ ```yaml
189
+ items:
190
+ robots: true
191
+ match: '^/eshop/'
192
+ variants: hreflang
193
+ source_locale: cs # default: the scenario's first locale
194
+ limit: 100
195
+ rotate: week
196
+ ```
197
+
198
+ - The list, `match`/`exclude`, `limit` and `rotate` are taken in
199
+ `source_locale` (its base URL, `{locale}` = the source locale) for every
200
+ locale, so all locales walk the same window. Item ids come from the
201
+ source pages; `--items` takes an id or a source path.
202
+ - In another locale each chosen page's alternate for that locale is walked:
203
+ `<link rel="alternate" hreflang>` in the raw HTML head (no JavaScript),
204
+ else the `Link` header, else the sitemap's `xhtml:link`. Matching: the
205
+ same tag (`en-us` for `en_US`), else the bare language for a locale with a
206
+ region, else the first regional tag for a bare locale (`sk-sk` for `sk`).
207
+ `x-default` never stands for a locale. The page is read with the
208
+ environment's gate (headers, HTTP Basic, client certificates) and the
209
+ walk's cookies, on the environment's own origins only; 15 s and 1 MB at
210
+ most, stopping at `</head>`; a timeout or 5xx is tried once more. When no
211
+ alternate is on the host the page was read from (staging naming
212
+ production, also one domain per language), every alternate opens with its
213
+ path on the walked locale's origin.
214
+ - A page with no alternate for the locale, only `x-default`, an alternate
215
+ that points back to the page itself (another language), or a page that
216
+ cannot be read is not walked in that locale and is reported: the walk
217
+ says how many, `steps/<step>/variants.<locale>.json` keeps the list,
218
+ `items.json` marks the item (`missing: [sk]`) and lists every page under
219
+ `variants.locales`, the node gets `stack.variants`, HC1005 counts them
220
+ (advisory, also without the `seo` pack), and `kaloko export findings`
221
+ gives one row per page and locale at the source URL. Several source pages
222
+ leading to one alternate (a fallback to a listing or home) also count in
223
+ HC1005; they are walked. A translation that answers 4xx/5xx is walked
224
+ and also counts as missing (`error 404`). Reciprocity is HC125 (`seo` pack).
225
+ - The source page is read once per walk, whichever locale needs it. Each
226
+ capture of a variant keeps its own `path` in `items.json`; the item's
227
+ `path` and id are the source page's, and its `url`, title and fields too
228
+ when the run walks the source locale (else those of the first walked
229
+ locale).
230
+ - A script that lists pages per locale instead: give each page the same
231
+ `key` in every locale (for example the source path), so they are one item.
232
+ - Hosted runs (`kaloko schedule add`) do not group variants yet: there each
233
+ locale reads its own list and HC1005 is not reported (`kaloko validate`
234
+ warns). Use `kaloko walk` or the scheduled GitHub workflow.
235
+
86
236
  ## Cost and size: choose deliberately
87
237
 
88
238
  - Deterministic criteria and packs run on every page. They are cheap.
89
- - Semantic criteria go to the evaluator. On a stack they run only on the
90
- first `semantic_sample` pages (default 20). The other pages show the
91
- criterion as not evaluated. Raise the sample only when the question
239
+ - Semantic criteria go to the evaluator. On a stack they run only on
240
+ `semantic_sample` pages (default 20), the first in walk order. With
241
+ `semantic_order: priority`, or an evaluator budget, pages failing a
242
+ deterministic acceptance criterion come first, then those never or least
243
+ recently checked. The other pages show the criterion as not evaluated. `evaluate.budget.calls`
244
+ caps the calls of a whole evaluation (evaluation.md). Raise the sample only when the question
92
245
  really differs between pages, and tell the person what it costs
93
246
  (one call per page and viewport).
94
247
  - Stacks capture the first screen of each page (fast, small). Set
95
248
  `capture: { full_page: true }` when the criteria are about the whole page.
96
249
  - Walking takes about 1–2 s per page. For a first look, use
97
250
  `kaloko walk --limit 50`. Re-walk chosen pages with
98
- `--items clanek-7,clanek-12` (matches the id or the path).
99
- - A shared run holds at most 20000 files and 300 MB. Each page stores about
100
- four files, so a stack of a few thousand first-screen pages fits. Split
101
- larger catalogues by section (`match`), one stack per section.
251
+ `--items clanek-7,clanek-12` (matches the id or the path; picked from
252
+ every matching page, also outside this week's rotation, and such a
253
+ partial walk leaves the rotation state alone).
254
+ - A shared run holds at most 20000 files and 300 MB as stored (HTML and JSON
255
+ travel gzip-compressed, about a tenth of their size on disk). Each page
256
+ stores about four files, so a stack of a few thousand first-screen pages
257
+ fits. For SEO crawls where the checks read the page, not the picture, set
258
+ `capture: { screenshot: thumb }`: only the 640 px thumbnail is kept. Split
259
+ larger catalogues by section (`match`) or walk a rotating sample
260
+ (`rotate: week`).
102
261
 
103
262
  ## Reading the results
104
263
 
@@ -118,13 +277,19 @@ the walk opens each page and captures it.
118
277
  `get_stack_items {run_id, step, status?, criterion?, query?, offset, limit}`
119
278
  lists pages with fields, failing criteria, evidence and screenshot URLs.
120
279
  This is the way to hand an agent "fix these 37 articles".
280
+ - `kaloko export findings [--format csv|json] [--locale l] [--step s] [--out file]`
281
+ writes the fix list of the local run: one row per failing criterion or
282
+ health check, page and locale, with URL, evidence, `edit_url`,
283
+ `content_ref` and `recheck` (`kaloko walk --steps s --items <page> --locales l`,
284
+ which re-walks just that page after the fix).
121
285
 
122
286
  ## Report
123
287
 
124
288
  Say how many pages were walked, how many fail and on which criteria (with
125
289
  two or three example paths), whether the limit cut the list, and that
126
- semantic criteria covered only the sample. Put the full list in the CSV or
127
- the MCP result rather than in the issue.
290
+ semantic criteria covered only the sample. With `variants: hreflang`, also
291
+ say per locale how many source pages have no version there (HC1005). Put the
292
+ full list in the CSV or the MCP result rather than in the issue.
128
293
 
129
294
  ## Design environments
130
295