First, find out what the app is.
Every per-page feature needs a list of pages. Discovery builds it three ways at once: a crawler follows links, a static pass reads the source for routes the crawler cannot reach, and an agent walks past the walls (sign-in, an onboarding wizard, a feature flag) that stop the other two. The result is the URL map every sweep and every check runs against.

Three ways in, one map out.
Crawl
Starting from paths you give it, a crawler follows same-origin links to a depth and page budget you set. Cheap, fast, and it records which pages were reached and from where.
Read the source
When the project has a repository, the run reads its route definitions and templates for paths the crawler never saw: pages behind a form submission, admin routes, deep links only an email contains. Read-only, and optional.
Walk past the walls
An agent takes the blocked entries, signs in with the catalog's login test, completes the wizard, flips the flag, and records how it got there. That "how to reach" note is kept on the page node for every later run.
Grouping rules keep one screen one node.
Built-in normalisation folds numeric ids, UUIDs and long hashes into :id. Real apps also route on human slugs, so a project adds its own rules: a segment after a literal, a segment matching a pattern, a whole-path rewrite, query keys to drop, and for hash-routed apps a rule that makes the fragment the route. Rules preview before they apply, and a remap shows what would merge.
Every visit any test makes adds to the map: the screenshot's page URL is normalised and attached to its node with the test that reached it, so the map grows with ordinary regression runs, not only discovery.
[
{ "kind": "after_segment", "segment": "tenant", "placeholder": ":tenant" },
{ "kind": "after_segment", "segment": "client", "placeholder": ":client" },
{ "kind": "after_segment", "segment": "event", "placeholder": ":event" },
{ "kind": "query_drop", "keys": ["utm_source", "utm_medium"] }
]
/organizer/tenant/acme/client/foo-2026/event/housing-summit/housing
/organizer/tenant/globex/client/bar/event/expo/housing
→ /organizer/tenant/:tenant/client/:client/event/:event/housing/account/orders
reached: 0 visits · status: BLOCKED
requires: a signed-in customer account
how to reach: sign in at /login (catalog test "customer-login"),
then open Account → Orders
last discovery: passed · 9 visited (9 new) · 1 blocked · 1 rule addedWhere it stops today
- Discovery boots the app in an isolated environment and can take minutes on a large monorepo; the status line on the Explore screen is where to watch it.
- Source reading needs a repository the project can clone and a framework whose routes are declared in files the pass understands; a fully dynamic router is found by crawling and walking only.
- Grouping rules are per project and hand-written. A new route shape that mints tokens will fan out until a rule covers it; the token_segment rule catches most, not all.
- Clearing the map to rebuild it deletes the accumulated per-page check history with it.