kaloko 0.18.0 → 0.19.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 +36 -1
- package/schemas/run.v1.json +47 -1
- package/schemas/scenario.v1.json +81 -14
- package/skills/kaloko/reference/acceptance.md +18 -4
- package/skills/kaloko/reference/evaluation.md +44 -1
- package/skills/kaloko/reference/integrations.md +6 -0
- package/skills/kaloko/reference/mcp.md +2 -0
- package/skills/kaloko/reference/stacks.md +104 -14
- package/src/cli.mjs +157 -33
- package/src/lib/build.mjs +18 -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 +73 -0
- package/src/lib/health-core.mjs +28 -5
- package/src/lib/health-index.mjs +187 -0
- package/src/lib/health.mjs +4 -0
- 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 +38 -12
- package/src/lib/pack-help.mjs +24 -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 +11 -3
- package/src/lib/seo-core.mjs +372 -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.mjs +230 -23
- package/src/lib/upload.mjs +3 -2
- package/viewer/index.html +31 -17
package/package.json
CHANGED
package/schemas/config.v1.json
CHANGED
|
@@ -33,10 +33,28 @@
|
|
|
33
33
|
"minProperties": 1,
|
|
34
34
|
"additionalProperties": { "$ref": "#/$defs/environment" }
|
|
35
35
|
},
|
|
36
|
-
"
|
|
36
|
+
"evaluate": {
|
|
37
37
|
"type": "object",
|
|
38
38
|
"additionalProperties": false,
|
|
39
|
+
"description": "Settings of every evaluation (kaloko evaluate and the end of kaloko walk).",
|
|
40
|
+
"properties": {
|
|
41
|
+
"budget": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"description": "Upper bound of one evaluation (#158). Steps go first, then the semantic sample of each stack, chosen by priority (pages failing a deterministic acceptance check, then those checked longest ago) unless the step says semantic_order: walk; what is past the budget stays not_evaluated with a note.",
|
|
45
|
+
"properties": {
|
|
46
|
+
"calls": { "type": "integer", "minimum": 0, "description": "Uncached requests to JEV (your key or through Kaloko) per evaluation, escalations included; requests to your own openai_compatible evaluator are not counted. With a budget, screens without semantic criteria get no quick signals unless already cached. kaloko evaluate --budget N overrides it." }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"evaluators": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "Text evaluators (jev, and your own of kind openai_compatible under any other name), the vision evaluator and triage.",
|
|
54
|
+
"additionalProperties": { "$ref": "#/$defs/openaiEvaluator" },
|
|
39
55
|
"properties": {
|
|
56
|
+
"text": { "type": "string", "default": "jev", "description": "The evaluator that answers the semantic questions first: jev, or the name of an openai_compatible evaluator here (a local model in Ollama or LM Studio). Its verdicts are marked via: own-model on the canvas, in the PR comment and in acceptance records." },
|
|
57
|
+
"escalate_to": { "const": "jev", "description": "Answers of your own evaluator (evaluators.text) that are not clear enough for a verdict (between the fail and pass thresholds, or missing) are asked again of JEV (your key or through Kaloko, counted in evaluate.budget). Only your model escalates to JEV, never the other way. Quick signals are not escalated." },
|
|
40
58
|
"jev": {
|
|
41
59
|
"type": "object",
|
|
42
60
|
"additionalProperties": false,
|
|
@@ -201,6 +219,23 @@
|
|
|
201
219
|
"images": { "type": "object", "additionalProperties": false, "properties": { "viewport": { "type": "string" }, "locale": { "type": "string", "description": "reader (the reader's language) or a locale." } } }
|
|
202
220
|
}
|
|
203
221
|
},
|
|
222
|
+
"openaiEvaluator": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"additionalProperties": false,
|
|
225
|
+
"required": ["kind", "base_url", "model"],
|
|
226
|
+
"description": "Your own text evaluator behind an OpenAI-compatible chat completions API (#158): Ollama (http://localhost:11434/v1), LM Studio (http://localhost:1234/v1), vLLM or a gateway. Used only when evaluators.text or evaluators.escalate_to names it; the page state goes only to base_url.",
|
|
227
|
+
"properties": {
|
|
228
|
+
"kind": { "const": "openai_compatible" },
|
|
229
|
+
"base_url": { "type": "string", "description": "The API root with /v1; requests go to {base_url}/chat/completions." },
|
|
230
|
+
"model": { "type": "string", "description": "The model as the server names it (qwen3:8b, gemma3:12b)." },
|
|
231
|
+
"api_key_env": { "type": "string", "description": "Variable with a key sent as a bearer token; a local server needs none." },
|
|
232
|
+
"concurrency": { "type": "integer", "minimum": 1, "maximum": 16, "default": 2, "description": "Requests in flight at once; a local model usually answers one at a time." },
|
|
233
|
+
"max_state_chars": { "type": "integer", "minimum": 1000, "default": 24000, "description": "The page text is cut to this length (the top of the page): small models have small context windows." },
|
|
234
|
+
"timeout_ms": { "type": "integer", "minimum": 1000, "default": 120000 },
|
|
235
|
+
"temperature": { "type": "number", "minimum": 0, "maximum": 2, "default": 0 },
|
|
236
|
+
"response_format": { "enum": ["json_schema", "json_object", "none"], "default": "json_schema", "description": "How the answer is held to JSON; json_object or none for servers without structured output." }
|
|
237
|
+
}
|
|
238
|
+
},
|
|
204
239
|
"record": {
|
|
205
240
|
"description": "Recordings of walked steps (opt-in): true or step records each step from the previous screen to its capture; flow records the whole walk per locale with a chapter per step; failed keeps only steps that fail; false or off turns it off. Uploaded on Business and Enterprise.",
|
|
206
241
|
"oneOf": [
|
package/schemas/run.v1.json
CHANGED
|
@@ -441,6 +441,40 @@
|
|
|
441
441
|
"null"
|
|
442
442
|
],
|
|
443
443
|
"description": "Version of the application under test (git tag on HEAD or --app-version); documentation captures carry it."
|
|
444
|
+
},
|
|
445
|
+
"site": {
|
|
446
|
+
"type": "object",
|
|
447
|
+
"description": "Site checks (#152, scenario health.site), once per site and run: robots.txt, sitemaps, llms.txt, http→https and www redirects. Advisory: health shows a finding per failed check (pack site, step site:<host>); they never decide a verdict.",
|
|
448
|
+
"properties": {
|
|
449
|
+
"origins": {
|
|
450
|
+
"type": "array",
|
|
451
|
+
"items": {
|
|
452
|
+
"type": "object",
|
|
453
|
+
"required": ["origin", "checks"],
|
|
454
|
+
"properties": {
|
|
455
|
+
"origin": { "type": "string" },
|
|
456
|
+
"checked_at": { "type": "string" },
|
|
457
|
+
"checks": {
|
|
458
|
+
"type": "array",
|
|
459
|
+
"items": {
|
|
460
|
+
"type": "object",
|
|
461
|
+
"required": ["id", "verdict"],
|
|
462
|
+
"properties": {
|
|
463
|
+
"id": { "type": "string", "description": "HC1101 … HC1107." },
|
|
464
|
+
"text": { "type": "string" },
|
|
465
|
+
"verdict": { "enum": ["pass", "fail", "not_evaluated"] },
|
|
466
|
+
"level": { "enum": ["error", "warning"] },
|
|
467
|
+
"evidence": { "type": ["string", "null"] },
|
|
468
|
+
"na": { "type": "boolean", "description": "Nothing to check here (no robots.txt to read, no other host name)." },
|
|
469
|
+
"details": { "type": "array", "items": { "type": "string" } },
|
|
470
|
+
"help": { "type": "object" }
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
444
478
|
}
|
|
445
479
|
},
|
|
446
480
|
"$defs": {
|
|
@@ -665,7 +699,9 @@
|
|
|
665
699
|
"counts": { "type": "object", "description": "Stack steps: pages per verdict." },
|
|
666
700
|
"evidence": { "type": ["string", "null"] },
|
|
667
701
|
"check": { "type": "object" },
|
|
668
|
-
"help": { "type": "object" }
|
|
702
|
+
"help": { "type": "object" },
|
|
703
|
+
"cross_page": { "type": "boolean", "description": "Stack steps (#151): a check across the stack's pages (HC121–HC126: duplicates, canonicals, hreflang pairs, sitemap pages). counts says how many pages are affected; it cannot be adopted as a criterion." },
|
|
704
|
+
"urls": { "type": "array", "items": { "type": "string" }, "description": "Cross-page checks: the first affected addresses (at most 20); the stack's items.json has every group." }
|
|
669
705
|
}
|
|
670
706
|
}
|
|
671
707
|
},
|
|
@@ -724,6 +760,16 @@
|
|
|
724
760
|
"string",
|
|
725
761
|
"null"
|
|
726
762
|
]
|
|
763
|
+
},
|
|
764
|
+
"rotation": {
|
|
765
|
+
"type": "object",
|
|
766
|
+
"description": "Rotating sample (items.rotate), per locale: mode (week or run), week or run, offset, limit, total, rounds (runs to cover every page), covered, never (pages not checked yet) and oldest_checked.",
|
|
767
|
+
"additionalProperties": { "type": "object" }
|
|
768
|
+
},
|
|
769
|
+
"cross_page": {
|
|
770
|
+
"type": "array",
|
|
771
|
+
"description": "Cross-page checks of a stack with the seo pack (#151): id, verdict, pages compared and pages affected. items.json has them in full under cross_page (groups with the affected addresses) and marks each affected page with cross_fails.",
|
|
772
|
+
"items": { "type": "object" }
|
|
727
773
|
}
|
|
728
774
|
}
|
|
729
775
|
}
|
package/schemas/scenario.v1.json
CHANGED
|
@@ -94,7 +94,23 @@
|
|
|
94
94
|
"additionalProperties": false,
|
|
95
95
|
"description": "Health (CS Kondice): the checks of packs are advisory, shown apart from acceptance and never part of a verdict. acceptance lists the packs whose checks still count for acceptance in this scenario, as they did before health was split off (the design pack always does). To make one check count, adopt it: kaloko health adopt <step>:<check> (e.g. home:HC103) adds the criterion AC103 with from: HC103.",
|
|
96
96
|
"properties": {
|
|
97
|
-
"acceptance": { "type": "array", "uniqueItems": true, "items": { "enum": ["seo", "usability", "a11y", "perf", "console", "errors", "app", "design"] } }
|
|
97
|
+
"acceptance": { "type": "array", "uniqueItems": true, "items": { "enum": ["seo", "usability", "a11y", "perf", "console", "errors", "app", "design", "aeo", "locale"] } },
|
|
98
|
+
"site": {
|
|
99
|
+
"description": "Site checks (#152), once per site and run: robots.txt, the sitemaps it lists, llms.txt, and http→https and www redirects in one hop. Advisory (health, pack site, HC11xx); off unless set, because they make requests of their own. true for the defaults, or an object.",
|
|
100
|
+
"oneOf": [
|
|
101
|
+
{ "type": "boolean" },
|
|
102
|
+
{
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"paths": { "type": "array", "maxItems": 50, "items": { "type": "string", "pattern": "^/" }, "description": "Paths robots.txt must not block for search engines (default: / and the start path of each walked language)." },
|
|
107
|
+
"ai_crawlers": { "type": "boolean", "description": "false skips HC1103 (AI crawlers blocked in robots.txt) when blocking them is the site's decision. Default true." },
|
|
108
|
+
"sitemaps": { "type": "integer", "minimum": 1, "maximum": 50, "description": "Most sitemap files to fetch per site, index children included (default and most 50)." },
|
|
109
|
+
"environments": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Run the site checks only on these environments (e.g. [production]). Default: every environment; off production HC1102 is a warning and HC1103 is not evaluated." }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
98
114
|
}
|
|
99
115
|
},
|
|
100
116
|
"tags": {
|
|
@@ -344,6 +360,10 @@
|
|
|
344
360
|
"items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,39}$", "not": { "enum": ["index"] } },
|
|
345
361
|
"description": "States this step is captured in besides the default one, e.g. [empty, error, loading]; any name works. Each state is one more capture per viewport (keys <viewport>.state-<name>) and a value of the State switcher on the canvas. Design steps render <step>/<state>.html (or <step>/<locale>/<state>.html), else <step>/index.html?state=<name>; walks of web and app steps run the step script once more per state with the argument state. `default` may be listed; it is always captured."
|
|
346
362
|
},
|
|
363
|
+
"expect_status": {
|
|
364
|
+
"oneOf": [{ "type": "integer", "minimum": 100, "maximum": 599 }, { "type": "array", "minItems": 1, "items": { "type": "integer", "minimum": 100, "maximum": 599 } }],
|
|
365
|
+
"description": "The HTTP status this step is expected to answer with, e.g. 404 for a missing page. A status of 400 or more marks an error page: the errors pack checks HC601 and HC605 only on error pages (this, or a 4xx/5xx answer)."
|
|
366
|
+
},
|
|
347
367
|
"page": {
|
|
348
368
|
"enum": ["landing", "work", "list", "settings", "form", "doc"],
|
|
349
369
|
"description": "What kind of page this step is (see the scenario's page); the usability pack asks per type, e.g. one primary call to action only on landing pages."
|
|
@@ -359,10 +379,12 @@
|
|
|
359
379
|
"console",
|
|
360
380
|
"errors",
|
|
361
381
|
"app",
|
|
362
|
-
"design"
|
|
382
|
+
"design",
|
|
383
|
+
"aeo",
|
|
384
|
+
"locale"
|
|
363
385
|
]
|
|
364
386
|
},
|
|
365
|
-
"description": "Check packs whose checks are added to this node. Health checks (advisory): HC1xx seo, HC2xx usability, HC3xx a11y, HC4xx perf, HC5xx console, HC6xx errors, HC7xx app. AC8xx design: checks against the design reference, always acceptance. A pack listed in health.acceptance adds its checks as acceptance criteria with the same number (HC302 → AC302)."
|
|
387
|
+
"description": "Check packs whose checks are added to this node. Health checks (advisory): HC1xx seo, HC2xx usability, HC3xx a11y, HC4xx perf, HC5xx console, HC6xx errors, HC7xx app, HC9xx aeo, HC10xx locale. AC8xx design: checks against the design reference, always acceptance. A pack listed in health.acceptance adds its checks as acceptance criteria with the same number (HC302 → AC302)."
|
|
366
388
|
},
|
|
367
389
|
"capture": {
|
|
368
390
|
"$ref": "#/$defs/capture"
|
|
@@ -394,7 +416,7 @@
|
|
|
394
416
|
"items": {
|
|
395
417
|
"type": "object",
|
|
396
418
|
"additionalProperties": false,
|
|
397
|
-
"description": "Stack steps: which pages of one template the step covers. One source: urls, file, sitemap (with match/exclude), crawl or script.",
|
|
419
|
+
"description": "Stack steps: which pages of one template the step covers. One source: urls, file, sitemap or robots (with match/exclude), crawl or script.",
|
|
398
420
|
"properties": {
|
|
399
421
|
"urls": {
|
|
400
422
|
"type": "array",
|
|
@@ -405,19 +427,23 @@
|
|
|
405
427
|
},
|
|
406
428
|
"file": {
|
|
407
429
|
"type": "string",
|
|
408
|
-
"description": "Text (one path or URL per line), CSV (first column) or JSON array of paths/URLs or {path,title}; relative to the scenario"
|
|
430
|
+
"description": "Text (one path or URL per line), CSV (first column) or JSON array of paths/URLs or {path, title, key, purpose, edit_url, content_ref}; relative to the scenario"
|
|
409
431
|
},
|
|
410
432
|
"sitemap": {
|
|
411
433
|
"type": "string",
|
|
412
|
-
"description": "Sitemap path or URL (sitemap indexes are followed
|
|
434
|
+
"description": "Sitemap path or URL, gzipped (.gz) or not; sitemap indexes are followed two levels. `robots` reads the Sitemap: lines of /robots.txt (see robots)."
|
|
435
|
+
},
|
|
436
|
+
"robots": {
|
|
437
|
+
"type": "boolean",
|
|
438
|
+
"description": "The sitemaps listed in /robots.txt (Sitemap: lines); when their URLs name the walked locale (/sitemaps/de/…), only those of that locale"
|
|
413
439
|
},
|
|
414
440
|
"match": {
|
|
415
|
-
"type": "string",
|
|
416
|
-
"description": "Regex on the path: keep matching pages"
|
|
441
|
+
"oneOf": [{ "type": "string" }, { "type": "object", "minProperties": 1, "additionalProperties": { "type": "string" } }],
|
|
442
|
+
"description": "Regex on the path: keep matching pages. A map by locale ({ default: '^/eshop/', fi: '^/fi/tuotteet/' }) gives each locale its own regex (the locale, its language, then default). Applies to every source before rotate and limit."
|
|
417
443
|
},
|
|
418
444
|
"exclude": {
|
|
419
|
-
"type": "string",
|
|
420
|
-
"description": "Regex on the path: drop matching pages"
|
|
445
|
+
"oneOf": [{ "type": "string" }, { "type": "object", "minProperties": 1, "additionalProperties": { "type": "string" } }],
|
|
446
|
+
"description": "Regex on the path: drop matching pages. A string or a map by locale like match."
|
|
421
447
|
},
|
|
422
448
|
"crawl": {
|
|
423
449
|
"type": "object",
|
|
@@ -449,7 +475,15 @@
|
|
|
449
475
|
},
|
|
450
476
|
"script": {
|
|
451
477
|
"type": "string",
|
|
452
|
-
"description": "Module relative to the scenario: default export async ({ page, env, locale, url }) => [{ path | url, title?, key? }]"
|
|
478
|
+
"description": "Module relative to the scenario: default export async ({ page, env, locale, url, node, params }) => [{ path | url, title?, key?, purpose?, edit_url?, content_ref? }]. node: { id, title, lane, lane_title, session }; params: items.params. edit_url (the page in the CMS) and content_ref (its record id) show in the stack index and in kaloko export findings."
|
|
479
|
+
},
|
|
480
|
+
"resolve": {
|
|
481
|
+
"type": "string",
|
|
482
|
+
"description": "Module relative to the scenario that links each walked page to its source: default export async (item, { env, locale, node, params, url }) => ({ edit_url?, content_ref? }). Called for the walked pages only (after match, rotate, limit and walk --items), 15 s per page; a page it fails on is walked without the link."
|
|
483
|
+
},
|
|
484
|
+
"params": {
|
|
485
|
+
"type": "object",
|
|
486
|
+
"description": "Values passed to items.script as params, so one script serves several stacks (for example { family: magazine })."
|
|
453
487
|
},
|
|
454
488
|
"limit": {
|
|
455
489
|
"type": "integer",
|
|
@@ -457,11 +491,19 @@
|
|
|
457
491
|
"maximum": 10000,
|
|
458
492
|
"description": "At most this many pages (default 500)"
|
|
459
493
|
},
|
|
494
|
+
"rotate": {
|
|
495
|
+
"enum": ["week", "run"],
|
|
496
|
+
"description": "Rotating sample: each run takes limit pages starting at a stable offset, the ISO week (week) or the number of runs so far in this output folder (run), so the whole list is covered over several runs."
|
|
497
|
+
},
|
|
460
498
|
"semantic_sample": {
|
|
461
499
|
"type": "integer",
|
|
462
500
|
"minimum": 0,
|
|
463
501
|
"maximum": 10000,
|
|
464
|
-
"description": "Semantic criteria are evaluated on
|
|
502
|
+
"description": "Semantic criteria are evaluated on N pages only (default 20), in the order of semantic_order; deterministic ones on every page"
|
|
503
|
+
},
|
|
504
|
+
"semantic_order": {
|
|
505
|
+
"enum": ["walk", "priority"],
|
|
506
|
+
"description": "Which pages make the semantic sample. walk (default without an evaluator budget): the first N in walk order. priority (default with evaluate.budget): pages failing a deterministic acceptance criterion first, then those never or least recently checked (last_checked of a rotating sample), then walk order."
|
|
465
507
|
},
|
|
466
508
|
"viewports": {
|
|
467
509
|
"type": "array",
|
|
@@ -492,7 +534,7 @@
|
|
|
492
534
|
},
|
|
493
535
|
"from": {
|
|
494
536
|
"type": "string",
|
|
495
|
-
"description": "title | h1 | description | canonical | lang | words | http | url | path | a CSS selector (text), selector@attribute, or count:selector"
|
|
537
|
+
"description": "title | h1 | description | canonical | lang | words | http | url | path | jsonld_types (the @type values of the JSON-LD blocks) | og:title, twitter:card or meta:<name> (a meta tag's content) | a CSS selector (text), selector@attribute, or count:selector"
|
|
496
538
|
}
|
|
497
539
|
}
|
|
498
540
|
}
|
|
@@ -649,6 +691,16 @@
|
|
|
649
691
|
"link_home",
|
|
650
692
|
"links_ok",
|
|
651
693
|
"hreflang_pairs",
|
|
694
|
+
"llms_txt",
|
|
695
|
+
"nojs_parity",
|
|
696
|
+
"jsonld_present",
|
|
697
|
+
"jsonld_valid",
|
|
698
|
+
"aeo_faq_schema",
|
|
699
|
+
"aeo_answer_first",
|
|
700
|
+
"locale_lang",
|
|
701
|
+
"locale_hreflang",
|
|
702
|
+
"locale_text",
|
|
703
|
+
"locale_prices",
|
|
652
704
|
"app_touch_targets",
|
|
653
705
|
"app_no_truncation",
|
|
654
706
|
"app_no_crash",
|
|
@@ -687,6 +739,11 @@
|
|
|
687
739
|
"attr": {
|
|
688
740
|
"type": "string"
|
|
689
741
|
},
|
|
742
|
+
"types": {
|
|
743
|
+
"type": "array",
|
|
744
|
+
"items": { "type": "string", "minLength": 1 },
|
|
745
|
+
"description": "jsonld_present: schema.org types the page's structured data must contain (Product, BreadcrumbList, FAQPage…)."
|
|
746
|
+
},
|
|
690
747
|
"property": {
|
|
691
748
|
"type": "string",
|
|
692
749
|
"minLength": 1,
|
|
@@ -759,7 +816,13 @@
|
|
|
759
816
|
"ratio": {
|
|
760
817
|
"type": "number",
|
|
761
818
|
"minimum": 0,
|
|
762
|
-
"description": "design_pixels: maximum share of differing pixels (default 0.03)."
|
|
819
|
+
"description": "design_pixels: maximum share of differing pixels (default 0.03); nojs_parity: the least share of the rendered body text the raw HTML must hold (default 0.3)."
|
|
820
|
+
},
|
|
821
|
+
"user_agent": {
|
|
822
|
+
"type": "string",
|
|
823
|
+
"minLength": 1,
|
|
824
|
+
"maxLength": 300,
|
|
825
|
+
"description": "nojs_parity: fetch the raw HTML as this crawler, without cookies, and respect its robots.txt rules: gptbot, oai-searchbot, claudebot, perplexitybot, googlebot, bingbot, or a whole user agent string. Without it, the walker's own browser user agent and session, and robots.txt is not read."
|
|
763
826
|
},
|
|
764
827
|
"follow": {
|
|
765
828
|
"type": "boolean",
|
|
@@ -918,6 +981,10 @@
|
|
|
918
981
|
"type": "boolean",
|
|
919
982
|
"description": "Screenshot of the whole page (default true; false for stack pages: the first screen only)"
|
|
920
983
|
},
|
|
984
|
+
"screenshot": {
|
|
985
|
+
"enum": ["full", "thumb"],
|
|
986
|
+
"description": "thumb keeps only the small card thumbnail (640 px wide) as the step's image: for stacks of hundreds of pages where the checks read the page, not the picture (SEO crawls). Default full."
|
|
987
|
+
},
|
|
921
988
|
"record": { "$ref": "#/$defs/record" },
|
|
922
989
|
"trace": { "type": "boolean", "default": true, "description": "Keep a trace per walked step (actions, network requests, console) shown under Technical; false or walk --no-trace turns it off." }
|
|
923
990
|
}
|
|
@@ -43,8 +43,15 @@ health checks (ids `HC1xx` for `seo`, `HC2xx` for `usability`; acceptance
|
|
|
43
43
|
criteria are `AC`, health checks `HC`, and the number is the check's: the
|
|
44
44
|
same check kept in acceptance or adopted is `AC103` for `HC103`) at load time:
|
|
45
45
|
`seo` checks one H1, title and description length, canonical, hreflang for
|
|
46
|
-
every locale, Open Graph, indexing, viewport, `html lang`, overflow
|
|
47
|
-
console
|
|
46
|
+
every locale, Open Graph, indexing, viewport, `html lang`, overflow,
|
|
47
|
+
console and structured data (HC112 JSON-LD present, HC113 every block valid);
|
|
48
|
+
`aeo` (HC9xx) adds answer-engine signals: FAQPage/QAPage where the page asks
|
|
49
|
+
and answers, an answer-first paragraph under the H1, `/llms.txt`, a meta
|
|
50
|
+
description, the raw HTML as GPTBot gets it without JavaScript (HC905,
|
|
51
|
+
`nojs_parity`; `user_agent: claudebot|googlebot|…` and `ratio` on a criterion
|
|
52
|
+
of your own); `locale` (HC10xx, scenarios with `locales`) checks html lang,
|
|
53
|
+
the own hreflang, the text's language and price decimals against the locale;
|
|
54
|
+
`usability` checks unlabeled inputs and link texts and asks the
|
|
48
55
|
evaluator questions that fit the step's page type. Pack checks are health,
|
|
49
56
|
not acceptance: they are evaluated on every walk but never decide a step's
|
|
50
57
|
verdict (see [evaluation.md#health](evaluation.md#health)). The `design` pack
|
|
@@ -122,13 +129,20 @@ English words; one plain word such as a brand name is left alone).
|
|
|
122
129
|
- a plain-language message with a way forward (semantic);
|
|
123
130
|
- no console errors. The document's own error status is not counted.
|
|
124
131
|
|
|
132
|
+
HC601 and HC605 apply only to error pages: a step with `expect_status`
|
|
133
|
+
(400 or more, or an own `http_status` criterion of 400 or more) or a page
|
|
134
|
+
that answered 4xx/5xx. On other pages they are not applicable, so keep the
|
|
135
|
+
pack off normal pages and stacks (`kaloko validate` warns on a stack).
|
|
136
|
+
|
|
125
137
|
## Error pages
|
|
126
138
|
|
|
127
139
|
Every site has them and they are easy to forget. Give each one its own
|
|
128
140
|
step with `packs: [errors]`:
|
|
129
141
|
|
|
130
|
-
- **404:** use `path: /no-such-page-{random}
|
|
131
|
-
|
|
142
|
+
- **404:** use `path: /no-such-page-{random}` and `expect_status: 404`, so a
|
|
143
|
+
soft 404 (HTTP 200) fails HC601 instead of being not applicable.
|
|
144
|
+
`{random}` becomes a new token on every walk, so no cache or an
|
|
145
|
+
accidentally created page makes it
|
|
132
146
|
pass. `kaloko draft` adds this step on its own.
|
|
133
147
|
- **Exact status:** override HC601 with your own AC601, for example
|
|
134
148
|
`{ id: AC601, text: Answers 410, check: { type: deterministic, assert: http_status, value: 410 } }`.
|
|
@@ -27,6 +27,48 @@ report with the reason `kaloko evaluate` gives.
|
|
|
27
27
|
The default evaluator is text-only and most accurate in English. It is weak
|
|
28
28
|
at numbers, which is why those are checked deterministically.
|
|
29
29
|
|
|
30
|
+
## Budget and your own model
|
|
31
|
+
|
|
32
|
+
On large sites the semantic questions are what costs money. Three settings keep it in hand:
|
|
33
|
+
|
|
34
|
+
- **Budget.** `evaluate.budget.calls` in `kaloko.config.yml` (or `kaloko evaluate --budget N`, `kaloko walk
|
|
35
|
+
--budget N`) caps the uncached JEV calls of one evaluation, the walk's own questions included. Ordinary steps
|
|
36
|
+
go first, then each stack's sample. What does not fit stays `not_evaluated` with the note "the evaluator
|
|
37
|
+
budget of N call(s) is used up", and `kaloko evaluate` prints how many captures it left out. Cached answers
|
|
38
|
+
are free, so the next evaluation continues where this one stopped. With a budget, screens without semantic
|
|
39
|
+
criteria get no new quick signals. Report the skipped count; never call those criteria passed.
|
|
40
|
+
- **Sample order.** A stack asks the semantic questions on `semantic_sample` pages (default 20). With a budget,
|
|
41
|
+
or `items.semantic_order: priority` on the step, they are chosen by priority: pages failing a deterministic
|
|
42
|
+
acceptance criterion first (health checks do not count), then pages never or least recently checked
|
|
43
|
+
(`last_checked` of a rotating sample), then the rest in walk order. Otherwise, and with `semantic_order: walk`,
|
|
44
|
+
the first pages in walk order. The same captures always give the same sample and spend the budget on the same
|
|
45
|
+
pages.
|
|
46
|
+
- **Your own model.** An evaluator of `kind: openai_compatible` (Ollama, LM Studio, vLLM) answers the same
|
|
47
|
+
questions from the same page text, for free and without the page leaving the machine. It is opt-in:
|
|
48
|
+
`evaluators.text: <name>` makes it answer first. With `evaluators.escalate_to: jev` its answers that are not
|
|
49
|
+
clear enough for a verdict (between the thresholds, a missing answer, or anything but a number from 0 to 1)
|
|
50
|
+
are asked again of JEV, counted in the budget; the quick signals are not escalated. Escalation goes only that
|
|
51
|
+
way, never from JEV to your model. Small models are rougher judges, so treat their verdicts as a first pass and
|
|
52
|
+
keep JEV for escalation. After three failed connections in a row an evaluation stops asking your model.
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
evaluate:
|
|
56
|
+
budget: { calls: 300 }
|
|
57
|
+
evaluators:
|
|
58
|
+
ollama:
|
|
59
|
+
kind: openai_compatible
|
|
60
|
+
base_url: http://localhost:11434/v1 # LM Studio: http://localhost:1234/v1
|
|
61
|
+
model: qwen3:8b
|
|
62
|
+
text: ollama
|
|
63
|
+
escalate_to: jev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Every text verdict then names who decided it, with `via`, `model` and `prompt_version`: `openai-compatible`
|
|
67
|
+
via `own-model` for your model, `typesafe-jev` for an escalated answer with the local score kept as
|
|
68
|
+
`first_score`. The canvas, acceptance records and the PR comment show your model's verdicts as such; say so in the
|
|
69
|
+
report too, and never present them as JEV's. Pages are not batched into one
|
|
70
|
+
request: each page state is one request, cached by its content, which is what makes re-evaluation free.
|
|
71
|
+
|
|
30
72
|
## Vision and reasons
|
|
31
73
|
|
|
32
74
|
Every semantic verdict carries one sentence, `reason`, shown as "Why" next to
|
|
@@ -71,7 +113,8 @@ evaluator saw the screenshot, say so.
|
|
|
71
113
|
## Health
|
|
72
114
|
|
|
73
115
|
The checks of packs (`seo`, `a11y`, `perf`, `usability`, `console`,
|
|
74
|
-
`errors`, `app`)
|
|
116
|
+
`errors`, `app`, `aeo`, `locale`), the cross-page checks of stacks with
|
|
117
|
+
`seo` and the site checks of `health.site` are the run's **health** (CS *kondice*): advisory, stored in
|
|
75
118
|
`node.health` and `summary.health` of `run.json`, never in `criteria`. They
|
|
76
119
|
do not change a step's status, the run verdict, the review queue, carried
|
|
77
120
|
approvals, required approvers, the acceptance record or exit codes. The
|
|
@@ -77,6 +77,12 @@ kaloko schedule hook <id> # URL + secret that start the run after a deploy
|
|
|
77
77
|
and the `health.worse` webhook on a new error-level finding. Each schedule
|
|
78
78
|
sets it to `error` (default), `warning` or `off` on kaloko.app or with
|
|
79
79
|
`kaloko schedule add --health-alert`; changing it is the person's call.
|
|
80
|
+
- Health for dashboards: every shared run with health checks gets counts per
|
|
81
|
+
locale, step, check and pack (`GET …/runs/<id>/health/aggregate`,
|
|
82
|
+
`…/scenarios/<id>/latest/health/aggregate`) and a diff against the previous
|
|
83
|
+
run per page (`…/health/diff`: new, fixed, still open, first seen). A
|
|
84
|
+
`read:health` token reads these and nothing else; the `health.changed`
|
|
85
|
+
webhook says when something is new or fixed. help/reference/health-api.md.
|
|
80
86
|
- Does not work for: native apps, local or private addresses (localhost, IPs,
|
|
81
87
|
`.local`), header credentials, base URLs per locale.
|
|
82
88
|
- For those, or for the full walk on a schedule, write a GitHub Actions
|
|
@@ -52,6 +52,8 @@ is null. Projects and products come complete, without pages.
|
|
|
52
52
|
| `add_sample_run` | adds the sample run (or returns the one there is); needs an upload or full token and the Developer / Tester role |
|
|
53
53
|
| `search_screens` | the screen library: newest screenshot of every step across the organization's flows (`q` words matched against titles, ids, paths, URLs, page title and H1; `project`, `product`, `module`, `scenario`, `locale`, `viewport`, `device`, `status`, `environment`; `source: design` or `all` for design versions; `group: none` for every locale and viewport; `cursor` from `next`), each with a signed image and the canvas link |
|
|
54
54
|
| `get_health` | the advisory health of a run (`run_id`), the newest run of a scenario (`scenario`) or the trend of a project (`project`): findings by severity, pack and step with evidence and a fix, and what is new or fixed since the previous run; never a verdict, see [evaluation.md#health](evaluation.md#health) |
|
|
55
|
+
| `get_health_aggregate` | health counted for a dashboard (`run_id`, or `scenario` and `environment` for its newest run; `by` locale, step, check, pack; filters; `top` pages per group): pass / fail / unsure / not evaluated per group, the most affected pages, and new / fixed / still open since the previous run; group ids are stable across runs |
|
|
56
|
+
| `get_health_diff` | per page, locale and check what is new, fixed or still open since the previous run (`state` picks one list), each with `first_seen` (run and date); ids `step\|locale\|check\|page` are stable across runs |
|
|
55
57
|
| `adopt_health_check` | turns a health check into an acceptance criterion of the scenario (`run_id`, `step`, `check` such as `HC103`, `text` to reword it; it becomes `AC103` with `from: HC103`); Developer / Tester role. Only when the person asks; `kaloko health pull` then writes it into the scenario file |
|
|
56
58
|
| `get_affected_steps` | which steps of a scenario changed files reach, with the reason per step (`scenario`, `changed_files`, `run_id`, `upstream`); route files are matched against the step paths and URLs of the newest run. Imports and `affects:` globs need the repository: `kaloko affected` follows them |
|
|
57
59
|
|
|
@@ -17,6 +17,15 @@ 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.
|
|
28
|
+
|
|
20
29
|
Not for a flow of different screens: those stay separate steps. A stack
|
|
21
30
|
step does not click through anything. It opens each page by URL. Use a
|
|
22
31
|
step script only to prepare each page, for example to dismiss a dialog
|
|
@@ -31,10 +40,13 @@ step script only to prepare each page, for example to dismiss a dialog
|
|
|
31
40
|
purpose: Every article reads well and is findable.
|
|
32
41
|
packs: [seo] # health checks of every page (advisory)
|
|
33
42
|
items:
|
|
34
|
-
sitemap: /sitemap.xml # or urls / file / crawl / script (exactly one)
|
|
43
|
+
sitemap: /sitemap.xml # or robots: true / urls / file / crawl / script (exactly one)
|
|
35
44
|
match: '^/clanky/' # regex on the path with its query; exclude: '…' drops pages
|
|
45
|
+
# or a map by locale: { default: '^/blog/', fi: '^/fi/blogi/' }
|
|
36
46
|
limit: 800 # default 500, at most 10000
|
|
37
|
-
|
|
47
|
+
# rotate: week # each ISO week the next `limit` pages (run: each run)
|
|
48
|
+
semantic_sample: 20 # semantic criteria on 20 pages only
|
|
49
|
+
# semantic_order: priority # failing and least recently checked pages first
|
|
38
50
|
# viewports: [desktop] # default: the first viewport of the scenario
|
|
39
51
|
fields: # index columns (max 12); default title, h1, words, http
|
|
40
52
|
- { id: title, label: Title, from: title }
|
|
@@ -55,12 +67,43 @@ Item sources:
|
|
|
55
67
|
- `file: qa/data/products.csv` is a list next to the scenario. Accepted
|
|
56
68
|
formats are text with one path per line, CSV (first column), or JSON:
|
|
57
69
|
`["/a"]` or `[{ path, title, key, purpose }]`, where `key` gives a stable id.
|
|
58
|
-
- `sitemap: /sitemap.xml` also follows a sitemap index
|
|
70
|
+
- `sitemap: /sitemap.xml` also follows a sitemap index, two levels deep
|
|
71
|
+
(up to 50 sitemaps each). Gzipped sitemaps work: a `.gz` URL or gzip
|
|
72
|
+
bytes behind any URL are unpacked.
|
|
73
|
+
- `sitemap: robots` (or `robots: true`) reads the `Sitemap:` lines of
|
|
74
|
+
`/robots.txt` on the environment's origin. When their URLs name the
|
|
75
|
+
walked locale as a segment (`/sitemaps/de/sitemap.xml.gz`,
|
|
76
|
+
`sitemap-de.xml`; `en_US` finds `en-us`, `en` does not), only that
|
|
77
|
+
locale's sitemaps are read; otherwise all of them. Use it when a site
|
|
78
|
+
lists one sitemap per language, so the scenario does not hard-code paths.
|
|
59
79
|
- `crawl: { from: /clanky, links: "a.card", next: "a.next", pages: 20 }`
|
|
60
80
|
collects links from a listing and follows its pagination.
|
|
61
81
|
- `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.
|
|
82
|
+
`({ page, env, locale, url, node, params }) => [paths | { path, title, key, purpose }]`
|
|
83
|
+
for anything else, such as an API or a search. `node` is
|
|
84
|
+
`{ id, title, lane, lane_title, session }` of the step and `params` is
|
|
85
|
+
`items.params`, so one script can serve several stacks.
|
|
86
|
+
|
|
87
|
+
Fix handoff: a script or a JSON list may give each page `edit_url` (the
|
|
88
|
+
page in the CMS, http(s) only) and `content_ref` (its record id for a
|
|
89
|
+
content API). `resolve: steps/resolve.mjs` (default export
|
|
90
|
+
`async (item, { env, locale, node, params, url }) => ({ edit_url, content_ref })`)
|
|
91
|
+
looks them up for the walked pages only, 15 s per page; a page it fails on is walked
|
|
92
|
+
without them and the walk says how many. They show in the page detail
|
|
93
|
+
("Edit in CMS"), the stack CSV, `get_stack_items` and
|
|
94
|
+
`kaloko export findings`.
|
|
95
|
+
|
|
96
|
+
`match` and `exclude` apply to every source before `limit` (and before
|
|
97
|
+
`rotate`). Either is a regex or a map by locale; the walked locale, then its
|
|
98
|
+
language (`en` for `en_US`), then `default` is used. A `match` map without
|
|
99
|
+
the locale and without `default` is an error.
|
|
100
|
+
|
|
101
|
+
`rotate: week` (or `rotate: run`) walks a big list in turns: each ISO week
|
|
102
|
+
(or each run) takes the next `limit` pages, wrapping around, so the list is
|
|
103
|
+
covered in `pages ÷ limit` rounds. `run` keeps its count and each page's last
|
|
104
|
+
check in `.cache/stack/` of the output folder, so use `week` on CI with a
|
|
105
|
+
fresh checkout. The stack index gets `rotation` per locale (window, covered,
|
|
106
|
+
never checked) and each page `last_checked` (its previous check, or null).
|
|
64
107
|
|
|
65
108
|
An item's `purpose` (for example its title and meta description) is what the
|
|
66
109
|
semantic evaluator reads as `screen_purpose` for that page instead of the
|
|
@@ -73,11 +116,44 @@ in the wrong language.
|
|
|
73
116
|
Pages are deduplicated. External URLs are allowed (e.g. an old domain).
|
|
74
117
|
`{locale}` in any source string is replaced by the walked locale. This is
|
|
75
118
|
how one sitemap with every language is split:
|
|
76
|
-
`match: '^/guides/.*lang={locale}'`, or `sitemap: /{locale}/sitemap.xml
|
|
119
|
+
`match: '^/guides/.*lang={locale}'`, or `sitemap: /{locale}/sitemap.xml`,
|
|
120
|
+
or `sitemap: /sitemaps/{locale}/sitemap.xml.gz`. One content family per
|
|
121
|
+
stack: the same per-locale sitemap with `match: '/eshop/'` in one step and
|
|
122
|
+
`match: '/(magazin|magazine|revista)/'` in another.
|
|
77
123
|
|
|
78
124
|
Field sources: `title`, `h1`, `description`, `canonical`, `lang`, `url`,
|
|
79
125
|
`path`, `words` (words in main/article/body), `http` (the navigation
|
|
80
|
-
status), `
|
|
126
|
+
status), `jsonld_types` (the schema.org types of the page's JSON-LD, for
|
|
127
|
+
example `Product, Offer, BreadcrumbList`; `invalid×1` when a block does not
|
|
128
|
+
parse), a meta tag by name (`og:title`, `og:image`, `twitter:card`,
|
|
129
|
+
`meta:robots`), `count:<selector>`, `<selector>` (text) and
|
|
130
|
+
`<selector>@<attr>`.
|
|
131
|
+
|
|
132
|
+
Structured data is also summarised without a field: every capture records
|
|
133
|
+
`structured_data` (blocks, invalid blocks, types), and `items.json` carries
|
|
134
|
+
`structured_data` per page and, at the top, pages per type, pages without
|
|
135
|
+
structured data and pages with invalid JSON-LD. The `seo` pack checks it
|
|
136
|
+
(HC112 present, HC113 valid); `packs: [aeo]` adds the answer-engine signals
|
|
137
|
+
(HC901–HC905; HC905 `nojs_parity` fetches each page's raw HTML as GPTBot,
|
|
138
|
+
respecting robots.txt, and fails when the title, canonical, H1, JSON-LD or
|
|
139
|
+
most of the text appear only after JavaScript). See
|
|
140
|
+
help/reference/checks-and-packs.md.
|
|
141
|
+
|
|
142
|
+
With the `seo` pack a stack also compares its pages with each other after the
|
|
143
|
+
walk (HC121–HC126, advisory): shared titles, descriptions and H1s within a
|
|
144
|
+
language, canonicals pointing elsewhere or to error/noindex pages, hreflang
|
|
145
|
+
that the other walked version does not answer, and (sitemap or robots
|
|
146
|
+
sources) listed pages that answer non-200, redirect or say noindex. They are
|
|
147
|
+
health entries of the stack node (`cross_page: true`, `counts` in pages,
|
|
148
|
+
`urls`); `items.json` has them under `cross_page` with the groups, and each
|
|
149
|
+
affected page lists the ids in `cross_fails`. They cannot be adopted.
|
|
150
|
+
`packs: [locale]` (scenarios with `locales`) checks html lang, the own
|
|
151
|
+
hreflang, the text's language and price decimals against the walked locale
|
|
152
|
+
(HC1001–HC1004). `health: { site: true }` on the scenario adds site checks
|
|
153
|
+
once per run (robots.txt, sitemaps, llms.txt, http→https and www redirects;
|
|
154
|
+
HC1101–HC1107, `site.json` in the run, `site` in the manifest). Off the
|
|
155
|
+
`production` environment HC1102 is a warning and HC1103 is skipped;
|
|
156
|
+
`health.site.environments: [production]` runs them there only.
|
|
81
157
|
|
|
82
158
|
A step `execution.script` still works. It is called with `item` in the
|
|
83
159
|
context, opens `item.url` itself and may call `capture()`. Without a script,
|
|
@@ -86,19 +162,28 @@ the walk opens each page and captures it.
|
|
|
86
162
|
## Cost and size: choose deliberately
|
|
87
163
|
|
|
88
164
|
- 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
|
-
|
|
165
|
+
- Semantic criteria go to the evaluator. On a stack they run only on
|
|
166
|
+
`semantic_sample` pages (default 20), the first in walk order. With
|
|
167
|
+
`semantic_order: priority`, or an evaluator budget, pages failing a
|
|
168
|
+
deterministic acceptance criterion come first, then those never or least
|
|
169
|
+
recently checked. The other pages show the criterion as not evaluated. `evaluate.budget.calls`
|
|
170
|
+
caps the calls of a whole evaluation (evaluation.md). Raise the sample only when the question
|
|
92
171
|
really differs between pages, and tell the person what it costs
|
|
93
172
|
(one call per page and viewport).
|
|
94
173
|
- Stacks capture the first screen of each page (fast, small). Set
|
|
95
174
|
`capture: { full_page: true }` when the criteria are about the whole page.
|
|
96
175
|
- Walking takes about 1–2 s per page. For a first look, use
|
|
97
176
|
`kaloko walk --limit 50`. Re-walk chosen pages with
|
|
98
|
-
`--items clanek-7,clanek-12` (matches the id or the path
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
177
|
+
`--items clanek-7,clanek-12` (matches the id or the path; picked from
|
|
178
|
+
every matching page, also outside this week's rotation, and such a
|
|
179
|
+
partial walk leaves the rotation state alone).
|
|
180
|
+
- A shared run holds at most 20000 files and 300 MB as stored (HTML and JSON
|
|
181
|
+
travel gzip-compressed, about a tenth of their size on disk). Each page
|
|
182
|
+
stores about four files, so a stack of a few thousand first-screen pages
|
|
183
|
+
fits. For SEO crawls where the checks read the page, not the picture, set
|
|
184
|
+
`capture: { screenshot: thumb }`: only the 640 px thumbnail is kept. Split
|
|
185
|
+
larger catalogues by section (`match`) or walk a rotating sample
|
|
186
|
+
(`rotate: week`).
|
|
102
187
|
|
|
103
188
|
## Reading the results
|
|
104
189
|
|
|
@@ -118,6 +203,11 @@ the walk opens each page and captures it.
|
|
|
118
203
|
`get_stack_items {run_id, step, status?, criterion?, query?, offset, limit}`
|
|
119
204
|
lists pages with fields, failing criteria, evidence and screenshot URLs.
|
|
120
205
|
This is the way to hand an agent "fix these 37 articles".
|
|
206
|
+
- `kaloko export findings [--format csv|json] [--locale l] [--step s] [--out file]`
|
|
207
|
+
writes the fix list of the local run: one row per failing criterion or
|
|
208
|
+
health check, page and locale, with URL, evidence, `edit_url`,
|
|
209
|
+
`content_ref` and `recheck` (`kaloko walk --steps s --items <page> --locales l`,
|
|
210
|
+
which re-walks just that page after the fix).
|
|
121
211
|
|
|
122
212
|
## Report
|
|
123
213
|
|