You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(lint): descend into loop bodies and every nested region in the flow rule family (#5383) (#5635)
The flow anti-pattern rules read a flow's `nodes` / `edges` flat off the top
level, so every rule in the family was blind to anything authored inside an
ADR-0031 container — a `loop` body, a `parallel` branch, a `try_catch`
try/catch. Loop bodies are where a lot of real branching lives, so this was a
large share of authorable flow metadata that no flow rule inspected.
Measured in a real app: 8 `decision` nodes carried the inert singular
`config.condition` that `flow-inert-node-condition` exists to catch, all 8
inside a `loop` body, and none were reported. The identical key on a top-level
decision fired immediately — same key, same node type, only the nesting depth
differed.
`lintFlowPatterns` now iterates `collectFlowGraphs` — the same traversal the
engine's registration pass uses, and the one `validate-expressions.ts` already
uses on the author side — and prefixes each finding's `where` with the region
scope, so a message still points at exactly one node. Findings on a flow's own
graph are unchanged byte for byte, since the top-level graph's scope is empty.
Two properties of the walk are load-bearing rather than incidental:
- Nodes and edges stay PAIRED per region. The branch-routing rules reason about
a node together with its out-edges, and a region is a self-contained
sub-graph. Flattening into one node bag plus one edge bag would break them
both ways: a nested decision's out-edges would be absent from the top-level
list so it would read as having none and be skipped, while two nodes in
different regions sharing an id (ids are unique per graph, not per flow) would
have their out-edges merged into one phantom fan-out. A regression test pins
the second case.
- A container's config is read region-STRIPPED for the recursive scans.
`collectTemplateStrings` walks config to its string leaves and a container's
config physically contains its descendants', so a nested double-brace hit was
already visible before this change — but attributed to the enclosing `loop`,
the same failure mode #4380 fixed for `validate-flow-template-paths`.
Descending without stripping would have made it a double report. It now names
the node carrying the string, still exactly once. `stripRegions` is exported
from `flow-walk.ts` rather than copied, so there is one definition of that
view.
`flow-runas-unscoped` deliberately keeps its top-level-only data-node search:
widening a build-gating rule is its own change with its own blast radius, filed
as #5633.
Verified the repo's own example apps (`app-showcase` / `app-crm` / `app-todo`,
34 flows) report zero findings before and after, and that the descent does reach
their real loop bodies — an inert condition injected into showcase's
`loop_tasks` body is caught and scoped to it.
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments