I now have all the information needed. Let me produce the section content.

# Section 03: Trigger Finding

## Overview

This section implements the `_find_trigger()` and `_find_containing_function()` helper functions in `src/godotiq/tools/flow/__init__.py`. These functions locate all entry points for a given trigger string across every parsed GDScript in the project. A "trigger" can be either an exact function name or an exact signal name.

**Depends on**: Section 01 (fixtures and dataclasses must exist)
**Blocks**: Section 04 (recursive tracing consumes the output of `_find_trigger`)

---

## Background

The trace_flow tool needs to answer: "given a trigger string, where does execution start?" There are two search modes:

1. **Function name match**: Scan all `GdScript` objects. If any `GdFunction.name` equals the trigger exactly, that `(file_path, function_name)` pair is an entry point.

2. **Signal name match**: Scan all `GdScript` objects. If any `GdSignal.name` equals the trigger exactly, find the functions that *emit* that signal. This requires cross-referencing `GdSignalEmission` objects (which carry a `.line` number) against function line ranges (`GdFunction.line` to `GdFunction.end_line`).

Both modes run in a single pass. All matches across all files are returned (no disambiguation).

### Key Data Structures (from `gd_parser.py`)

These are defined in `src/godotiq/parsers/gd_parser.py`:

```python
@dataclass
class GdFunction:
    name: str = ""
    args: list[tuple[str, str, str]] = field(default_factory=list)
    return_type: str = ""
    is_static: bool = False
    is_virtual: bool = False
    is_private: bool = False
    line: int = 0
    end_line: int = 0

@dataclass
class GdSignal:
    name: str = ""
    args: list[tuple[str, str]] = field(default_factory=list)
    line: int = 0

@dataclass
class GdSignalEmission:
    signal_name: str = ""
    arg_count: int = 0
    line: int = 0

@dataclass
class GdScript:
    signals: list[GdSignal] = field(default_factory=list)
    functions: list[GdFunction] = field(default_factory=list)
    signal_emissions: list[GdSignalEmission] = field(default_factory=list)
    # ... other fields omitted
```

Scripts are stored in `session._gd_scripts` as `dict[str, GdScript]` keyed by `res://` paths.

---

## Tests

Write these tests in `tests/test_tools/test_trace_flow.py`. They use the `trace_flow_session` fixture from Section 01 (loaded from `tests/fixtures/trace_flow/`). Import the helpers directly:

```python
from godotiq.tools.flow import _find_trigger, _find_containing_function
```

### test_find_trigger_by_function_name

Call `_find_trigger("accept_order", session._gd_scripts)`. Assert the result contains a tuple where the second element is `"accept_order"` and the first element is the `res://` path to `order_manager.gd`.

### test_find_trigger_by_signal_name

Call `_find_trigger("order_accepted", session._gd_scripts)`. The fixture `order_manager.gd` defines `accept_order()` which emits `order_accepted`. Assert the result contains a tuple `(res://path/to/order_manager.gd, "accept_order")` -- the function that emits the signal, not the signal declaration itself.

### test_find_trigger_returns_empty_for_unknown

Call `_find_trigger("nonexistent_thing", session._gd_scripts)`. Assert the result is an empty list.

### test_find_trigger_multiple_entries

When the trigger name exists as a function in multiple files, all matches are returned. This can be tested by constructing two minimal `GdScript` objects in-memory (no fixture needed) that both contain a function named `"reset"`, placing them in a dict, and verifying `_find_trigger("reset", scripts)` returns two entries.

### test_find_containing_function_returns_correct_function

Construct a `GdScript` with two functions: `func_a` at lines 5-10 and `func_b` at lines 12-20. Call `_find_containing_function(script, 7)`. Assert it returns `func_a`. Call with line 15. Assert it returns `func_b`.

### test_find_containing_function_returns_none_outside_range

Construct a `GdScript` with a single function at lines 5-10. Call `_find_containing_function(script, 3)`. Assert it returns `None`.

---

## Implementation

All code goes in `src/godotiq/tools/flow/__init__.py`, alongside the dataclasses from Section 01.

### `_find_containing_function`

```python
def _find_containing_function(script: GdScript, line: int) -> GdFunction | None:
    """Return the GdFunction whose line range contains the given line number, or None."""
```

Algorithm:
- Iterate `script.functions`.
- For each function, check `func.line <= line <= func.end_line`.
- Handle degenerate case: if `func.end_line < func.line`, only match if `line == func.line`.
- Return the first match, or `None`.

### `_find_trigger`

```python
def _find_trigger(trigger: str, scripts: dict[str, GdScript]) -> list[tuple[str, str]]:
    """Find all (file_path, function_name) pairs matching the trigger exactly.

    Two search modes run in one pass over all scripts:
    1. Function name match: any GdFunction.name == trigger
    2. Signal name match: any GdSignal.name == trigger, then find the
       function(s) that emit that signal via GdSignalEmission line attribution.

    Returns a deduplicated list of (res:// path, function name) tuples.
    """
```

Algorithm:
1. Initialize `results: list[tuple[str, str]] = []` and a `seen: set[tuple[str, str]] = set()` for deduplication.
2. For each `(file_path, script)` in `scripts.items()`:
   - **Function match**: For each `func` in `script.functions`, if `func.name == trigger`, add `(file_path, func.name)` to results (if not already in `seen`).
   - **Signal match**: For each `sig` in `script.signals`, if `sig.name == trigger`, then scan `script.signal_emissions` for emissions where `emission.signal_name == trigger`. For each matching emission, call `_find_containing_function(script, emission.line)`. If a containing function is found, add `(file_path, containing_func.name)` to results (if not already in `seen`).
3. Return `results`.

**Important detail**: The deduplication via `seen` prevents the same `(file, function)` pair from appearing twice when a function both matches by name AND emits a signal that matches by name.

**Important detail**: Signal emissions reference the signal by its bare name (the `signal_name` field of `GdSignalEmission`), which matches `GdSignal.name` directly. No prefix stripping is needed here (unlike `GdSignalConnection.signal_source` which may be qualified like `"Events.order_accepted"`).

---

## File Summary

| File | Action |
|------|--------|
| `src/godotiq/tools/flow/__init__.py` | Add `_find_containing_function()` and `_find_trigger()` |
| `tests/test_tools/test_trace_flow.py` | Add 6 trigger-finding tests |

---

## Verification

After implementing:

1. Run `pytest tests/test_tools/test_trace_flow.py -k "find_trigger or find_containing"` -- all 6 new tests pass.
2. Run `pytest` -- all existing 695+ tests still pass (no regressions).
3. Import check: `from godotiq.tools.flow import _find_trigger, _find_containing_function` succeeds.