# Sprint 6a — trace_flow: Trace Game Action Through Codebase

## Objective

Implement `godotiq_trace_flow` (E01) — the tool that answers "what happens when the player does X?" by tracing the complete chain of function calls and signal emissions through all files in the project.

**This is the #1 unique differentiator.** No other Godot MCP has anything like it. GDAI, tomyud1, ee0pdt — none can trace a game action through the codebase.

After this sprint: **31 MCP tools**.

---

## How It Works

trace_flow is pure Python, filesystem-only. No GDScript, no addon. It uses existing Phase 1 infrastructure:

1. **gd_parser** → extracts signals, emissions, connections, function calls, autoload refs per file
2. **project_index** → scans all .gd files, builds cross-reference maps
3. **dependency_graph / signal_map** → knows who emits what, who listens to what

### Algorithm:

```
INPUT: trigger = "accept_order" (function or signal name)

1. Find the trigger in the codebase:
   - Search all parsed .gd files for function named "accept_order"
   - OR search for signal named "accept_order"
   - Result: file + line where the trigger lives

2. Analyze the trigger function body:
   - Find all .emit() calls within the function
   - Find all direct autoload method calls (e.g. PrinterManager.reserve_printer())
   - Find all local method calls that might chain further

3. For each emitted signal:
   - Use signal_map / project_index to find all .connect() listeners
   - Each listener is a function in another file → go to step 2 for that function

4. For each autoload method call:
   - Find the autoload script file
   - Find the called function → go to step 2

5. Build the chain with:
   - file, function, action description, what it emits/calls
   - failure_points: where the chain can break (checks, conditions, error handling)

6. Stop at depth limit (default 10) or when no more outgoing connections
```

### Example on PFT:

```
trigger: "accept_order"

Step 1: Found in order_manager.gd:184 → func accept_order(order_id: String) -> bool

Step 2: Body analysis of accept_order:
  - line 190: order_accepted.emit(order_id)  → signal emission
  - line 192: emit "order_status_changed"

Step 3: Who listens to "order_accepted"?
  - game_hud.gd connects _on_order_event
  - order_panel.gd connects _on_new_order_id_event
  - pipeline_controller.gd connects _handle_new_order  ← THIS continues the chain

Step 4: pipeline_controller._handle_new_order:
  - calls PrinterManager.reserve_printer() → autoload call
  - emits Events.printer_reserved

Step 5: Continue recursively...

OUTPUT:
  flow: [{file, action, emits}, {file, action, emits}, ...]
  failure_points: [{step, if, then}, ...]
```

---

## DO NOT TOUCH:
- `godot-addon/` — no GDScript changes
- `src/godotiq/parsers/` — use them, don't modify
- `src/godotiq/resolvers/` — use them, don't modify
- `src/godotiq/tools/bridge/` — this is a filesystem tool, not a bridge tool
- Existing test files

