Skip to Content
v1.33.8 · shipped One-screen report · Looks like your app Read the changelog →
Admin consoleUser stories
Positive-side contract — gates fail when drafts diverge · · open live demo ↗

User stories · Inventory

Route: /inventory (sidebar: Checks › User stories)

The User stories page in Mushi Mushi is the v2 positive side of the loop. Where Reports / Graph / Fixes catch what your users felt break, the inventory describes what should exist — every page, every action, every expected outcome — so the gates can fail when the two diverge.

User stories needs a plan that includes it. On such a plan it is listed under Checks in the sidebar; otherwise its card on Checks shows an Upgrade pill.

What you see

With no map yet, the page is one step: Turn on discovery (it opens the Discovery tab with the SDK snippet). Writing the map file by hand is the other way in.

Once the map exists:

  1. Status sentence across the top: how many actions are broken (stopped working, or no code behind them yet) and how many a test proves, with Check every action and Crawl the app.
  2. Your app, page by page: one card per page with what a user can do on it. Broken actions are red and sort first; pages with a broken action come first.
  3. Story cards: one per user story (signup, pricing, …) with per-story counts and a ⋯ menu for runs scoped to that story.
  4. Action drawer: click any action to inspect its expected_outcome, the last walk and its history.

The tabs are User stories, All actions (the page × element table), Code checks (open findings, ?tab=gates), Live probes (?tab=synthetic), Out of sync (?tab=drift), Discovery and Map file (?tab=yaml). The ?tab= ids are unchanged, so old links work.

Check every action / Crawl the app

The status line has one Check every action and one Crawl the app for the whole project. They call the same server routes the GitHub Action uses (@mushi-mushi/mcp-ci):

  • Check every action runs the server-side checks: status claims, API contract, unused backend endpoints and calls to unknown endpoints. Results show on the Code checks tab. It posts nothing to a pull request: the lint checks (dead handlers, mock data) need your ESLint report, which only the GitHub Action sends.
  • Crawl the app crawls every page in the map and reconciles it with inventory.yaml. Differences land in the Out of sync tab.

To run just one story, open the ⋯ menu on its card:

  • Crawl this story’s pages — crawls only the pages linked to that story.
  • Run gates on this story — runs the gates on that story’s actions only.

SDK-driven discovery

Most teams will never hand-author inventory.yaml. Turn on capture.discoverInventory in the SDK config:

import { MushiProvider } from '@mushi-mushi/react' <MushiProvider config={{ projectId: '…', apiKey: '…', capture: { discoverInventory: true }, }}> <App /> </MushiProvider>

The SDK quietly observes routes, data-testids, and outbound API paths in production. Claude drafts a candidate inventory.yaml from the stream every 24 h and the Discovery tab surfaces the diff for review. You accept, edit, or skip — the production payload itself never contains user data.

Surface mode in the graph

The same inventory powers a Surface toggle on the Knowledge graph — every Page / Element / Action node overlaid on the live bug graph so the dead corners (high-traffic elements with no expected_outcome) light up.

The gates

The GitHub Action rolls these into one composite check. The Code checks tab lists their findings under the names in the first column:

Gate (console name)IdWhat it checks
Dead handlersdead_handlerESLint mushi-mushi/no-dead-handler: buttons and links whose handler does nothing. Needs your ESLint report from CI
Mock data in productionmock_leakESLint mushi-mushi/no-mock-leak: faker or placeholder data in non-test paths. Needs your ESLint report from CI
API contractapi_contractFrontend calls whose request or response does not match the API (server side)
Live route crawlcrawlEvery page in the map loaded live; runs through Crawl the app, not as part of the composite check
Status claimsstatus_claimActions marked verified or wired in the map whose derived status disagrees
Unused backend endpointsorphan_endpointBackend endpoints nothing calls
Calls to unknown endpointsunknown_callFrontend calls to endpoints the backend does not have

Watch your live site

Discovery → Watch your live site asks Firecrawl to crawl your live site once a day, following your site’s own links from the address you enter. Mushi Mushi files a bug report, before a user finds the problem, for each page that:

  • returns a server error (5xx);
  • returns a 404 or another 4xx from a link on your own site (sign-in walls and rate limits don’t count);
  • fails to load;
  • changed in a way Firecrawl’s checker calls broken: an error message, a blank page, or missing main content. Normal wording and layout edits are ignored.

