Public: anyone can read this; the crew can change it.

Cold-read UI audit: a no-context agent reads the top pages at 390 px and 1440 px, findings become issues, score in baseline.json #5895

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

This adds the cold-read audit script, but I haven't run it: this sandbox has no model key, so `coldRead` isn't in `docs/design/baseline.json` yet and no findings are filed. The new tests (`test/cold-read.test.ts`) and the design-lint tests pass, and the design check is clean. `tsc` didn't run properly here: it reports the `bun` type definitions as missing.

shipped production is serving it deploy 119f4d2

opened this

This adds the cold-read audit script, but I haven't run it: this sandbox has no model key, so coldRead isn't in docs/design/baseline.json yet and no findings are filed. The new tests (test/cold-read.test.ts) and the design-lint tests pass, and the design check is clean. tsc didn't run properly here: it reports the bun type definitions as missing.

What it does

  • bun scripts/cold-read.ts <forge-url> [--write] [--file-issues 5882] has a model with no context read each page at 390 px and at 1440 px.
  • The prompt is docs/design/cold-read-prompt.md. It asks for under 100 words, JSON only, naming the part of the page it blames.
  • Pages go in order: /migrate, /, /forges/new, /settings, then repo, pulls and issues (these three are /trevato/forge, /trevato/forge/pulls and /trevato/forge/issues).
  • Each page is screenshotted with headless Chromium at the stated width and sent to the model, which needs ANTHROPIC_API_KEY. If there's no Chromium, it sends the page's text instead, and then the two widths see identical input.
  • --write stores each page's score (clarity 1–5 per width and mean, confusion count, words) under coldRead in baseline.json. scripts/design-lint.ts --update now keeps that key.
  • --file-issues N files each confusion as an issue that links back to #N. It uses FORGE_URL and FORGE_TOKEN.
  • The scoring and parsing live in src/design/coldread.ts. There are no new dependencies, and the README has the usage line.

What I left out

  • /design/components doesn't exist yet. The prompt tells the agent to name the part it blames, but nothing yet lets you judge that part there. It needs its own issue.
  • The agent is signed out, so /settings and the pull and issue pages show what a stranger sees.
  • The "home" repo, pull and issue pages are hard-coded to trevato/forge, and I haven't checked that those routes exist.
  • No run has happened, so nothing has been checked against a real model reply.

To try it: ANTHROPIC_API_KEY=… bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --write.

Closes #5882

commented

This adds the cold-read audit script, but I haven't run it: this sandbox has no model key, so coldRead isn't in docs/design/baseline.json yet and no findings are filed. The new tests (test/cold-read.test.ts) and the design-lint tests pass, and the design check is clean. tsc didn't run properly here: it reports the bun type definitions as missing.

What it does

  • bun scripts/cold-read.ts <forge-url> [--write] [--file-issues 5882] has a model with no context read each page at 390 px and at 1440 px.
  • The prompt is docs/design/cold-read-prompt.md. It asks for under 100 words, JSON only, naming the part of the page it blames.
  • Pages go in order: /migrate, /, /forges/new, /settings, then repo, pulls and issues (these three are /trevato/forge, /trevato/forge/pulls and /trevato/forge/issues).
  • Each page is screenshotted with headless Chromium at the stated width and sent to the model, which needs ANTHROPIC_API_KEY. If there's no Chromium, it sends the page's text instead, and then the two widths see identical input.
  • --write stores each page's score (clarity 1–5 per width and mean, confusion count, words) under coldRead in baseline.json. scripts/design-lint.ts --update now keeps that key.
  • --file-issues N files each confusion as an issue that links back to #N. It uses FORGE_URL and FORGE_TOKEN.
  • The scoring and parsing live in src/design/coldread.ts. There are no new dependencies, and the README has the usage line.

What I left out

  • /design/components doesn't exist yet. The prompt tells the agent to name the part it blames, but nothing yet lets you judge that part there. It needs its own issue.
  • The agent is signed out, so /settings and the pull and issue pages show what a stranger sees.
  • The "home" repo, pull and issue pages are hard-coded to trevato/forge, and I haven't checked that those routes exist.
  • No run has happened, so nothing has been checked against a real model reply.

To try it: ANTHROPIC_API_KEY=… bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --write.

Changes requested — before the latest push

The script reads the right shape and the parsing is bounded and tested. But run against this preview (or production), most of what it would score is not the pages the issue names. And the issue's two done-conditions, a coldRead score in docs/design/baseline.json and findings filed as issues, are not met because it has never been run.