## Files to CREATE:
- `src/godotiq/tools/flow/trace_flow.py` — the tool implementation
- `src/godotiq/tools/flow/__init__.py` — package init (if doesn't exist)
- `tests/test_tools/test_trace_flow.py` — tests

## Files to MODIFY:
- `src/godotiq/tools/bridge/__init__.py` (or `server.py` or `__main__.py`) — register the new tool
- `src/godotiq/prompts/godot_development.md` — document trace_flow

---

## Implementation

### Key: Read Existing Code First

Before writing trace_flow, Claude Code MUST read these files to understand the actual data structures:

1. `src/godotiq/parsers/gd_parser.py` — understand GdScript dataclass, signal definitions, connections, emissions
2. `src/godotiq/parsers/project_index.py` — understand ProjectIndex, how to scan and access parsed scripts
3. `src/godotiq/tools/code/dependency_graph.py` — see how it already builds dependency data
4. `src/godotiq/tools/code/signal_map.py` — see how it already maps signal wiring

trace_flow builds ON TOP of these. Don't reinvent — reuse.

### Architecture

```python
"""E01 — godotiq_trace_flow: Trace game action through the codebase.

Given a trigger (function name, signal name, or description), follows
the chain of calls and signal emissions through all project files.
Returns the complete flow with steps, files involved, and failure points.

Pure filesystem analysis — no addon needed.
"""

from __future__ import annotations
from pathlib import Path
from dataclasses import dataclass, field


@dataclass
class FlowStep:
    """One step in the flow trace."""
    file: str              # relative path to .gd file
    function: str          # function name at this step
    line: int              # line number
    action: str            # human-readable description
    emits: list[str] = field(default_factory=list)    # signals emitted
    calls: list[str] = field(default_factory=list)    # functions called

    def to_dict(self) -> dict:
        return {
            "file": self.file,
            "function": self.function,
            "line": self.line,
            "action": self.action,
            "emits": self.emits,
            "calls": self.calls,
        }


@dataclass
class FailurePoint:
    """A point where the flow can break."""
    step: int
    condition: str         # "if" clause
    consequence: str       # "then" what happens

    def to_dict(self) -> dict:
        return {"step": self.step, "if": self.condition, "then": self.consequence}


async def trace_flow(
    trigger: str = "",
    project_path: str = "",
    depth: int = 10,
    detail: str = "normal",
) -> dict:
    """Trace how a game action flows through the codebase.

    Args:
        trigger: Function name, signal name, or keyword to search for.
            Examples: "accept_order", "order_accepted", "printer_done"
        project_path: Path to Godot project root. If empty, uses session default.
        depth: Maximum trace depth (default 10). Prevents infinite loops.
        detail: "brief" (steps only), "normal" (+ failure points), "full" (+ line numbers, call details)

    Returns:
        Dict with:
        - flow: list of steps [{file, function, action, emits, calls}]
        - failure_points: list [{step, if, then}]
        - files_involved: count of unique files
        - total_steps: number of steps in the chain
    """
```

### The Core Algorithm

```python
def _trace_from_function(
    file_path: str,
    function_name: str,
    parsed_scripts: dict,      # path → GdScript (from project_index)
    signal_listeners: dict,    # signal_name → [(file, handler_func)]
    autoloads: dict,           # autoload_name → script_path
    visited: set,              # prevent cycles: set of (file, function)
    depth: int,
    max_depth: int,
) -> list[FlowStep]:
    """Recursively trace from a function through emissions and calls."""

    if depth >= max_depth:
        return []

    key = (file_path, function_name)
    if key in visited:
        return []  # cycle detected
    visited.add(key)

    steps = []
    script = parsed_scripts.get(file_path)
    if script is None:
        return []

    # Find the function
    func = None
    for f in script.functions:
        if f.name == function_name:
            func = f
            break
    if func is None:
        return []

    # Build this step
    step = FlowStep(
        file=file_path,
        function=function_name,
        line=func.line,
        action="",  # will be filled
    )

    # Find emissions within this function's line range
    for emission in script.emissions:
        if func.line <= emission.line <= (func.end_line or 99999):
            step.emits.append(emission.signal_name)

    # Find autoload calls within this function's line range
    for ref in script.autoload_refs:
        if func.line <= ref.line <= (func.end_line or 99999):
            step.calls.append(f"{ref.autoload_name}.{ref.method or '?'}")

    # Build action description
    if step.emits:
        step.action = f"Emits: {', '.join(step.emits)}"
    elif step.calls:
        step.action = f"Calls: {', '.join(step.calls)}"
    else:
        step.action = f"Internal logic in {function_name}"

    steps.append(step)

    # Follow emitted signals to their listeners
    for signal_name in step.emits:
        listeners = signal_listeners.get(signal_name, [])
        for listener_file, listener_func in listeners:
            sub_steps = _trace_from_function(
                listener_file, listener_func,
                parsed_scripts, signal_listeners, autoloads,
                visited, depth + 1, max_depth,
            )
            steps.extend(sub_steps)

    # Follow autoload calls
    for call in step.calls:
        parts = call.split(".")
        if len(parts) == 2:
            autoload_name, method_name = parts
            autoload_path = autoloads.get(autoload_name)
            if autoload_path:
                sub_steps = _trace_from_function(
                    autoload_path, method_name,
                    parsed_scripts, signal_listeners, autoloads,
                    visited, depth + 1, max_depth,
                )
                steps.extend(sub_steps)

    return steps
```

### Building signal_listeners Map

This is the key data structure. For every signal in the project, map it to all files/functions that connect to it:

```python
def _build_signal_listeners(parsed_scripts: dict) -> dict[str, list[tuple[str, str]]]:
    """Build signal_name → [(file_path, handler_function)] map."""
    listeners = {}

    for file_path, script in parsed_scripts.items():
        for conn in script.connections:
            signal_name = conn.signal_name
            handler = conn.target_method
            if signal_name not in listeners:
                listeners[signal_name] = []
            listeners[signal_name].append((file_path, handler))

    return listeners
```

### Finding the Trigger

```python
def _find_trigger(trigger: str, parsed_scripts: dict) -> list[tuple[str, str]]:
    """Find where the trigger lives. Returns [(file_path, function_name)]."""
    results = []

    for file_path, script in parsed_scripts.items():
        # Check function names
        for func in script.functions:
            if func.name == trigger or trigger in func.name:
                results.append((file_path, func.name))

        # Check signal names (if trigger matches a signal, find who emits it)
        for sig in script.signals:
            if sig.name == trigger:
                # Find functions that emit this signal
                for emission in script.emissions:
                    if emission.signal_name == trigger:
                        # Find which function contains this emission
                        containing_func = _find_containing_function(script, emission.line)
                        if containing_func:
                            results.append((file_path, containing_func))

    return results
```

### Failure Point Detection

Look for patterns in the function body that indicate branching:

```python
def _detect_failure_points(script, func, step_index: int) -> list[FailurePoint]:
    """Detect potential failure points in a function."""
    failures = []

    # Simple heuristic: look for early returns, error checks, conditions
    # in the raw source code between func.line and func.end_line
    # This is approximate — not a full code analysis

    # Pattern: "if not X: return" or "if X == null: return"
    # Pattern: "if queue.is_empty(): ..."
    # Pattern: "if not is_instance_valid(X): return"

    return failures
```

### Integration with existing tools

**IMPORTANT**: Check how `dependency_graph` and `signal_map` already build their data. trace_flow should use the SAME data source, not rebuild from scratch. Ideally:

```python
# If dependency_graph already has a function that returns parsed project data:
from ..code.dependency_graph import _get_project_data  # or similar

# Or if signal_map builds the listener map:
from ..code.signal_map import _build_listener_map  # or similar
```

Read the actual code before deciding what to import vs rebuild.

---

## Register the Tool

In the MCP tool registration:

```python
@mcp.tool("godotiq_trace_flow")
async def godotiq_trace_flow(trigger: str, project_path: str = "", depth: int = 10, detail: str = "normal") -> dict:
    """Trace how a game action flows through the entire codebase."""
    return await trace_flow(trigger=trigger, project_path=project_path, depth=depth, detail=detail)
```

---

## Tests

### `tests/test_tools/test_trace_flow.py`

Use existing fixture .gd files to test. The fixtures should have signal chains:
- File A emits signal X
- File B connects to X and emits signal Y
- File C connects to Y

```python
"""Tests for trace_flow tool."""
import pytest

# Test against fixture files that have signal chains


class TestTraceFlow:
    def test_find_trigger_by_function_name(self):
        """Given a function name, find the file and line."""
        # Use existing fixtures
        pass

    def test_find_trigger_by_signal_name(self):
        """Given a signal name, find who emits it."""
        pass

    def test_trace_single_step(self):
        """Function with no outgoing connections → 1 step."""
        pass

    def test_trace_signal_chain(self):
        """A emits → B listens → B emits → C listens."""
        pass

    def test_trace_autoload_call(self):
        """A calls EconomyManager.method → traces into economy_manager.gd."""
        pass

    def test_depth_limit(self):
        """Trace stops at depth limit."""
        pass

    def test_cycle_detection(self):
        """A → B → A doesn't infinite loop."""
        pass

    def test_trigger_not_found(self):
        """Unknown trigger returns empty flow."""
        pass

    def test_multiple_listeners(self):
        """Signal with 3 listeners → 3 branches in the flow."""
        pass

    def test_detail_brief(self):
        """brief detail returns fewer fields."""
        pass
```

**NOTE**: The tests depend on the fixture files having real signal chains. Check what fixtures exist in `tests/fixtures/` and verify they have .emit() and .connect() patterns. If the fixture files don't have cross-file signal chains (they're individual files, not a connected project), create a minimal test fixture set:

