# Opus Review

**Model:** claude-opus-4-6
**Generated:** 2026-03-06T12:30:00Z

---

# Review: `godotiq_trace_flow` Implementation Plan

## 1. Critical Bug: `GdSignalConnection.target` Can Contain Lambda Expressions

The plan assumes `GdSignalConnection.target` is always a plain function name (e.g., `"_on_print_started"`). However, the existing parser captures lambda Callables verbatim. From the test at `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_parsers/test_gd_parser.py` line 434:

```python
assert conn.target.startswith("func")
```

This means a connection like `level_up.connect(func(_l, _u): fired[0] = true)` produces a target of `"func(_l, _u): fired[0] = true"`. The plan's `_build_signal_listeners` would try to look up a function named `"func(_l, _u): fired[0] = true"` in the script, which will never match any `GdFunction.name`. This is not a crash bug, but it is a **silent data loss** -- lambda-connected signal handlers will be invisible to the tracer with no warning.

**Recommendation**: Add explicit handling for lambda targets. At minimum, detect when `target.startswith("func")` and either skip with a note in the FlowStep (e.g., "connected to inline lambda, cannot trace further") or filter them out during listener map construction. Document this as a known limitation.

## 2. Critical Bug: Signal Listener Map Is Missing the Connection's Owning File Context

Section 3, "Signal Listener Map", says:

> The handler function name comes from the `target` field
> Store as `listeners[signal_name].append((script_path, target))`

This stores `script_path` as the file where the `.connect()` call appears. But when `_trace_from_function` recurses on listeners, it does `_trace_from_function(listener_file, listener_func, ...)`. This **assumes the handler function exists in the same file as the `.connect()` call**. For the event bus pattern, this is typically true: `Events.order_accepted.connect(_on_order_accepted)` -- the `_on_order_accepted` function is defined in the file that calls `.connect()`. However, Godot 4 also allows:

```gdscript
Events.order_accepted.connect(some_other_node.handler_method)
```

In this case the target would be `"some_other_node.handler_method"` and the handler does NOT live in the connecting script. The plan does not address qualified target names at all.

