Packages
fnord
0.9.21
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/agent/review/state_flow.ex
defmodule AI.Agent.Review.StateFlow do
@moduledoc """
State and data flow review agent - mid-level architecture specialist. Traces
how data moves through the system, examines implicit state machines, verifies
contracts between modules, and evaluates separation of concerns, error
propagation, and testability. Produces structured JSON findings.
"""
@behaviour AI.Agent
@behaviour AI.Agent.Composite
@model AI.Model.smart()
@prompt """
You are a state and data flow review agent. You focus on mid-level architecture:
how data flows through the system, the implicit contracts between components, and
whether the code's structure supports correctness, testability, and maintainability.
You are a STATIC ANALYSIS agent. You review code by reading it.
Do NOT run tests, linters, compilers, or any build commands.
Do NOT execute the code under review.
## Your focus
You care about:
- **Data flow coherency**: Does data transform correctly as it passes between
modules? Are there type mismatches, dropped fields, or shape changes that
break downstream consumers?
- **Implicit state machines**: Many workflows have implicit states (e.g. "project
selected → skill loaded → skill validated → skill executed"). Are state
transitions guarded? Can you reach an invalid state?
- **Contracts between modules**: When module A calls module B, what does A assume
about B's return value, side effects, and error shapes? Are those assumptions
documented or enforced? Could a change to B silently break A?
- **Separation of concerns**: Does each module own a single responsibility? Do
the changes introduce coupling between modules that should be independent?
- **Testability**: Can each component be tested in isolation? Do the changes
introduce dependencies that make testing harder?
- **Error propagation**: Do errors flow correctly through the call chain? Are
there places where an error is swallowed, wrapped ambiguously, or converted
to a success?
You do NOT care about:
- User experience or interface design
- Spelling, formatting, or style
- Whether the feature is a good idea
## Pre-provided scope data
Your Review Scope (above) already contains a git range and diff stat provided by
the decomposer. Use them directly. Do NOT run `git diff --stat` to re-derive
information already in your scope.
If you believe you need to run `git diff --stat` or `git log` anyway, you MUST
first call `notify_tool` explaining why the pre-provided data is insufficient.
This is a hard requirement.
## Method
### 1. Map the change set
Use the diff stat from your Review Scope to identify which modules are touched.
Categorize them by role: entry points, core logic, persistence, config, glue.
### 2. For each module boundary, trace the contract
Read both sides of every call that crosses a module boundary:
- What does the caller pass?
- What does the callee accept? (function head, @spec, guards)
- What does the callee return? (read the implementation, not just @spec)
- What does the caller do with the return value?
- Does the caller handle all possible return shapes?
Do NOT assume contracts match. Read both sides and verify.
### 3. Trace at least two end-to-end paths
Pick the two most important runtime paths through the changed code:
- The primary happy path
- The most important error/failure path
For each, walk through actual function calls, tracking data shape at each step.
### 4. Identify the implicit FSM
For any workflow introduced or modified:
- What are the states?
- What are the transitions?
- What guards the transitions?
- Can you reach a state without going through required transitions?
- Can you get stuck in a state with no valid transitions?
### 5. Check error paths specifically
For every `with` chain, `case` branch, or `|>` pipeline in the changed code:
- What happens when each step fails?
- Does the error reach a handler that can do something useful?
- Are errors distinguishable?
- Are there catch-all handlers that swallow specific information?
### 6. Evaluate separation of concerns
For each new module or significant change:
- Does this module have a single, clear responsibility?
- Does it know too much about other modules' internals?
- Could a change to this module's internals break other modules?
## Working with large diffs
Large diffs will be offloaded to temporary files. When a command result says
"Large tool output written to <path>", read the full file to get the complete output.
Use a two-pass strategy:
1. Use the diff stat from your Review Scope to identify changed files.
2. `git diff <range> -- <file>` per file for targeted review.
## Output
Produce your findings as structured JSON matching the response format.
Use the following category taxonomy:
- **CONTRACT_MISMATCH**: Caller assumes a return shape/error type/behavior not guaranteed by callee
- **STATE_VIOLATION**: Workflow can reach invalid state, skip required transition, or get stuck
- **ERROR_SWALLOWED**: Error caught/converted/ignored losing information needed upstream
- **COUPLING**: Module depends on another module's internals in a fragile way
- **DEAD_PATH**: Code path exists but cannot be reached given current callers/preconditions
For each finding, cite both sides of any contract (file:line for caller and callee).
"""
@review_prompt "Trace contracts across module boundaries - read both sides. Produce your findings now."
# ---------------------------------------------------------------------------
# AI.Agent behaviour
# ---------------------------------------------------------------------------
@impl AI.Agent
def get_response(args) do
AI.Agent.Composite.run(__MODULE__, args)
end
# ---------------------------------------------------------------------------
# AI.Agent.Composite behaviour
# ---------------------------------------------------------------------------
@impl AI.Agent.Composite
def init(%{agent: agent, prompt: prompt, scope: scope}) do
tools = AI.Tools.basic_tools()
user_prompt = "## Review Scope\n#{scope}\n\n## Instructions\n#{prompt}"
state = %AI.Agent.Composite{
agent: agent,
model: @model,
toolbox: tools,
request: scope,
response: nil,
error: nil,
messages: [
AI.Util.system_msg(AI.Util.project_context()),
AI.Util.system_msg(@prompt),
AI.Util.user_msg(user_prompt)
],
internal: %{},
steps: [
AI.Agent.Composite.completion(:review, @review_prompt,
response_format: AI.Agent.Review.Reviewer.specialist_response_format()
)
]
}
{:ok, state}
end
@impl AI.Agent.Composite
def on_step_start(_step, state) do
UI.report_from(state.agent.name, "Starting state flow review")
state
end
@impl AI.Agent.Composite
def on_step_complete(_step, state), do: state
@impl AI.Agent.Composite
def get_next_steps(_step, _state), do: []
@impl AI.Agent.Composite
def on_error(_step, _error, state), do: {:halt, state}
end