One agent. One full cycle: spec read, code written, quality gate, release, QA. End to end.
Methodology — spec-first, domain-driven
I don't prompt my agents.
I write a contract with them.
A domain spec. Stories that settle things instead of describing them. A dev agent bound to an architecture it can't route around. A QA agent that replays the story against the deployed app. What runs in production is what was written.
Hover a stage to isolate it. The ⓘ nodes open the real file, copyable. Drag the graph. Tap an ⓘ node to open the real file.
--- title: Story — Auto distance & duration (Google Routes API) status: ready-for-review created: 2026-06-17 fr: FR-16 # the PRD requirement this story covers epic: E4 — Calculator author: John (PM) dev_kit: ../layou-dev-kit # the architecture the dev agent re-reads FIRST depends_on: story-address-autocomplete.md --- ## Story As a **ride driver filling in a Transfer**, I want **distance and duration to fill themselves in**, so that **I stop typing them and my price matches the real trip.** ## Product decisions (settled with Youcef — 2026-06-17) | Topic | Decision | |---|---| | API | Routes API (New) — CORS-friendly from Flutter web | | Editability | HELP, never a lock: the driver corrects, we never overwrite | | Round trip | 2 calls (A→B + B→A) then sum. No naive ×2 | | API failure | fill nothing. Empty field, manual entry, no crash | ## Out of scope Cloud Function proxy · polyline/map · new Firestore field. ## Tasks T0 verify the key with curl · T1 domain (entity + usecase) T2 data (Routes API repo) · T3 calculator · T4 the 3 forms T5 price recompute · T6 tests · T7 analyze
// Flutter web renders into a <canvas>: there is no DOM to click.
// So every interactive element carries a stable
// Semantics(identifier:) → flt-semantics-identifier="nav_fab_create"
// THAT detail is what makes the app drivable by an agent.
const URL = process.env.UPDRIVE_URL || 'https://updrive-prod.web.app';
function bySemantics(page, identifier) {
return page.locator(`[flt-semantics-identifier="${identifier}"]`).first();
}
async function fillField(page, identifier, value) {
const node = bySemantics(page, identifier);
await node.waitFor({ state: 'visible', timeout: 5000 });
await node.click({ force: true }); // focus the RIGHT field
await page.keyboard.press('End');
for (let i = 0; i < 60; i++) await page.keyboard.press('Backspace');
await page.keyboard.insertText(value); // the only reliable path here
}
// The agent then replays the story, screen by screen: sign-up →
// dashboard → calculator → invoice → listing. In light AND dark.
// A screenshot at every step. The contract
A prompt evaporates.
A story gets replayed.
That's the whole difference. A story can be re-read, reviewed, tested — and six months later it still explains why the code looks the way it does. An agent handed a story has nothing left to guess. And an agent that guesses, invents.
And I don't type it myself. The PM agent grills me: it asks the questions that sting, I settle them, it writes. The story comes out of that session — which is exactly why it holds decisions instead of options. My job here isn't the writing: it's arbitrating, and signing.
status:where the story sits in the loopfr: FR-16the PRD requirement it covers — traceability runs all the way back to the specepic:the batch it belongs todev_kit: ../layou-dev-kitthe architecture the dev agent re-reads before writing a linedepends_on:what must exist first — this is what makes parallelism safe
What the story settles
- Decisions, not options. "Routes API (New), because the legacy JS SDK is unusable from Dart." The agent doesn't arbitrate: it applies.
- Out-of-scope, in writing. That's the line that stops an eager agent from refactoring half the app.
- The failure behaviour. API silent → fill nothing, keep the field editable. No crash, no silent €0.00.
- Numbered tasks. T0…T7. A story that won't split is a story that's too big.
Several independent stories → several agents in parallel. Agent A on the client picker, Agent B on persistence, at the same time.
depends_on says who waits for what. Two agents only
collide when the spec forgot to say so.
The dev agent
An architecture it can't route around
The layou-dev-kit is its own repo, sitting next to the app. The story points at it, the agent reads it before coding. These aren't tips: they're invariants. When an agent goes ×10 faster, this is the only thing keeping the code from going ×10 sideways.
- 01
presentationnever importsdata. - 02
domainimports neitherdata, norpresentation, nor Firebase, nor Flutter UI. - 03 Root
lib/= shell, router, startup, DI. Nothing else. - 04 Every external SDK is wrapped before it reaches app code.
- 05 Usecase required for writes, side effects, and business decisions.
- 06 Implementation order: domain → data → app → presentation.
- 07 UI: screen → connector → organism → molecule.
- 08 Clean Architecture from day 1. Even for an MVP. Zero throwaway code.
The QA agent
Green tests
don't prove the app works.
So the QA agent doesn't read the code: it opens a real browser on the
deployed app and replays the story, screen by screen,
like a user. The catch: a Flutter app renders into a
<canvas> — there's nothing to click. The fix is one
line of architecture: every interactive element carries a stable
Semantics(identifier:). That's what makes the app drivable
by an agent.
- Against the deployed app The E2E suite hits the production URL. Not a mock, not an emulator: what the user actually touches.
- Light AND dark Every screen, in both themes. That's where 80% of visual regressions show up.
- One suite per epic epic-1-auth, epic-4-calculator, epic-7-acceptance… plus a run-all that chains them.
- A screenshot at every step The proof is visual. I review a full journey in 30 seconds.
- What's stubbed is written down The test plan states what isn't built yet → the agent doesn't report phantom bugs.
- PASS → done. FAIL → story. A bug doesn't drift into a fuzzy backlog: it becomes a story again, with its contract.
Honestly
What this method doesn't do
It doesn't decide for me
The spec, the product trade-offs, the go/no-go: that's me. Agents execute a contract, they don't sign it.
It doesn't replace expertise
If you don't know why a non-lazy list will crawl on an old Android, no agent will volunteer that.
It doesn't forgive a fuzzy story
Vague story = vague code, ×10 faster. The bottleneck isn't the code any more: it's the writing.
It isn't waterfall
A decision lands mid-flight? The story gets corrected, so does the PRD. What's frozen is what's written — not the plan.
Want this loop on your app?
Senior Flutter/mobile, 7+ years, agent-accelerated delivery. 20 minutes to find out whether I'm the right fit.
Book a fit-call