Pages your robots.txt blocks, links that only differ by ?utm_… parameters, and pages you removed are not reported.

A page has to fail twice before it becomes a report. When a check finds a broken page, Mushi Mushi loads that page again, waits about 10 seconds for it to finish loading, and files only if it is still broken. A page caught mid-load (a loading screen, only a “Skip to content” link, a status line waiting on a request) is not a broken page. A server error or a page that won’t load also files when two checks in a row see it. If your whole site is down, Mushi Mushi files one report within the hour. Two cases file at low severity: a page Firecrawl’s checker keeps calling broken that Mushi Mushi can’t load again to confirm (marked “unconfirmed”), and a server error that keeps coming back but loads fine each time Mushi Mushi looks again (3 times in 7 days, marked “intermittent”). These second loads use your app’s own Firecrawl key: at most 6 per hourly read, shared across your apps, 1 credit each.

Each broken page files one report. When 5 or more pages break at once (the site is down, or a deploy broke a shared layout), they file one report that lists them all. While a page stays broken, Mushi Mushi doesn’t file again. When it is no longer broken, it leaves the “Broken right now” list; its report stays as it is for you to close. If it breaks again later, it has to fail twice again before it files.

Check now runs a check straight away, and results show in 1 to 3 minutes. Each app can run 6 checks an hour this way; the daily check always runs.

The watch uses your Firecrawl key, or a key shared with all your apps. It costs 1 credit per page per day, about 750 a month for 25 pages. Firecrawl’s own estimate is up to twice that, because it holds back 1 credit per page for checking changed pages. Turn off deletes the crawl in Firecrawl and needs the key to do so; reports already filed stay. Only public pages are checked; flows behind sign-in belong in QA stories.

The crawler sends MushiSiteWatch in its User-Agent header (MushiSiteWatch/1.0 (+https://kensaur.us/mushi-mushi), after a normal Chrome user agent). Filter that string out of Sentry and your analytics so the daily check doesn’t count as visitors or errors, for example by dropping events in Sentry’s beforeSend when the request’s user agent contains MushiSiteWatch. The header is on the HTTP request; don’t rely on navigator.userAgent showing it. A watch turned on before this header was added starts sending it the next time you save it or turn it off and on.

Synthetic monitor

Mushi Mushi re-walks the inventory’s expected_outcome checks against your staging URL every 15 minutes once you enable it. The monitor is fail-closed by default: write paths (POST / PUT / DELETE actions) are skipped unless you turn on Allow mutations for the project in Map file → Crawler & probes (the synthetic_monitor_allow_mutations project setting; it is not an inventory.yaml key).

Set this only when staging is fully isolated from production and the write endpoints have an idempotency key or roll back cleanly.

Spec traceability — every fix carries the originating Action

As of the 2026-05-09 release every fix dispatched against a report that has been linked to an inventory Action (via the reports_against graph edge classify-report writes) carries that link end-to-end:

  • The dispatch row (fix_dispatch_jobs.inventory_action_node_id) and the resulting attempt (fix_attempts.inventory_action_node_id) both store the FK to the graph_nodes row for the originating Action.
  • The fix-worker LLM prompt includes a Markdown spec-context block with the action description, page, story, and every assertion in the contract — see Concepts → Spec traceability — read AND write side.
  • A deterministic validateAgainstSpec gate runs before the PR opens. Soft warnings (no changed file references the contract’s required DB table or page route) land on fix_attempts.spec_validation_warnings JSONB and surface in the fix’s detail panel under Spec validation warnings.
  • A targeted post-PR synthetic probe is queued for the originating Action immediately after the PR opens. The synthetic-monitor cron drains the queue with priority on its next tick — so a fix that immediately makes the action regressed shows up here within minutes of merge, not on the next 15-min reconciler sweep.

External AI agents (Cursor, Claude Code, OpenAI Agents SDK, LangGraph, CrewAI, A2A v1.0.0) get the same anchor — see Connecting your AI agent for the per-surface details.

See also

Last updated on