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.
- package/package.json +1 -1
- package/schemas/config.v1.json +47 -1
- package/schemas/run.v1.json +57 -1
- package/schemas/scenario.v1.json +100 -14
- package/skills/kaloko/reference/acceptance.md +18 -4
- package/skills/kaloko/reference/evaluation.md +44 -1
- package/skills/kaloko/reference/integrations.md +30 -1
- package/skills/kaloko/reference/mcp.md +3 -0
- package/skills/kaloko/reference/stacks.md +181 -16
- package/src/cli.mjs +191 -37
- package/src/lib/build.mjs +37 -7
- package/src/lib/capture.mjs +31 -9
- package/src/lib/config.mjs +15 -1
- package/src/lib/crawler.mjs +242 -0
- package/src/lib/draft.mjs +2 -0
- package/src/lib/evaluate.mjs +124 -38
- package/src/lib/findings.mjs +98 -0
- package/src/lib/health-core.mjs +28 -5
- package/src/lib/health-findings.mjs +116 -0
- package/src/lib/health-index.mjs +202 -0
- package/src/lib/health.mjs +4 -0
- package/src/lib/integrations-cli.mjs +6 -4
- package/src/lib/jev.mjs +34 -14
- package/src/lib/linkcheck.mjs +59 -22
- package/src/lib/local-evaluator.mjs +270 -0
- package/src/lib/manifest-core.mjs +115 -14
- package/src/lib/pack-help.mjs +26 -0
- package/src/lib/packs.mjs +41 -3
- package/src/lib/pii.mjs +2 -0
- package/src/lib/pr.mjs +5 -1
- package/src/lib/probe.mjs +62 -1
- package/src/lib/recheck.mjs +5 -1
- package/src/lib/scenario.mjs +16 -3
- package/src/lib/seo-core.mjs +400 -0
- package/src/lib/seo-init.mjs +272 -0
- package/src/lib/service.mjs +31 -2
- package/src/lib/site.mjs +362 -0
- package/src/lib/stack-core.mjs +244 -0
- package/src/lib/stack.mjs +291 -51
- package/src/lib/traffic-core.mjs +289 -0
- package/src/lib/traffic.mjs +149 -0
- package/src/lib/upload.mjs +3 -2
- package/src/lib/variants-core.mjs +198 -0
- 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
|
-
|
|
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
|
|
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 }]`
|
|
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), `
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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.
|
|
127
|
-
|
|
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
|
|