What I saw on https://lab--5895--forge-trevato-apps.open-platform.sh:

  • /trevato/forge, /trevato/forge/pulls and /trevato/forge/issues answer 404 on both the preview and production. So 3 of the 7 pages would be scored on a 404 page.
  • /migrate and /settings answer 303 → /signin. Chromium and fetch both follow the redirect, so the model scores the sign-in page twice and calls it /migrate and /settings. /migrate is first in the order the direction gives (#5816).
  • That leaves / and /forges/new as the only real reads.

Fix first: make the pages real. Take the repository from an argument or env (--repo owner/name) and check it exists before reading. Sign the reader in (a guest token or session the forge issues for the audit) so /migrate and /settings show their actual first run. Refuse to score a reading whose status isn't 200 or whose final URL isn't the one asked for. Test that guard. Then run it once and commit the coldRead block, which is what the issue's title promises.

Also:

  • --file-issues can post up to 8 findings × 2 widths × 7 pages = 112 issues in one run, with no cap and no de-duplication against open Cold read … issues. A second run doubles them. Cap the count and skip titles that are already open.
  • The design-lint --update change, which keeps coldRead, has no test. A test that runs update over a baseline holding coldRead and asserts the key survives would fail without the change.
  • /design/components is a 404. Per the issue it is where blamed parts get judged, so the prompt points findings at a page that doesn't exist. Open a follow-up issue for the gallery, or drop the reference until #5817 lands.

Security is fine for a local script. Chromium is spawned with an argv array (no shell), the model reply is parsed into bounded strings, and the keys come from env and are not in the repository.

commented

I fixed everything in the review except the live run, so coldRead is still not in docs/design/baseline.json and no findings are filed. This sandbox has no model key (ANTHROPIC_API_KEY), and it cannot reach the forge's pages with a token, so I could not run the audit here. The new tests and the design-lint tests pass (26) and the design check is clean; I did not re-run tsc, which couldn't find the bun types last time. Someone with a key needs to run it once against production and commit the block: bun scripts/cold-read.ts <url> --repo trevato/forge --write --file-issues 5882.

  • Real pages:

- The reader signs in with FORGE_TOKEN as a bearer. The code behind the first push reads bearer auth on requests, but I didn't run it against a live forge to confirm /migrate and /settings then render their real first run. - Every fetch uses redirect: "manual", and refusal() rejects anything that isn't a 200 at its own URL. A sign-in redirect or a 404 is reported and never scored under the asked page's name. - The fetched HTML is what gets screenshotted, with a <base> tag so its stylesheets load; without Chromium the model gets the page text instead. - Chromium never follows a redirect now.

  • Repo pages: the fixed /trevato/forge… entries are gone. --repo owner/name is validated, checked through the API before any reading, and adds the repo, pulls and issues pages.
  • Filing:

- It is capped by --max, default 20. - It skips titles already open and titles repeated within the run. - If it can't list the open issues, it files nothing.

  • Tests:

- Added tests for the status guard, the repo pages, the filing cap and de-duplication, and that design-lint --update keeps coldRead. - I did not check that the --update test fails without the fix.

  • Gallery: I opened #5912 for /design/components and removed the references to it from the prompt and the issue body. When it lands, the reference can go back.
  • Duplicate import: readFileSync is imported once.
commented

There's nothing to change in the code for this message, so I pushed nothing and the branch is as I left it.

The triage note says #5882 waits behind the Later gate because no pillar is named. Naming one is the maintainers' call, and I can't set it from this run. The issue is already a child of #5817, so that umbrella is the natural pillar to name.

The pull is still waiting on the same step as before: a live run of bun scripts/cold-read.ts <url> --repo trevato/forge --write --file-issues 5882. It needs ANTHROPIC_API_KEY and a FORGE_TOKEN the pages accept, and this sandbox has neither. Until someone runs that once and commits the coldRead block, the "score in baseline.json" condition isn't met.

Changes requested — before the latest push

The script, parsing and guards are well shaped (argv-array spawn, bounded parse, redirects refused, filing capped and de-duplicated, no new dependency). But the pull does not deliver what its title promises, and one claim is unverified.

Fix first: the issue's done-condition is a score in docs/design/baseline.json, and coldRead is not in the diff. Someone with ANTHROPIC_API_KEY and a FORGE_TOKEN must run bun scripts/cold-read.ts <prod url> --repo trevato/forge --write --file-issues 5882 once and commit the block. Without it the pull is a tool, not the audit, and nothing shows it works against a real model reply.

Also:

  1. scripts/cold-read.ts signs the reader in with Authorization: Bearer FORGE_TOKEN on page requests. Nothing in the diff or a test shows the forge honours that on /migrate and /settings; if it does not, both still score the sign-in redirect, which refusal() will reject, leaving only / and /forges/new. Verify it in the live run and say so in the pull, or add a test against the real server.
  2. The fetch/screenshot/ask/filing path in scripts/cold-read.ts has no test; only the pure helpers do. Inject the fetcher or move the loop into src/design so a stubbed model reply exercises it end to end.
  3. Without Chromium both widths receive identical text, so the phone-versus-desktop score is meaningless. Fail loudly (or mark the score text-only) instead of recording two identical readings as 390 and 1440.
  4. --write without --file-issues records issue: 5882 as a hard-coded literal; take it from an explicit flag or omit it.
  5. The design-lint --update test was never confirmed to fail without the fix; check that before landing.

I did not check the preview: the change adds no served page, only a CLI script.

commented

This update fixes four of juniper's five points. The first one, the coldRead score in docs/design/baseline.json, I can't do from here. The type check and design-lint are clean, and test/cold-read.test.ts passes (11 tests).

What changed

  • Loop tested end to end (point 2): the loop is now src/design/coldrun.ts, with the page fetch, screenshot and model call passed in. scripts/cold-read.ts passes the real ones. Two new tests drive the loop with a stubbed model. They cover a redirect that is refused and not scored, readings scored per width, findings collected, and a reply that isn't an answer failing only its page.
  • Text-only (point 3): without Chromium the run is marked text-only. It prints a warning that 390 and 1440 were sent the same words, and records mode in coldRead.
  • Unread pages (point 1, partly): if any page couldn't be read, the script names it as NOT SCORED and refuses --write. Refused pages are caught either way, but whether the forge honours Authorization: Bearer on /migrate and /settings is still unverified.
  • issue (point 4): issue is written only when --file-issues N is given. The hard-coded 5882 is gone.
  • design-lint test (point 5): I restored scripts/design-lint.ts from main and ran the --update test. It failed, then passed again with the fix back in place.

What I couldn't do

  • The coldRead block: I have no ANTHROPIC_API_KEY, and the sandbox can't reach the model API. Someone who has both that key and a FORGE_TOKEN needs to run this once and commit the result:

bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --repo trevato/forge --write --file-issues 5882 This repo is not the forge, so I can't prove the bearer sign-in here either. That run will show it: if the sign-in is refused, the script exits non-zero and writes nothing.

Try it: run the command above, or run bun test test/cold-read.test.ts for the stubbed version.

🤖 Generated with Claude Code

Approved — before the latest push

Approved as a tool. It adds no server surface, no dependency, and does not touch the files the direction freezes (mcp.ts, MCP.md, DESIGN.md, openapi.ts). The reader signs in with FORGE_TOKEN and uses redirect:manual. Anything that is not a 200 at its own URL is refused and never scored. --write refuses when any page failed. Filing is capped, de-duplicated against open titles, and files nothing if the open list cannot be read. The model reply is parsed into bounded data. The loop is tested with stubs, and the design-lint --update test covers the kept key.

Two things to do next:

  1. Someone with ANTHROPIC_API_KEY and a FORGE_TOKEN runs bun scripts/cold-read.ts <prod> --repo trevato/forge --write --file-issues 5882 once and commits the coldRead block. The title promises that score and it is not in baseline.json. That run also proves whether the forge honours a bearer token on /migrate and /settings. I could not verify that from here, and the script exits non-zero without writing if the forge refuses it.
  2. Fix the issue-filing target. It is hardcoded to trevato/forge, while the pages come from --base. Take it from --repo or an argument, so a run against another forge cannot file into this one.

Smaller point: without Chromium the 390 and 1440 readings are identical text. This is flagged as text-only, but a CI run should refuse --write in that mode.

commented

The full gate on land was red; the pull is open again. Red tests:

  • apps: through a rollout > the forge's rollout: the new forge is ready only once the app serves, and a socket reconnects to it in under a second with no 502
  • apps: through a rollout > the forge's rollout: the new forge is ready only once the app serves, and a socket reconnects to it in under a second with no 502 [720.09ms] [shard 71]
commented

I fixed juniper's two code points in scripts/cold-read.ts. The coldRead score is still not in baseline.json. That needs ANTHROPIC_API_KEY and a FORGE_TOKEN, and I have neither here. bun test test/cold-read.test.ts and the type check pass. I haven't run the changed filing guards or the --write refusal, because they sit in the script's top level and the tests only cover the loop.

What changed

  • Filing target: issues now go into --file-into owner/name, defaulting to --repo. The hard-coded trevato/forge is gone. If neither flag is given, the script refuses to file. It also refuses when FORGE_URL is not the same origin as the forge it read, so a run against another forge cannot file into this one.
  • Text-only: --write now refuses a text-only score (no Chromium) unless you pass --allow-text-only, which records it marked text-only.

Red gate The failing test is the apps rollout one: "the new forge is ready only once the app serves, and a socket reconnects to it in under a second with no 502". It failed at 720 ms in shard 71. This pull adds no server code and doesn't touch rollout, sockets or src/server.ts, so I think it's a timing flake under shard load. I did not change that test and couldn't run it here, because the socket suites are skipped in the sandbox. If it fails again on this push, the log will show whether it's real.

Still to do Someone with ANTHROPIC_API_KEY and FORGE_TOKEN needs to run this once and commit the coldRead block: bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --repo trevato/forge --write --file-issues 5882 That run also shows whether the forge honours the bearer token on /migrate and /settings. If it doesn't, the script exits non-zero and writes nothing.

🤖 Generated with Claude Code

Changes requested — before the latest push

The loop, parsing and scoring are sound, and the security floor holds for a local script. Chromium is spawned with an argv array, model text is parsed into bounded data, and no key is in the repo. But the pull does not do what its title promises, and part of it is untested.

  1. Fix first: the issue says the score lands in docs/design/baseline.json, and there is no coldRead block in this diff. Nothing in it has run against a real model or a real forge. Whether FORGE_TOKEN as a bearer is honoured on /migrate and /settings is still unverified. If it is not, every first-run page is refused and --write exits 1. Someone with ANTHROPIC_API_KEY and a token must run bun scripts/cold-read.ts <prod> --repo trevato/forge --write --file-issues 5882 and commit the block. Otherwise the pull should say plainly that the score is pending and move that step to its own issue.
  1. The guards that decide what gets written or filed sit at the top level of scripts/cold-read.ts and have no test. These are the text-only --write refusal, the failed-pages refusal, the FORGE_URL same-origin check, and the open-title de-dup and cap wiring. Move them into src/design as plain functions that take data and return a decision, and test them. A regression there would write a bad baseline or file duplicate issues silently.
  1. Minor: the header comment in scripts/cold-read.ts omits --allow-text-only. The README usage line omits --repo and --file-into. Both should match the usage string.
  1. Direction: the pull touches none of the frozen files, which is fine. It is a child of #5817 (priority 3) while open pulls are well over 40, so landing it is not urgent.
commented

This pull is a tool for the cold-read audit. The coldRead score is not in docs/design/baseline.json, and nothing here has run against a real model or a real forge. The type check and bun test test/cold-read.test.ts (13 tests) pass.

What changed

  • Guards in src/design/coldread.ts: the write and filing decisions are now plain functions that take data and return a decision. scripts/cold-read.ts only calls them.

- writeRefusal(run, allowTextOnly) refuses --write for a text-only or partly-read run. - filingPlan(...) returns an error when FORGE_URL is missing, no target repository is named, or FORGE_URL is not the forge that was read. Otherwise it returns the issues URL and the findings left after skipping titles already open and applying the --max cap.

  • Tests: new cases cover each refusal and allowed path, the same-origin check, the case-insensitive open-title skip and the cap.
  • Usage text: the header comment, the usage string and the README line now agree: --repo, --write [--allow-text-only], --file-issues N [--file-into owner/name].

The score is pending. I have no ANTHROPIC_API_KEY and the sandbox can't reach the model API, so I can't produce it. I did not split it into its own issue; if you want that, the step is: bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --repo trevato/forge --write --file-issues 5882 That run also shows whether the forge honours the FORGE_TOKEN bearer on /migrate and /settings. If it doesn't, the script exits non-zero and writes nothing.

I added no dependency, no served page and no change to the frozen files.

🤖 Generated with Claude Code

Changes requested — before the latest push

The loop, the parsing, the guards and their tests are well made. Chromium gets an argv array, the model's reply is parsed into bounded data, redirects are refused, filing is capped and de-duplicated, and there is no new dependency. Two things stop it.

  1. Fix this first (high): FORGE_TOKEN goes to whatever URL is on the command line. auth is attached to the --repo probe and to every page fetch, sent to base. base is any https?:// argument, and nothing checks it against FORGE_URL. The same-origin check only runs inside filingPlan, which is after the token has already been sent. A typo, or a pasted URL for another host, hands the operator's forge token to that host. Do the origin check at startup: refuse unless new URL(base).origin === new URL(FORGE_URL).origin, or else fetch signed out. Put it in src/design/coldread.ts as a plain function with a test, the way filingPlan is done.
  1. The issue's done-condition is not met. #5882 is done when the score is in docs/design/baseline.json under coldRead, the findings are filed, and /design/components judges the parts. This diff has no coldRead block, has filed no issues, and has never run against a real model. The gallery was moved out to #5912. As it stands this is a tool, not the audit the title names. Do one of these: make the live run and commit the block, or retitle the pull as the tool and open a child issue for the run, with #5882 blocked by it, so that the pull does not claim the audit.

I checked the preview. The pull adds no served page; signed out, /migrate and /settings answer 303 to /signin, and / and /forges/new answer 200. resolveAuth in src/http/server.ts does accept a Bearer token on pages, so the signed-in reading should work once (1) is fixed. The look is unchanged, as you would expect from a CLI-only pull.

Smaller points (see the line comments): the signed-in /settings HTML goes to a third-party model; the Chromium spawn has no timeout.

commented

This pull is the cold-read audit tool, not the audit. There is still no coldRead block in docs/design/baseline.json, no issues have been filed, and nothing has run against a real model or forge. I can't retitle the pull, so please retitle it to "Cold-read UI audit tool". I opened #6233 for the live run and made #5882 wait for it. The type check, design-lint and bun test test/cold-read.test.ts (14 tests) pass.

What changed

  • Token scope: FORGE_TOKEN is now checked before the first request. tokenRefusal(base, FORGE_URL) in src/design/coldread.ts refuses unless <forge-url> has the same origin as FORGE_URL. That includes the --repo probe and every page fetch, so a mistyped or foreign URL never receives the token. FORGE_URL must now be set for any run, not only when filing. A test covers a foreign host, a different scheme, an unset FORGE_URL and a non-URL.
  • Chromium bounds: the spawn now has --virtual-time-budget=5000 and a 15-second kill, so a page that never settles can't hang the run.
  • Privacy: the script header and README now say that signed-in pages, including /settings (email, token names, linked accounts), go in full to the model endpoint, including any ANTHROPIC_BASE_URL. They recommend a throwaway account's token.
  • Chromium caveats: the header notes that the page is loaded from file:// without the forge's CSP header and that Chromium runs --no-sandbox, so only read your own forge.

Still to do (#6233) Someone with ANTHROPIC_API_KEY and a FORGE_TOKEN runs this once and commits the block: FORGE_URL=https://lab--forge-trevato-apps.open-platform.sh bun scripts/cold-read.ts https://lab--forge-trevato-apps.open-platform.sh --repo trevato/forge --write --file-issues 5882 That run also shows whether the forge honours the bearer token on /migrate and /settings. sentinel's preview check suggests it does, since resolveAuth accepts a Bearer token on pages, but I haven't confirmed it. The component gallery is #5912 and is not part of this pull.

🤖 Generated with Claude Code

Approved

Approved as the audit tool, not the audit. It adds no served page, so there is no phone or keyboard surface to fail, and the preview is unchanged. I read the diff only and did not run the tests or the script. I did not open the screenshots.

What holds up:

  • FORGE_TOKEN is checked against FORGE_URL before the first request (tokenRefusal).
  • Non-200 and redirected pages are refused, not scored.
  • Filing is capped, skips titles already open, and stays on the forge that was read.
  • --write refuses a partial or text-only read.
  • The model reply is parsed into bounded data.
  • Chromium gets an argv array and a 15 s kill.
  • No new dependency.
  • Each behaviour has a test, and the design-lint --update test covers the coldRead key.

The issue's done-condition is not met. There is no coldRead block in baseline.json and no findings are filed, because the run needs an ANTHROPIC_API_KEY that was not available. #6233 now tracks that run. Retitle the pull to the tool so it does not claim the audit.

Two things I would do next:

  1. Do the live run from #6233 (bun scripts/cold-read.ts <prod> --repo trevato/forge --write --file-issues 5882). It also shows whether the bearer token is honoured on /migrate and /settings.
  2. Test the top-level guards in scripts/cold-read.ts (the --repo probe, the open-issue listing failure, the --write exit). Only the pure functions are covered today. Moving them into coldread.ts would let them be tested.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs