Flowsim — plan vs. implementation flow verification
Framing
This is NOT a program simulator. It's a structured code review formatted as a narrative trace: "the plan claims X → grep/read the code → report what actually happens". LLMs are reliable at static analysis ("does this call exist", "what does this function return") when scoped to 2–3 hops. Flowsim keeps the scope tight on purpose.
Inputs
Also called inline by /sdlc Stage 5 (as its flow axis) whenever a parent plan
is available.
- Plan source: a
plans/brainstorm-<slug>.mdfile, aplans/tasks/task-N-<slug>.mdfile, or a TASKS.md row. The plan must describe at least one flow: entry point → steps → outcome. - Optional:
--max-hops N(default 3) — how many function/module jumps to follow per flow. - Optional:
--focus <module>— restrict tracing to one module (useful for large features). - Optional:
--force— ignore the prior-run cache (see Flow step 0) and re-trace every flow. - Optional signal: latest test results as corroborating evidence. Two sources, either counts:
- eval results at
<eval.features_dir>/<feature>/results.json(script/eval-runner features), and/or test.unitresults (app-package features whose coverage was routed to the project's native unit suite — see/sdlcStage 3). A passing unit test exercising a traced flow corroborates it; a failing one is a pre-existing mismatch. Flowsim degrades to mostly-grep only when neither source exists — having unit results (not just eval results) keeps the trace meaningful for the common app-code feature.
- eval results at
Flow
0. Check the prior-run cache
Before tracing, look for plans/flowsim-<feature-slug>.json from a previous run.
If it exists and --force was NOT passed:
- Load the prior flows array.
- For each prior flow with
status: "MATCH"and every step anchored to a realfile:line, check whether any of those anchor files have been modified since the cache was written (compare cache file mtime against each anchor file's mtime). - If no anchor files have changed: mark that flow as
cached-MATCHand skip re-tracing it in step 2. It carries through to the report unchanged. - If any anchor file has changed, or the flow had any non-MATCH status: re-trace from scratch in step 2.
This trims re-runs after a fix loop — flows whose code paths were not touched
by the fix do not need to be re-walked. Typical savings: 40–60% of trace work
on subsequent runs of /sdlc Stage 5 against the same feature.
If plans/flowsim-<feature-slug>.json does not exist, proceed normally — no
cache, every flow is traced fresh.
1. Extract claimed flows
From the plan, identify each distinct flow — a user/system action and its claimed path. Examples:
- "User submits order form → POST /api/orders → OrderService.create → Stripe.charge → db.orders.insert"
- "Cron runs → worker/discovery.py → fetch(source_url) → parse → upsert into
dealstable" - "User clicks 'Export' → GET /api/reports/export.csv → stream assembled from db"
List each flow as a numbered item. Stop here and ask the user to confirm if the plan is vague enough that you'd be guessing at the flows — do not invent flows that the plan didn't claim.
2. Trace each flow through the code
Skip any flow marked cached-MATCH in step 0 — its prior trace is reused as-is.
For every other flow, walk through up to --max-hops steps. At each hop, record:
- Claimed step: what the plan says happens.
- Code anchor: file path + line number + function/symbol name. Found via grep/read.
- Actual behavior: one sentence on what the code does at that anchor.
- Status:
MATCH/MISMATCH/UNCLEAR/MISSING.
Rules:
- Every anchor must be a real
file:linereference. If you can't find one, markMISSING— do not hallucinate. - Follow the actual call chain, not what the plan hopes for. If the plan says A→B→C but the code does A→D→C, report A→D→C and flag
MISMATCHat step 2. - Stop at
--max-hopseven if the chain continues. Note this as "truncated at hop N — continue manually if needed".
3. Cross-reference with evals and tests
If .claude/project.json has eval.features_dir and results exist for this feature:
- A passing eval that exercises the traced flow → note as "corroborated by eval
<name>". - A failing eval → flag as "pre-existing failure:
<test>— may indicate theMISMATCHis known". - No eval for this flow → note "no eval coverage for this flow".
Also check the test.unit / test.e2e config for tests matching the flow's surface (e.g., a POST /api/orders flow should have a route test). Don't re-run them — just note whether they exist.
4. Report
Produce a markdown block:
## Flowsim: <feature name>
### Flow 1: <one-line description>
| # | Claimed | Anchor | Actual | Status |
|---|---------|--------|--------|--------|
| 1 | User POSTs /api/orders | `api/routes/orders.py:42` `create_order()` | Matches | MATCH |
| 2 | Validates payload via OrderSchema | `api/schemas/order.py:10` `OrderSchema` | Schema exists but missing `payment_method` field | **MISMATCH** |
| 3 | OrderService.create | (MISSING) | No `OrderService` class found; inline logic in route handler | **MISMATCH** |
**Eval coverage**: `evals/orders/` has 3 fixtures, 2 pass, 1 fail (`missing-payment-method.json`).
**Test coverage**: `tests/test_orders.py` exists with 4 cases; none exercise Flow 1 end-to-end.
**Summary**: Flow 1 deviates from the plan at steps 2 and 3. Step 2 mismatch is corroborated by a failing eval. Step 3 suggests the plan's service-layer separation was not implemented.
### Flow 2: ...
Rules
- Three hops max by default. Deeper chains get unreliable; if the plan implies a 5-hop flow, split it into two flows of 3 hops each.
- Every claim needs a
file:lineanchor or an explicitMISSINGmarker. No "I think this is in the code somewhere". - Don't invent flows the plan didn't claim. If the plan is vague, say so and ask the user to clarify before tracing.
- Don't fix anything. Flowsim is read-only. Hand findings to the user (or, when running inside the pipeline, to Stage 5's fix loop).
- Cap output at ~60 lines of markdown unless there are many flows. A 200-line flowsim report is a sign the plan is too ambitious for one feature.