**Recommendation**: Add a filter in `_build_signal_listeners` to skip targets that contain a `.` (qualified references to other objects' methods), or document it as a v1 limitation. The existing code tool (`_find_signal_listeners` in `/Users/salvatorefarruggio/Desktop/godotiq/src/godotiq/tools/code/__init__.py`) has the same simplification, so this is at least consistent, but it should be explicitly called out.

## 3. Architectural Concern: Tool Registration Pattern

The plan says to register the tool in `server.py`. But looking at the current `server.py`, the existing `code` tools (`dependency_graph`, `signal_map`, `impact_check`, `validate`) all register themselves via `@mcp.tool()` decorators inside `src/godotiq/tools/code/__init__.py`, and `server.py` simply imports the module:

```python
from godotiq.tools import code  # noqa: E402, F401
```

The `flow` module is **not currently imported in server.py**. The plan says to modify `server.py` to add the tool registration there, but the existing convention is:
1. Put `@mcp.tool()` decorator in the module file (e.g., `tools/flow/__init__.py`)
2. Import the module in `server.py` to trigger registration

**Recommendation**: Follow the existing pattern. Put the `@mcp.tool()` decorator in `src/godotiq/tools/flow/__init__.py` (importing `mcp` from `godotiq.server`), and add `from godotiq.tools import flow  # noqa: E402, F401` to `server.py`.

## 4. Footgun: Autoload Path Format Mismatch

The plan's risk table mentions "Autoload res:// path in ProjectIndex doesn't match script key format" but does not specify exactly how to handle it. Looking at the actual data, both `session._index.autoloads` values and `session._gd_scripts` keys use `res://` paths, so direct lookup should work.

**Recommendation**: Remove the vague mitigation and replace with a concrete assertion: both `session._index.autoloads` values and `session._gd_scripts` keys use `res://` paths, so direct lookup `scripts[autoloads[ref.autoload_name]]` should work. Add a defensive `.get()` with None check for cases where the autoload script file does not exist or failed to parse.

## 5. Footgun: `GdAutoloadRef.access` Is Not Always a Method Name

The plan says:

> For each autoload ref, resolve the autoload name to a script path via `autoloads[ref.autoload_name]`, then recurse into `ref.access` as the function name

But `GdAutoloadRef.access` captures any member access, not just method calls. For example, `GameManager.game_time` (a property access, not a function call) would produce `access="game_time"`. The parser regex `\b(AutoloadName)\.(\w+)` cannot distinguish `PrinterManager.reserve_printer()` (function call) from `PrinterManager.game_time` (property access).

**Recommendation**: In `_trace_from_function`, when filtering autoload refs within the function's line range, read the raw source line and check if `ref.autoload_name + "." + ref.access` is followed by `(` on that line. This would reliably distinguish method calls from property accesses.

## 6. Missing Consideration: `end_line` of Zero for Last Function in File

The plan's risk table says "Default to a large number (99999) when end_line is 0." But the parser **does** set `end_line` for the last function at EOF cleanup. The `GdFunction` is initialized with `end_line=lineno`, and the parser updates it on exit. The value `0` would only occur for a `GdFunction` that was never processed by the parser.

**Recommendation**: Remove the "99999" fallback from the plan. Instead, if `func.end_line < func.line`, treat it as a degenerate case and only scan `func.line` itself. A large sentinel value would cause the tracer to attribute emissions/refs from subsequent functions to the current function.

## 7. Performance Concern: Re-reading Raw Source Files

For each function visited during tracing, `_detect_failure_points` will call `Path.read_text()` on the `.gd` file. In a project with many scripts and deep trace chains, the same file could be re-read multiple times.

**Recommendation**: Cache the file contents. Build a `dict[str, list[str]]` of `file_path -> lines` at the top level of `_build_trace_flow`, and pass it to `_trace_from_function` / `_detect_failure_points`.

## 8. Missing Test: Signal Emission Without a Containing Function

What if a signal emission occurs outside any function? The parser would still capture the `GdSignalEmission`, but `_find_containing_function` would return None.

**Recommendation**: Add a test case for emissions at top level and ensure `_find_trigger` gracefully skips emissions that cannot be attributed to a function.

## 9. Missing Test: Autoload Ref With `pattern != "direct"`

The `GdAutoloadRef` has multiple patterns: `"direct"`, `"has_node"`, `"get_node"`, `"get_node_or_null"`. Non-`"direct"` patterns have empty or path-based `access` fields. Trying to recurse into these as function names will always fail silently.

**Recommendation**: Explicitly filter autoload refs to `ref.pattern == "direct"` before attempting to trace into them. Add a test that verifies `has_node` / `get_node` refs do not produce spurious flow steps.

## 10. Ambiguity: `project_path` Parameter in Spec vs. Plan

The spec lists `project_path` as an input parameter but the plan's tool signature does not include it. None of the other tools accept a `project_path` parameter.

**Recommendation**: Drop `project_path` from the spec. Follow the plan's tool signature.

## 11. Missing Consideration: Signal Name Collisions in the Listener Map

The listener map is keyed by signal name alone. If two unrelated scripts both define a signal called `"completed"`, the tracer will incorrectly follow listeners for the wrong signal.

**Recommendation**: Document this as a known limitation. Consider a v2 enhancement where the listener map is qualified by signal source.

## 12. Shared vs. Per-Entry Visited Set

The plan uses a fresh `visited` set per entry point. If multiple entry points converge on the same downstream function, the downstream path would appear multiple times in the output.

**Recommendation**: Either use a shared visited set across all entry points, or document that the output may contain duplicate flow steps when multiple entry points converge. A shared visited set is probably the better default.

## 13. Test Strategy Gap: No Integration Test With Server Registration

No test verifying that the MCP tool is properly registered and callable through the server.

**Recommendation**: Add a test that imports `from godotiq.server import mcp` and verifies that `"godotiq_trace_flow"` appears in the registered tools.

## 14. Minor: `detail` Parameter Validation

No validation or error for invalid `detail` values. If someone passes `detail="verbose"`, it would silently behave like `"normal"`.

**Recommendation**: Either validate and return an error for unknown values, or document that unknown values default to `"normal"`.

## 15. Minor: Conftest Location

The plan says to create a conftest fixture in the test file itself. The existing conftest at `tests/test_tools/conftest.py` handles session loading for other tools.

**Recommendation**: Add the `trace_flow_session` fixture to the existing `tests/test_tools/conftest.py`.

## Summary of Critical Issues

1. **Lambda targets** will silently break signal chain tracing (Section 3, Signal Listener Map)
2. **Property access** vs method call ambiguity in autoload refs will produce false flow branches (Section 3, step 9)
3. **Non-"direct" autoload ref patterns** (`has_node`, `get_node`, `get_node_or_null`) will produce lookup failures (Section 3, step 5)
4. **Tool registration** should follow the existing module-level `@mcp.tool()` pattern, not be added to `server.py` directly (Section 4)

The plan is otherwise well-structured, thorough in its use of existing infrastructure, and the test matrix covers the important happy paths. The algorithm is sound for the common case (event bus + programmatic wiring). Addressing the four critical items above would make this ready for implementation.