```
tests/fixtures/trace_flow/
├── project.godot         # minimal, with autoloads
├── events.gd             # signal bus with definitions
├── order_manager.gd      # emits order_accepted
├── pipeline_controller.gd # connects to order_accepted, calls PrinterManager
└── printer_manager.gd    # handles reserve_printer, emits printer_reserved
```

These can be minimal files — just enough to test the signal chain.

---

## Update Prompt

Add to `godot_development.md`:

```
**`godotiq_trace_flow`** — Trace how a game action flows through the entire codebase.
Given a trigger (function or signal name), follows the chain of calls and signal
emissions through all files. Returns the complete flow with steps and failure points.
No game needed — pure filesystem analysis.

Example: trace_flow(trigger="accept_order") → shows the complete order acceptance
pipeline across 8 files, from UI tap to payment received.

Use this BEFORE modifying any function to understand what it triggers downstream.
```

---

## Verification

1. `pytest tests/ -v` → ALL 695+ tests pass
2. New tests for trace_flow pass
3. **Live test on PFT**:
   > `trace_flow(trigger="accept_order")` should show the order pipeline
   > `trace_flow(trigger="printer_done")` should show the print completion flow
4. Import check: `python -c "from godotiq.tools.flow.trace_flow import trace_flow; print('OK')"`

---

## Summary

| File | Action | Purpose |
|------|--------|---------|
| `src/godotiq/tools/flow/__init__.py` | CREATE | Package init |
| `src/godotiq/tools/flow/trace_flow.py` | CREATE | trace_flow tool |
| `tests/test_tools/test_trace_flow.py` | CREATE | Tests |
| `tests/fixtures/trace_flow/` | CREATE | Minimal fixture project for signal chain tests |
| `src/godotiq/tools/bridge/__init__.py` or `server.py` | MODIFY | Register tool |
| `src/godotiq/prompts/godot_development.md` | MODIFY | Document tool |
