# Sprint 6a Research — trace_flow

## Codebase Research

### Parser Data Structures (gd_parser.py)

**GdScript** — root container for parsed .gd file:
```python
@dataclass
class GdScript:
    class_name: str = ""
    extends: str = ""
    signals: list[GdSignal]                    # signal definitions
    functions: list[GdFunction]                # function definitions
    variables: list[GdVariable]
    enums: list[GdEnum]
    inner_classes: list[str]
    signal_connections: list[GdSignalConnection]  # .connect() calls
    signal_emissions: list[GdSignalEmission]      # .emit() calls
    autoload_refs: list[GdAutoloadRef]            # autoload references
    preload_refs: list[GdPreloadRef]
    is_instance_valid_lines: list[int]
```

**GdSignal** — signal definition:
- `name: str`, `args: list[tuple[str, str]]`, `line: int`

**GdSignalEmission** — `.emit()` call:
- `signal_name: str`, `arg_count: int`, `line: int`

**GdSignalConnection** — `.connect()` call:
- `signal_source: str` (e.g. "self.signal_name" or "SomeNode.signal_name")
- `target: str` (callable target passed to .connect())
- `has_bind: bool`, `bind_args: str`, `line: int`
- **NOTE**: Field names differ from spec assumption — it's `signal_source` and `target`, NOT `signal_name` and `target_method`

**GdFunction** — function definition:
- `name: str`, `args: list[tuple[str, str, str]]`, `return_type: str`
- `is_static: bool`, `is_virtual: bool`, `is_private: bool`
- `line: int`, `end_line: int`

**GdAutoloadRef** — autoload singleton reference:
- `autoload_name: str`, `access: str`, `pattern: str`, `line: int`
- **NOTE**: The field is `access`, NOT `method` as assumed in spec

**Parse function**: `parse_gd(path: str, known_autoloads: list[str] | None = None) -> GdScript`

### Project Index (project_index.py)

**ProjectIndex** key fields:
- `project_root: Path`
- `scripts: list[str]` — res:// paths
- `autoloads: dict[str, str]` — autoload_name → res://path
- `script_to_scenes: dict[str, list[str]]`

**API**: `scan_project(project_root: Path, cache=None) -> ProjectIndex`

### Session API (session.py)

**GodotIQSession** — the main interface tools use:
```python
class GodotIQSession:
    def get_script(self, res_path: str) -> GdScript | None
    def resolve_file_path(self, path: str) -> str | None
    def get_all_signal_definitions(self) -> dict[str, list[str]]
    def get_all_signal_emissions(self) -> dict[str, list[str]]
    def get_all_signal_connections(self) -> dict[str, list[str]]
    def get_autoload_dependents(self, autoload_name: str) -> list[str]

    # Internal state
    self._index: ProjectIndex
    self._gd_scripts: dict[str, GdScript]  # res://path → parsed GdScript
```

### Existing Code Tools (src/godotiq/tools/code/__init__.py)

All code tools are in `__init__.py`, not separate files. Key reusable patterns:

- **_build_dependency_graph**: Uses `_find_signal_listeners()` to find cross-file signal connections
- **_build_signal_map**: Collects definitions, emissions, listeners per signal. Has `_collect_all_defined_signals()`
- **_build_impact_check**: Finds callers, emitters, listeners for change impact
- All accept a `session: GodotIQSession` parameter
- All return `dict`

### Tool Registration Pattern (server.py)

```python
@mcp.tool(
    name="godotiq_example",
    annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": False},
)
async def godotiq_example(param: str, ctx: Context = None) -> dict:
    session = ctx.request_context.lifespan_context["session"]
    if session is None:
        return {"error": "No Godot project loaded"}
    return result
```

### Flow Tool Directory

`src/godotiq/tools/flow/__init__.py` exists with docstring only:
```python
"""Flow and Debug tools — execution flow, error tracking."""
```

### Test Fixtures

**sample_project** fixtures:
- `economy_manager.gd`: 3 signals (cash_changed, insufficient_funds, transaction_recorded), emits cash_changed 2x
- `progression_manager.gd`: 3 signals (xp_gained, level_up, product_unlocked), refs EconomyManager, GameManager
- **NO cross-file signal connections** — need custom fixtures for trace_flow

**Test patterns**:
- Import internal helpers directly: `from godotiq.tools.code import _build_signal_map`
- Use `session` fixture from conftest
- Can inject fake GdScript data for edge cases

### Prompt Documentation (godot_development.md)

Tool docs organized under UNDERSTAND / EDIT / PLAY & DEBUG categories. trace_flow goes under UNDERSTAND.

---

## Web Research

### Static Call-Graph Analysis

**Core approach**: Parse source → build AST → identify declarations and calls → build directed graph edges.

**Cycle detection**: DFS with visited set + recursion stack. If DFS revisits a node on the recursion stack, a cycle is detected. Time complexity O(V+E).

**Depth limiting**: Fixed depth limit (default 10) is standard. Iterative deepening DFS is an option for unknown-depth exploration. Always track visited nodes to prevent redundant traversal.

**IDE patterns**: Call Hierarchy provides both "Calls To" (callers) and "Calls From" (callees) views. Class Hierarchy Analysis (CHA) resolves actual methods based on type hierarchy.

### Handling Indirect Calls (Signals/Events)

Signals are the primary indirect call mechanism. Detection requires:
1. Identifying registration calls (`.connect()`)
2. Tracking callback parameter types
3. Mapping registered callbacks to implementations

Multi-Layer Type Analysis (MLTA) eliminates 86-98% of false indirect-call targets vs single-layer approaches.

### GDScript Signal Patterns

**Emission syntax** (Godot 4.x): `signal_name.emit(args)` — type-safe, compiler-validated.
Legacy `emit_signal("name")` still works but deprecated.

**Connection methods**:
1. **Editor connections** — saved in .tscn files as `[connection signal="X" from="Y" to="Z" method="M"]`
2. **Code connections** — `node.signal.connect(callable)` in `_ready()`

**Signal Bus pattern**: Autoload singleton with only signal definitions. Any script can emit/connect globally. Common names: `Events`, `GameEvents`, `EventBus`.

**Organization rules**:
- Emit signals upward (toward parent/root)
- Listen to signals downward (toward children)
- Use common parent to wire sibling communication
- Event Bus for cross-branch/global communication

**Static analysis implications**:
- Must parse both .gd files (`.connect()` calls) AND .tscn files (`[connection]` entries)
- Event Bus signals need special handling (global scope, many subscribers)
- Signal chains can form cycles — need cycle detection
- GdSignalConnection.signal_source format: "self.signal_name" or "NodePath.signal_name"
- GdSignalConnection.target format: callable name (handler function)

---

## Testing Strategy

**Existing test setup**: pytest + pytest-asyncio, `asyncio_mode = "auto"`. Tests import internal helpers directly. Session fixture loads from `tests/fixtures/sample_project/`.

**Key gap**: No cross-file signal chain fixtures exist. Must create `tests/fixtures/trace_flow/` with:
- `project.godot` (minimal, with autoloads)
- `events.gd` (signal bus)
- `order_manager.gd` (emits order_accepted)
- `pipeline_controller.gd` (connects to order_accepted, calls PrinterManager)
- `printer_manager.gd` (handles reserve_printer, emits printer_reserved)

**Test patterns to follow**: Use `GodotIQSession(fixture_path).load()`, then call internal helpers with session.
