kaloko 0.17.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/README.md +7 -0
- package/package.json +1 -1
- package/schemas/config.v1.json +51 -1
- package/schemas/run.v1.json +64 -1
- package/schemas/scenario.v1.json +81 -14
- package/skills/kaloko/SKILL.md +4 -0
- package/skills/kaloko/reference/acceptance.md +18 -4
- package/skills/kaloko/reference/ci.md +83 -0
- 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 +186 -34
- package/src/lib/build.mjs +18 -7
- package/src/lib/capture.mjs +31 -9
- package/src/lib/changelog-core.mjs +19 -1
- package/src/lib/ci.mjs +4 -2
- 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/git.mjs +14 -1
- 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 +40 -19
package/README.md
CHANGED
|
@@ -162,6 +162,13 @@ jobs:
|
|
|
162
162
|
Uploads go by content hash: captures that did not change since an earlier run
|
|
163
163
|
are not sent again, and an interrupted `share` picks up where it stopped.
|
|
164
164
|
|
|
165
|
+
With the Kaloko GitHub App (Team and up), each pull request also gets one
|
|
166
|
+
`Kaloko` check on its newest commit. It turns green when the runs shared for
|
|
167
|
+
that commit pass, whether they came from this workflow, a coding agent in the
|
|
168
|
+
cloud or a laptop. Branch protection can require it, and labels and
|
|
169
|
+
`/kaloko skip <reason>` control a single pull request. Details:
|
|
170
|
+
`skills/kaloko/reference/ci.md`.
|
|
171
|
+
|
|
165
172
|
## When a screenshot is not enough
|
|
166
173
|
|
|
167
174
|
```bash
|
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,
|
|
@@ -136,6 +154,21 @@
|
|
|
136
154
|
"site_contacts": { "type": "boolean", "default": true, "description": "kaloko share takes the site's own contacts for not personal data: e-mails on the environment's domain, e-mails and phone numbers in the page footer, and those of an Organization or ContactPoint in JSON-LD. false checks them like any other value." }
|
|
137
155
|
}
|
|
138
156
|
},
|
|
157
|
+
"github": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"description": "The Kaloko check on pull requests (#141). The service reads it from the base branch; it may only make the check stricter than Settings on Kaloko.",
|
|
161
|
+
"properties": {
|
|
162
|
+
"checks": { "enum": ["off", "manual", "auto", "strict"], "description": "manual: only on request (label kaloko:run, /kaloko walk); auto: every pull request, the scenarios its change reaches; strict: every pull request, all scenarios, review required." },
|
|
163
|
+
"scope": { "enum": ["affected", "all"] },
|
|
164
|
+
"require_review": { "type": "boolean", "description": "Also post Kaloko review, which turns green when the runs are accepted." },
|
|
165
|
+
"drafts": { "enum": ["wait", "run"], "description": "wait: no check on a draft pull request until it is ready for review." },
|
|
166
|
+
"skip_authors": { "type": "array", "items": { "type": "string" }, "description": "Only in Settings: skipping authors would loosen the check, so the file's list is ignored." },
|
|
167
|
+
"skip_by": { "enum": ["write", "maintain", "admin"], "description": "The access a person needs to skip a pull request." },
|
|
168
|
+
"author_may_skip": { "type": "boolean" },
|
|
169
|
+
"wait_hours": { "type": "integer", "minimum": 1, "maximum": 336, "description": "strict: hours to wait for a run before the check times out." }
|
|
170
|
+
}
|
|
171
|
+
},
|
|
139
172
|
"share": {
|
|
140
173
|
"type": "object",
|
|
141
174
|
"additionalProperties": false,
|
|
@@ -186,6 +219,23 @@
|
|
|
186
219
|
"images": { "type": "object", "additionalProperties": false, "properties": { "viewport": { "type": "string" }, "locale": { "type": "string", "description": "reader (the reader's language) or a locale." } } }
|
|
187
220
|
}
|
|
188
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
|
+
},
|
|
189
239
|
"record": {
|
|
190
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.",
|
|
191
241
|
"oneOf": [
|
package/schemas/run.v1.json
CHANGED
|
@@ -116,6 +116,23 @@
|
|
|
116
116
|
"sha": {
|
|
117
117
|
"type": "string",
|
|
118
118
|
"description": "Full commit hash (GitHub check runs need it)."
|
|
119
|
+
},
|
|
120
|
+
"repo": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "owner/name of the origin remote (kaloko 0.18+): the service finds the pull request by commit when pr is missing."
|
|
123
|
+
},
|
|
124
|
+
"host": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"enum": ["github", "gitlab", "other"],
|
|
127
|
+
"description": "Where the origin remote lives."
|
|
128
|
+
},
|
|
129
|
+
"prefix": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "The project's folder inside the repository (monorepos), without a trailing slash."
|
|
132
|
+
},
|
|
133
|
+
"dirty": {
|
|
134
|
+
"type": "boolean",
|
|
135
|
+
"description": "Tracked files differed from the commit when the run started: the run does not count for the commit's GitHub check."
|
|
119
136
|
}
|
|
120
137
|
}
|
|
121
138
|
},
|
|
@@ -424,6 +441,40 @@
|
|
|
424
441
|
"null"
|
|
425
442
|
],
|
|
426
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
|
+
}
|
|
427
478
|
}
|
|
428
479
|
},
|
|
429
480
|
"$defs": {
|
|
@@ -648,7 +699,9 @@
|
|
|
648
699
|
"counts": { "type": "object", "description": "Stack steps: pages per verdict." },
|
|
649
700
|
"evidence": { "type": ["string", "null"] },
|
|
650
701
|
"check": { "type": "object" },
|
|
651
|
-
"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." }
|
|
652
705
|
}
|
|
653
706
|
}
|
|
654
707
|
},
|
|
@@ -707,6 +760,16 @@
|
|
|
707
760
|
"string",
|
|
708
761
|
"null"
|
|
709
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" }
|
|
710
773
|
}
|
|
711
774
|
}
|
|
712
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
|
}
|
package/skills/kaloko/SKILL.md
CHANGED
|
@@ -168,6 +168,10 @@ only when a person says that value may be uploaded.
|
|
|
168
168
|
[reference/recording.md](reference/recording.md)
|
|
169
169
|
- CI: a workflow that walks each pull request's preview deployment and
|
|
170
170
|
comments on it (`kaloko ci github|gitlab`): [reference/ci.md](reference/ci.md)
|
|
171
|
+
- The `Kaloko` check on a pull request, what makes it pass, `kaloko ci plan`,
|
|
172
|
+
labels and `/kaloko` commands, and walking from a coding agent in the cloud
|
|
173
|
+
(commit first, a dirty tree does not count; never skip the check yourself):
|
|
174
|
+
[reference/ci.md#the-kaloko-check-on-pull-requests](reference/ci.md#the-kaloko-check-on-pull-requests)
|
|
171
175
|
- Environments, access (gates, accounts, sign-in), e-mails, readonly and
|
|
172
176
|
production rules, and what is yours to arrange for the project's stack:
|
|
173
177
|
[reference/environments.md](reference/environments.md)
|
|
@@ -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 } }`.
|
|
@@ -30,6 +30,10 @@ the deployment):
|
|
|
30
30
|
`walk --ephemeral --affected` and `share --pr` (one comment per scenario,
|
|
31
31
|
updated on later pushes).
|
|
32
32
|
|
|
33
|
+
Before the loop, the workflow runs `kaloko ci plan` (see
|
|
34
|
+
[The Kaloko check on pull requests](#the-kaloko-check-on-pull-requests)), so
|
|
35
|
+
the check on the pull request knows which scenarios to wait for.
|
|
36
|
+
|
|
33
37
|
`kaloko ci` also adds the `preview` environment to `kaloko.config.yml` with
|
|
34
38
|
`base_url: ${KALOKO_PREVIEW_URL}`. Scenarios that list `environments:` must
|
|
35
39
|
include `preview`, or `start` refuses them; the command says which.
|
|
@@ -67,6 +71,85 @@ in the newest shared run of the scenario count too, so a fresh CI checkout
|
|
|
67
71
|
maps routes as well as a laptop with earlier runs. See
|
|
68
72
|
[execution.md](execution.md#only-what-changed).
|
|
69
73
|
|
|
74
|
+
## The Kaloko check on pull requests
|
|
75
|
+
|
|
76
|
+
With the Kaloko GitHub App installed (an organization admin: Settings →
|
|
77
|
+
Integrations → GitHub), every pull request in a connected repository gets one
|
|
78
|
+
check named `Kaloko` on its newest commit. It does not walk anything itself:
|
|
79
|
+
it waits for the runs shared for that commit, from a coding agent in the
|
|
80
|
+
cloud, a person's machine or this workflow, and decides from them.
|
|
81
|
+
|
|
82
|
+
| The check says | When |
|
|
83
|
+
|---|---|
|
|
84
|
+
| success | every scenario the change reaches has a run on this commit without failing steps, or nothing reaches a scenario |
|
|
85
|
+
| failure | a run on this commit has a failing step (in strict mode a partial one too) |
|
|
86
|
+
| in progress | a reached scenario has no run on this commit yet; the summary says how to get one |
|
|
87
|
+
| neutral | the repository has no scenario yet, or only code Kaloko cannot map without the repository changed (auto mode) |
|
|
88
|
+
| skipped | someone with the right access skipped it; the summary names them and their reason |
|
|
89
|
+
|
|
90
|
+
Runs of a dirty working tree (tracked files differ from the commit) are
|
|
91
|
+
listed but do not count: commit first, then walk. A new push supersedes the
|
|
92
|
+
old commit's check. The optional `Kaloko review` check follows the people's
|
|
93
|
+
decision (accepted, returned) on those runs.
|
|
94
|
+
|
|
95
|
+
Which scenarios a change reaches: the service maps the pull request's files
|
|
96
|
+
onto the steps' paths and captured URLs, like `kaloko affected` without the
|
|
97
|
+
repository. `kaloko ci plan` sends what `kaloko affected` found (it follows
|
|
98
|
+
imports and `affects:` globs), which wins:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
kaloko ci plan # every scenario of the config, for HEAD
|
|
102
|
+
kaloko ci plan --scenario qa/flows/checkout.yml --none # this commit reaches nothing there
|
|
103
|
+
kaloko ci plan --dry-run --json # show it, send nothing
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Modes (Settings, per repository): `off`, `manual` (only after the label
|
|
107
|
+
`kaloko:run` or the comment `/kaloko walk`), `auto` (the default) and
|
|
108
|
+
`strict` (all scenarios, review required, unknown impact waits, the check
|
|
109
|
+
times out after `wait_hours`). The `github:` section of `kaloko.config.yml`
|
|
110
|
+
on the base branch may make it stricter, never looser:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
github:
|
|
114
|
+
checks: strict # off | manual | auto | strict
|
|
115
|
+
scope: all # affected | all
|
|
116
|
+
require_review: true
|
|
117
|
+
drafts: run # wait (default) | run
|
|
118
|
+
skip_by: maintain # write | maintain | admin
|
|
119
|
+
author_may_skip: false
|
|
120
|
+
wait_hours: 12
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
One pull request: the labels `kaloko:skip` (whole pull request), `kaloko:all`,
|
|
124
|
+
`kaloko:review`, `kaloko:run`; the comments `/kaloko skip <reason>` (this
|
|
125
|
+
commit only), `/kaloko walk`, `/kaloko all`; the buttons on the check. Only
|
|
126
|
+
people with the access set in Settings skip, never apps or bots, and every
|
|
127
|
+
skip is on the check and in the audit log. Do not skip a check yourself as an
|
|
128
|
+
agent; walk, or tell the person why it fails.
|
|
129
|
+
|
|
130
|
+
To make it block merging, the repository's branch protection (or ruleset)
|
|
131
|
+
requires the status check `Kaloko`. Kaloko never changes branch protection.
|
|
132
|
+
|
|
133
|
+
### Coding agents in the cloud
|
|
134
|
+
|
|
135
|
+
An agent in a sandbox (Claude Code on the web, Codex, Cursor background
|
|
136
|
+
agents, Copilot) runs the same commands as on a laptop:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
git commit -am "…" # walk what is committed
|
|
140
|
+
npx kaloko start --scenario qa/flows/checkout.yml --env preview
|
|
141
|
+
npx kaloko walk --affected
|
|
142
|
+
npx kaloko share # --pr also comments, if gh is signed in
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- `KALOKO_TOKEN` goes into the sandbox's secrets or environment variables
|
|
146
|
+
(tester role, upload scope; a token per agent shows who shared what).
|
|
147
|
+
- `npx kaloko browser install --with-deps` belongs in the environment's setup
|
|
148
|
+
script. When the sandbox cannot run a browser or reach the preview, let the
|
|
149
|
+
workflow above walk instead.
|
|
150
|
+
- `gh` is not needed: the run carries the repository and the commit, and
|
|
151
|
+
Kaloko finds the pull request.
|
|
152
|
+
|
|
70
153
|
## Exit codes
|
|
71
154
|
|
|
72
155
|
| Code | Meaning |
|
|
@@ -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
|
|