@crvouga/atlas 1.12.0 → 1.14.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/CHANGELOG.md +13 -1
- package/dist/critics/src/contract.d.ts +113 -0
- package/dist/critics/src/contract.js +48 -0
- package/dist/critics/src/credentials.d.ts +72 -0
- package/dist/critics/src/credentials.js +136 -0
- package/dist/critics/src/critique.d.ts +128 -0
- package/dist/critics/src/critique.js +163 -0
- package/dist/critics/src/index.d.ts +21 -0
- package/dist/critics/src/index.js +21 -0
- package/dist/critics/src/judging.d.ts +153 -0
- package/dist/critics/src/judging.js +145 -0
- package/dist/critics/src/publish.d.ts +23 -0
- package/dist/critics/src/publish.js +21 -0
- package/dist/critics/src/runner.d.ts +104 -0
- package/dist/critics/src/runner.js +202 -0
- package/dist/critics/src/scope.d.ts +8 -0
- package/dist/critics/src/scope.js +26 -0
- package/dist/playwright/src/index.d.ts +4 -0
- package/dist/playwright/src/index.js +4 -0
- package/dist/playwright/src/ui-catalog.d.ts +51 -0
- package/dist/playwright/src/ui-catalog.js +44 -0
- package/dist/reports/src/compose.d.ts +2 -2
- package/dist/reports/src/compose.js +6 -5
- package/dist/reports/src/evidence-types.d.ts +3 -0
- package/dist/reports/src/evidence-view.d.ts +2 -1
- package/dist/reports/src/evidence-view.js +5 -2
- package/dist/reports/src/layer-merge.d.ts +16 -4
- package/dist/reports/src/layer-merge.js +23 -5
- package/dist/reports/src/report-index.js +9 -5
- package/dist/reports/src/report-status.d.ts +1 -1
- package/dist/reports/src/report-status.js +6 -4
- package/dist/reports/src/ui-catalog-publish.d.ts +92 -0
- package/dist/reports/src/ui-catalog-publish.js +68 -0
- package/dist/reports/src/ui-catalog.d.ts +229 -0
- package/dist/reports/src/ui-catalog.js +273 -0
- package/dist/reports/src/ui-state.d.ts +70 -0
- package/dist/reports/src/ui-state.js +66 -0
- package/dist/schema/src/api.d.ts +351 -0
- package/dist/schema/src/api.js +72 -1
- package/dist/schema/src/critique.d.ts +144 -0
- package/dist/schema/src/critique.js +96 -0
- package/dist/schema/src/index.d.ts +2 -1
- package/dist/schema/src/index.js +2 -1
- package/dist/schema/src/server-config.d.ts +7 -1
- package/dist/schema/src/server-config.js +6 -3
- package/dist/server/src/app.js +15 -0
- package/dist/server/src/auth.d.ts +1 -1
- package/dist/server/src/auth.js +2 -2
- package/dist/server/src/config.js +2 -2
- package/dist/server/src/openapi.d.ts +3 -3
- package/dist/server/src/openapi.js +29 -6
- package/dist/server/src/store-api.js +39 -1
- package/docs/agents.md +1 -0
- package/docs/critics.md +190 -0
- package/docs/extending.md +1 -0
- package/docs/reference/critics.md +840 -0
- package/docs/reference/http-api.md +52 -2
- package/docs/reference/playwright.md +63 -0
- package/docs/reference/reports.md +608 -8
- package/docs/reference/schema.md +152 -2
- package/docs/reference/server.md +3 -3
- package/docs/report-contract.md +6 -2
- package/docs/rich-evidence.md +5 -0
- package/docs/server.md +8 -4
- package/docs/ui-catalog.md +173 -0
- package/docs/ui.md +15 -0
- package/llms-full.txt +457 -8
- package/llms.txt +3 -0
- package/package.json +8 -2
- package/packages/critics/README.md +18 -0
- package/packages/schema/schemas/critique.schema.json +254 -0
- package/packages/schema/schemas/effective-delta.schema.json +73 -0
- package/packages/schema/schemas/effective-evidence.schema.json +73 -0
- package/packages/schema/schemas/layer-rows.schema.json +73 -0
- package/packages/schema/schemas/server-config.schema.json +2 -1
- package/packages/schema/schemas/server-info.schema.json +1 -0
- package/packages/server/openapi.json +1443 -80
- package/packages/visualizer/src/app/AppShell.tsx +36 -30
- package/packages/visualizer/src/app/router.tsx +10 -1
- package/packages/visualizer/src/catalog/UiCatalogFilters.tsx +65 -0
- package/packages/visualizer/src/catalog/UiCatalogPage.tsx +342 -0
- package/packages/visualizer/src/catalog/provider.tsx +25 -0
- package/packages/visualizer/src/catalog/search.ts +68 -0
- package/packages/visualizer/src/catalog/words.ts +26 -0
- package/packages/visualizer/src/components/Header.tsx +17 -4
package/docs/critics.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Critics: automated reviewers that mark which UIs need polish
|
|
2
|
+
|
|
3
|
+
A **critic** looks at one captured UI (an image and what is known about it) and says how polished it is, and ideally
|
|
4
|
+
where and why it is not. A critic may be a pretrained model that gives a score, a vision-capable language model that
|
|
5
|
+
follows a rubric, or a classic measure such as a contrast ratio. All three are the same thing to Atlas, through one small
|
|
6
|
+
contract, so people review the likeliest problems first.
|
|
7
|
+
|
|
8
|
+
**Critics are producers that publish layers.** Atlas supplies the contract, the runner, the evidence format and the
|
|
9
|
+
credential. It never executes a model on the server and never holds a provider key. A critique is not a test result and
|
|
10
|
+
is not human review: it never changes whether a report passes.
|
|
11
|
+
|
|
12
|
+
- [What is available](#what-is-available)
|
|
13
|
+
- [The critic contract](#the-critic-contract)
|
|
14
|
+
- [A critique is a layer](#a-critique-is-a-layer)
|
|
15
|
+
- [Running critics](#running-critics)
|
|
16
|
+
- [State-aware judging, calibration and modes](#state-aware-judging-calibration-and-modes)
|
|
17
|
+
- [Authentication and authorization](#authentication-and-authorization)
|
|
18
|
+
- [Credentials](#credentials)
|
|
19
|
+
- [What is not here yet](#what-is-not-here-yet)
|
|
20
|
+
|
|
21
|
+
## What is available
|
|
22
|
+
|
|
23
|
+
Import `@crvouga/atlas/critics`. It needs no model, no provider SDK and no network; a critic brings its own.
|
|
24
|
+
|
|
25
|
+
| You want | Use |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Define a critic | `defineCritic` (the contract below) |
|
|
28
|
+
| Judge many images with caching, retries, rate limits, timeouts and budgets | `runCritic` |
|
|
29
|
+
| Store the opinions | `critiqueLayer`, `publishCritiques` (a layer of parts of kind `critique`) |
|
|
30
|
+
| Read them back | `readCritique`, `critiquesOfLayer`, `critiqueCurrency` |
|
|
31
|
+
| Read a score in context, measure a threshold, decide an exit code | `relativeVerdict`, `calibrate`, `gateDecision` |
|
|
32
|
+
| Keep keys out of configuration and logs | `parseCredentialRef`, `resolveCredential`, `assertNoLiteralCredentials`, `createRedactor` |
|
|
33
|
+
| Let a token publish critiques and nothing else | the `critic` role (below) |
|
|
34
|
+
|
|
35
|
+
The JSON Schema of the evidence is [`critique.schema.json`](../packages/schema/schemas/critique.schema.json). The
|
|
36
|
+
function-level reference is generated from the code: [critics](reference/critics.md).
|
|
37
|
+
|
|
38
|
+
## The critic contract
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { defineCritic } from '@crvouga/atlas/critics';
|
|
42
|
+
|
|
43
|
+
export default defineCritic({
|
|
44
|
+
name: 'contrast', version: '1.0.0', kind: 'heuristic', // kind is an open string: model, llm, ml, heuristic…
|
|
45
|
+
describe: () => ({ sendsImagesTo: null, needsSetup: false }),
|
|
46
|
+
evaluate: async ({ image, imageSha256, entry, signal }) => ({
|
|
47
|
+
verdict: { score: measureContrast(image), scale: '0..1, higher is better' }
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A critic only judges one image. `describe()` says where images are sent (`sendsImagesTo`, `null` when they never leave
|
|
53
|
+
the machine), the model, and the fixed parameters (temperature, the SHA-256 of the rubric): together with the critic's
|
|
54
|
+
`name` and `version` and the image hash they are the identity of a judgement. `evaluate` returns a `verdict` (a score
|
|
55
|
+
and how to read it), `findings`, or both, and may throw: throw a `RateLimitedError` (with the provider's `Retry-After` in
|
|
56
|
+
milliseconds), an `InvalidOutputError`, or any `CriticError` with your own reason code. The runner records the failure
|
|
57
|
+
and the next run retries it. A failure never becomes a score.
|
|
58
|
+
|
|
59
|
+
## A critique is a layer
|
|
60
|
+
|
|
61
|
+
A critique is a check inside a part of kind `critique`, in a layer whose `producer` is `critic.<name>`. The check's
|
|
62
|
+
`status` says only whether the critic ran: `passed` a verdict was produced, `failed` the critic could not produce one.
|
|
63
|
+
The opinion is evidence, by the rules of [extending.md](extending.md#the-rules-that-keep-it-compatible-forever), so it
|
|
64
|
+
can never decide a result. It lives in `extensions["atlas.critique"]` and is repeated as standard evidence, so a viewer
|
|
65
|
+
that has never heard of critics still shows it: the score is a measurement named `polish`, and each finding is an
|
|
66
|
+
annotation of type `critique`.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"producer": "critic.vision-llm",
|
|
71
|
+
"kind": "critique",
|
|
72
|
+
"checks": [{
|
|
73
|
+
"id": "critique/checkout/payment-sheet",
|
|
74
|
+
"status": "passed",
|
|
75
|
+
"measurements": [{ "name": "polish", "value": 0.62, "unit": "score" }],
|
|
76
|
+
"annotations": [{ "type": "critique", "level": "warning", "message": "spacing: Label touches both edges of the button. (suggestion: Increase horizontal padding.)" }],
|
|
77
|
+
"extensions": { "atlas.critique": {
|
|
78
|
+
"schema": 1,
|
|
79
|
+
"subject": { "entryKey": "checkout/payment-sheet", "imageSha256": "…", "state": "overlay" },
|
|
80
|
+
"critic": { "name": "vision-llm", "kind": "llm", "version": "1.0.0" },
|
|
81
|
+
"model": { "id": "…", "provider": "…" },
|
|
82
|
+
"parameters": { "temperature": 0, "rubric": "<sha256 of the rubric>" },
|
|
83
|
+
"verdict": { "score": 0.62, "scale": "0..1, higher is better", "label": "needs-polish", "confidence": 0.7 },
|
|
84
|
+
"findings": [{ "id": "f1", "category": "spacing", "severity": "minor", "region": { "unit": "px", "x": 24, "y": 612, "w": 342, "h": 48 }, "rationale": "Label touches both edges of the button.", "suggestion": "Increase horizontal padding." }]
|
|
85
|
+
} }
|
|
86
|
+
}]
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- A single score (a model-based critic) is a valid critique: everything except `schema`, `subject` and `critic` is optional.
|
|
91
|
+
- `kind`, `category`, `severity`, `label` and failure `reason` are open strings, and every object is open. A value or field
|
|
92
|
+
nobody has heard of is kept and shown generically, never rejected.
|
|
93
|
+
- A reader that cannot read the whole extension (a missing `subject`, a score that is not a number, a `schema` version it
|
|
94
|
+
does not know) drops the extension and keeps the check and its status. An unreadable opinion is unknown.
|
|
95
|
+
- **Anchored to pixels.** `subject.imageSha256` is the hash of the image that was judged. When an entry's image changes,
|
|
96
|
+
`critiqueCurrency` says the old critique judged an earlier image: it is shown, never hidden, and the entry counts as not
|
|
97
|
+
yet judged.
|
|
98
|
+
- **Slots.** The producer is the critic's name; the critic and model versions are labels. A newer version of a critic
|
|
99
|
+
replaces an older one's critique of the same entry. Put the version in `parameters` of `critiqueLayer` to compare
|
|
100
|
+
versions side by side instead.
|
|
101
|
+
- **The prompt is recorded as a hash** (`parameters.rubric`), not as text.
|
|
102
|
+
- **Failures are data.** A crash, a timeout, a rate limit that never cleared and an unparseable answer are `failed`
|
|
103
|
+
critiques with `failure.reason` (`error`, `timeout`, `rate-limited`, `invalid-output`, `image-mismatch`…) and a message.
|
|
104
|
+
|
|
105
|
+
**Critiques never change the gate.** Parts of kind `critique` are left out of every count and of `atlas report status`,
|
|
106
|
+
even when they are the only thing recorded (a report of opinions alone has no test results, so the gate says so). A low
|
|
107
|
+
score does not fail it, and neither does a critic that crashed. A project that wants a score to fail something opts in
|
|
108
|
+
with `gateDecision` in `gate` mode, which is computed by the reader from critique data and never changes a check's
|
|
109
|
+
status, a slot or a winner.
|
|
110
|
+
|
|
111
|
+
## Running critics
|
|
112
|
+
|
|
113
|
+
`runCritic` owns everything around the judgement; a critic only judges one image.
|
|
114
|
+
|
|
115
|
+
- **Caching.** An entry is skipped, without fetching its image, when a critique with a verdict already exists for the same
|
|
116
|
+
critic, version, model, parameters and image hash (`existing`, read with `critiquesOfLayer`). Identical images across
|
|
117
|
+
runs are judged once; only a changed image is judged again. A failed critique is not a cache hit. `force` judges again.
|
|
118
|
+
Layers are content-addressed, so publishing the same outcomes twice is harmless.
|
|
119
|
+
- **Concurrency, timeout, rate limits.** A bounded number of calls in flight. A call over `timeoutMs` is aborted and
|
|
120
|
+
recorded as `timeout`; the run goes on. A rate-limit answer waits at least `Retry-After`, with exponential backoff and
|
|
121
|
+
jitter, for a bounded number of attempts, and then is recorded as `rate-limited`.
|
|
122
|
+
- **Budgets.** `budget: { maxImages, maxTokens, maxCostUsd }`. When one is reached the run stops cleanly and returns what
|
|
123
|
+
it finished with `stoppedBy: 'budget'`. Cost and tokens come from the `usage` a critic reports.
|
|
124
|
+
- **Dry run.** `dryRun` loads and sends nothing and returns what would be judged and skipped, and where images would go
|
|
125
|
+
(`sendsImagesTo`).
|
|
126
|
+
- **Reproducibility.** Results come back in the order of the entries, so the same inputs publish the same layer; a
|
|
127
|
+
deterministic critic gives identical scores on every run. Atlas records what explains a result (critic, version,
|
|
128
|
+
model, rubric hash, parameters) and does not promise that a language model answers the same twice. A re-run adds a
|
|
129
|
+
newer layer instead of changing an old one.
|
|
130
|
+
- **Images may leave the machine.** `describe().sendsImagesTo` is the thing to show a person before a run that sends
|
|
131
|
+
images to a third party, and to require explicit confirmation for.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { critiquesOfLayer, publishCritiques, runCritic } from '@crvouga/atlas/critics';
|
|
135
|
+
|
|
136
|
+
const result = await runCritic({
|
|
137
|
+
critic, entries, loadImage: (entry) => fetchImage(entry),
|
|
138
|
+
existing: layers.flatMap((layer) => critiquesOfLayer(layer).map((found) => found.critique)),
|
|
139
|
+
budget: { maxImages: 200 }, secrets: [providerKey]
|
|
140
|
+
});
|
|
141
|
+
await publishCritiques(store, { critic, outcomes: result.outcomes, recordedAtMs: Date.now() });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## State-aware judging, calibration and modes
|
|
145
|
+
|
|
146
|
+
A raw score is not absolute: a loading overlay is not a full screen.
|
|
147
|
+
|
|
148
|
+
- `relativeVerdict` reads a score against the project's scores of the **same state kind** (a percentile within the kind)
|
|
149
|
+
and against the same entry's previous image (`delta`), and goes into `verdict.relative`.
|
|
150
|
+
- `calibrate` measures a critic against labelled examples **per state kind**: sample sizes, precision, recall, the
|
|
151
|
+
threshold chosen, and `inSample`, which is true unless held-out examples were given. In-sample figures flatter a critic
|
|
152
|
+
and are labelled as such.
|
|
153
|
+
- `gateDecision` turns scores into an exit code. Modes: `report` (default: shown, always exit 0), `warn` (a warning for each
|
|
154
|
+
score below its threshold, exit 0), `gate` (exit 1 when a threshold is crossed). `gate` is **refused** (exit 2, with the
|
|
155
|
+
reasons) unless every state kind present has a calibration of this critic and version with a threshold, at least 10 bad
|
|
156
|
+
and 10 good examples, and held-out figures. An explicit threshold never waives that evidence.
|
|
157
|
+
|
|
158
|
+
## Authentication and authorization
|
|
159
|
+
|
|
160
|
+
Run critics against a server that has tokens: a server with no tokens is open and every caller is an administrator, a
|
|
161
|
+
critic included.
|
|
162
|
+
|
|
163
|
+
A token of role `critic` (`ATLAS_CRITIC_TOKEN`, or `role: "critic"` under `auth.tokens`) may do what `read` may, and
|
|
164
|
+
publish critiques, and nothing else. Through the store protocol (`PUT /api/store/…`) it may upload blobs and layers, and
|
|
165
|
+
the catalog entries that make them visible, whose `producer` is `critic.<name>` and whose parts are all of kind
|
|
166
|
+
`critique`. Anything else is `403` naming the rule it broke, and the layer is not stored. It cannot post test results,
|
|
167
|
+
start live runs, change settings or remove anything. `publish` and `admin` tokens may publish critiques too. As for every
|
|
168
|
+
token: send it as `Authorization: Bearer`, never in a URL, never over plain `http` beyond this machine.
|
|
169
|
+
|
|
170
|
+
## Credentials
|
|
171
|
+
|
|
172
|
+
Developers bring their own models and AI accounts. Configuration names a credential by **reference**
|
|
173
|
+
(`env:SOME_API_KEY`, `file:/run/secrets/key`) and never contains one, so it can be committed;
|
|
174
|
+
`assertNoLiteralCredentials` refuses a configuration with a literal key and says where, never what. Credentials are read at
|
|
175
|
+
run time (`resolveCredential`) and every message the runner records goes through a redactor (`secrets` in `runCritic`,
|
|
176
|
+
`createRedactor`) that removes them, in their URL-encoded and base64 forms too, and anything shaped like a provider key,
|
|
177
|
+
token or JWT. A credential never reaches a layer, a progress event or an error message.
|
|
178
|
+
|
|
179
|
+
## What is not here yet
|
|
180
|
+
|
|
181
|
+
This release delivers the contract, the evidence format, the runner, state-aware judging and calibration, credential
|
|
182
|
+
handling and the scoped `critic` role. Still to come, tracked in [issue 50](https://github.com/crvouga/atlas/issues/50):
|
|
183
|
+
|
|
184
|
+
- the **catalog read surface** for critics (cursor pagination, `since`, realtime events, images by hash), which builds on
|
|
185
|
+
the UI catalog of [issue 43](https://github.com/crvouga/atlas/issues/43);
|
|
186
|
+
- the **`atlas critic` command** (`list`, `setup`, `doctor`, `run`, `watch`, `review`, `calibrate`), its interactive mode
|
|
187
|
+
and its `--format json` documents;
|
|
188
|
+
- the **built-in UIClip critic** and the **LLM critic** with provider adapters;
|
|
189
|
+
- the **viewer**: a ranked "needs polish" list, finding overlays and agreement across critics. Until then critiques are
|
|
190
|
+
stored, composed and kept out of test counts, and the viewer lists only test results.
|
package/docs/extending.md
CHANGED
|
@@ -249,6 +249,7 @@ Annotation `type`: `note`, `warning`, `deprecation`, `issue`, `accessibility`, `
|
|
|
249
249
|
| A kind of file Atlas has never seen | Attach it with its media type and a `role`. It is stored and offered for download today. |
|
|
250
250
|
| Structured data of your own | Put it in `extensions` under a name you own. It is kept, and shown as raw data. |
|
|
251
251
|
| A new annotation type, role or step kind | Use it. Open vocabularies need no registration. |
|
|
252
|
+
| An automated reviewer's opinion of a captured UI | A [critique](critics.md): a part of kind `critique` with the `atlas.critique` extension. It is never counted and never gates. |
|
|
252
253
|
| Something that should be shown well | Ask for a renderer (below). |
|
|
253
254
|
| A field every tool should have | Open an issue. It is added as an optional field, and the rules above guarantee nothing breaks. |
|
|
254
255
|
|