diff --git a/src/godotiq/tools/code/__init__.py b/src/godotiq/tools/code/__init__.py
index 6af0b8d..d81ae7c 100644
--- a/src/godotiq/tools/code/__init__.py
+++ b/src/godotiq/tools/code/__init__.py
@@ -1 +1,291 @@
-"""Code Intelligence tools — signals, dependencies, conventions."""
+"""Code Intelligence tools -- dependency graph, signal tracing.
+
+These tools help AI agents understand code relationships and impact.
+"""
+
+from __future__ import annotations
+
+from mcp.server.fastmcp import Context
+
+from godotiq.parsers.gd_parser import GdScript
+from godotiq.server import mcp
+from godotiq.session import GodotIQSession
+
+
+def _compute_impact_rating(
+    is_autoload: bool,
+    referenced_by_count: int,
+    signal_listener_count: int,
+    scene_usage_count: int,
+) -> tuple[str, str]:
+    """Compute impact rating and explanation."""
+    score = 0
+    reasons: list[str] = []
+
+    if is_autoload:
+        score += 3
+        reasons.append("autoload singleton (global)")
+
+    if referenced_by_count >= 5:
+        score += 3
+        reasons.append(f"referenced by {referenced_by_count} scripts")
+    elif referenced_by_count >= 2:
+        score += 2
+        reasons.append(f"referenced by {referenced_by_count} scripts")
+    elif referenced_by_count >= 1:
+        score += 1
+        reasons.append(f"referenced by {referenced_by_count} script")
+
+    if signal_listener_count >= 3:
+        score += 2
+        reasons.append(f"{signal_listener_count} signal listeners")
+    elif signal_listener_count >= 1:
+        score += 1
+        reasons.append(f"{signal_listener_count} signal listener(s)")
+
+    if scene_usage_count >= 3:
+        score += 1
+        reasons.append(f"used in {scene_usage_count} scenes")
+
+    if score >= 6:
+        rating = "critical"
+    elif score >= 4:
+        rating = "high"
+    elif score >= 2:
+        rating = "medium"
+    else:
+        rating = "low"
+
+    return rating, "; ".join(reasons) if reasons else "isolated file"
+
+
+def _find_signal_listeners(
+    session: GodotIQSession, gd: GdScript, res_path: str
+) -> list[dict]:
+    """Find listeners for signals emitted/defined by this script."""
+    # Collect signal names this file defines or emits
+    defined_signals = {s.name for s in gd.signals}
+    emitted_signals = {e.signal_name for e in gd.signal_emissions}
+    all_signals = defined_signals | emitted_signals
+
+    result: list[dict] = []
+    for sig_name in sorted(all_signals):
+        listeners: list[dict] = []
+        # Search all scripts for connections to this signal
+        for other_path, other_gd in session._gd_scripts.items():
+            if other_path == res_path:
+                continue
+            for conn in other_gd.signal_connections:
+                # Extract signal name from source (e.g., "self.signal_name" -> "signal_name")
+                conn_signal = conn.signal_source.rsplit(".", 1)[-1] if "." in conn.signal_source else conn.signal_source
+                if conn_signal == sig_name:
+                    listeners.append({
+                        "script": other_path,
+                        "connection": conn.target,
+                    })
+        result.append({
+            "signal_name": sig_name,
+            "listeners": listeners,
+        })
+    return result
+
+
+def _find_referenced_by(
+    session: GodotIQSession, gd: GdScript, res_path: str, is_autoload: bool
+) -> list[str]:
+    """Find scripts that reference this file."""
+    referenced_by: set[str] = set()
+
+    # 1. Autoload reverse lookup
+    if is_autoload:
+        idx = session._index
+        for name, path in idx.autoloads.items():
+            if path == res_path:
+                for dep in session.get_autoload_dependents(name):
+                    referenced_by.add(dep)
+
+    # 2. Preload/load reverse lookup
+    for other_path, other_gd in session._gd_scripts.items():
+        if other_path == res_path:
+            continue
+        for ref in other_gd.preload_refs:
+            if ref.path == res_path:
+                referenced_by.add(other_path)
+
+    # 3. Signal connection reverse lookup
+    defined_signals = {s.name for s in gd.signals}
+    for other_path, other_gd in session._gd_scripts.items():
+        if other_path == res_path:
+            continue
+        for conn in other_gd.signal_connections:
+            conn_signal = conn.signal_source.rsplit(".", 1)[-1] if "." in conn.signal_source else conn.signal_source
+            if conn_signal in defined_signals:
+                referenced_by.add(other_path)
+
+    return sorted(referenced_by)
+
+
+def _build_depth_trace(
+    session: GodotIQSession, res_path: str, depth: int, current: int = 1
+) -> dict[str, dict]:
+    """Build depth trace for transitive dependencies."""
+    if current >= depth:
+        return {}
+
+    gd = session.get_script(res_path)
+    if gd is None:
+        return {}
+
+    idx = session._index
+    trace: dict[str, dict] = {}
+
+    # Direct dependencies: autoloads and preloads
+    dep_paths: set[str] = set()
+    for ref in gd.autoload_refs:
+        autoload_path = idx.autoloads.get(ref.autoload_name)
+        if autoload_path:
+            dep_paths.add(autoload_path)
+    for ref in gd.preload_refs:
+        if ref.path.endswith(".gd"):
+            dep_paths.add(ref.path)
+
+    for dep_path in sorted(dep_paths):
+        dep_gd = session.get_script(dep_path)
+        if dep_gd is None:
+            continue
+
+        # This dependency's own deps
+        sub_deps: list[str] = []
+        for ref in dep_gd.autoload_refs:
+            sub_path = idx.autoloads.get(ref.autoload_name)
+            if sub_path:
+                sub_deps.append(sub_path)
+        for ref in dep_gd.preload_refs:
+            if ref.path.endswith(".gd"):
+                sub_deps.append(ref.path)
+
+        # This dependency's dependents
+        is_dep_autoload = dep_path in idx.autoloads.values()
+        dep_by = _find_referenced_by(session, dep_gd, dep_path, is_dep_autoload)
+
+        trace[dep_path] = {
+            "depends_on": sorted(set(sub_deps)),
+            "depended_by": dep_by,
+        }
+
+    return trace
+
+
+def _build_dependency_graph(
+    session: GodotIQSession, file_path: str, depth: int = 1
+) -> dict:
+    """Build dependency graph for a .gd file.
+
+    Args:
+        session: Loaded GodotIQSession.
+        file_path: res:// path or relative path.
+        depth: How many levels deep to trace (1 = direct only).
+
+    Returns:
+        Dictionary with dependency graph data.
+    """
+    res_path = session.resolve_file_path(file_path)
+    if res_path is None:
+        return {"error": "File not found or not a .gd script", "file": file_path}
+
+    gd = session.get_script(res_path)
+    if gd is None:
+        return {"error": "File not found or not a .gd script", "file": file_path}
+
+    idx = session._index
+
+    # Check autoload status
+    is_autoload = res_path in idx.autoloads.values()
+
+    # Signal cross-referencing
+    emits_signals = _find_signal_listeners(session, gd, res_path)
+
+    # Listens to
+    listens_to = [
+        {
+            "signal_source": c.signal_source,
+            "target": c.target,
+            "line": c.line,
+        }
+        for c in gd.signal_connections
+    ]
+
+    # Autoload references
+    references_autoloads = [
+        {
+            "autoload_name": r.autoload_name,
+            "access_pattern": r.pattern,
+            "line": r.line,
+        }
+        for r in gd.autoload_refs
+    ]
+
+    # Referenced by
+    referenced_by = _find_referenced_by(session, gd, res_path, is_autoload)
+
+    # Preloads
+    preloads = [r.path for r in gd.preload_refs]
+
+    # Scene usage
+    scene_usage = idx.script_to_scenes.get(res_path, [])
+
+    # Signal listener count for impact
+    total_listeners = sum(len(s["listeners"]) for s in emits_signals)
+
+    # Impact rating
+    impact_rating, impact_details = _compute_impact_rating(
+        is_autoload, len(referenced_by), total_listeners, len(scene_usage)
+    )
+
+    result: dict = {
+        "file": res_path,
+        "class_name": gd.class_name,
+        "extends": gd.extends,
+        "is_autoload": is_autoload,
+        "emits_signals": emits_signals,
+        "listens_to": listens_to,
+        "references_autoloads": references_autoloads,
+        "referenced_by": referenced_by,
+        "preloads": preloads,
+        "scene_usage": scene_usage,
+        "impact_rating": impact_rating,
+        "impact_details": impact_details,
+    }
+
+    # Depth tracing
+    depth = min(depth, 3)
+    if depth > 1:
+        result["depth_trace"] = _build_depth_trace(session, res_path, depth)
+
+    return result
+
+
+@mcp.tool(
+    name="godotiq_dependency_graph",
+    annotations={
+        "readOnlyHint": True,
+        "destructiveHint": False,
+        "idempotentHint": True,
+        "openWorldHint": False,
+    },
+)
+async def godotiq_dependency_graph(
+    file: str,
+    depth: int = 1,
+    ctx: Context = None,
+) -> dict:
+    """Dependency graph for a file. Shows what it depends on and what depends on it.
+
+    Args:
+        file: Path to .gd file (res:// or relative).
+        depth: How many levels deep to trace (1 = direct deps only, 2 = deps of deps).
+    """
+    session = ctx.request_context.lifespan_context["session"]
+    if session is None:
+        return {"error": "No Godot project loaded"}
+    return _build_dependency_graph(session, file, depth)
diff --git a/tests/test_tools/test_dependency_graph.py b/tests/test_tools/test_dependency_graph.py
new file mode 100644
index 0000000..7e68c6d
--- /dev/null
+++ b/tests/test_tools/test_dependency_graph.py
@@ -0,0 +1,61 @@
+"""Tests for godotiq_dependency_graph tool."""
+
+from __future__ import annotations
+
+from godotiq.session import GodotIQSession
+from godotiq.tools.code import _build_dependency_graph
+
+
+class TestDependencyGraphBasic:
+    def test_graph_basic(self, session: GodotIQSession) -> None:
+        """Basic graph structure is correct."""
+        result = _build_dependency_graph(session, "res://scripts/autoloads/economy_manager.gd")
+        assert result["file"] == "res://scripts/autoloads/economy_manager.gd"
+        assert result["class_name"] == "EconomyManagerClass"
+        assert result["is_autoload"] is True
+        assert "emits_signals" in result
+        assert "impact_rating" in result
+
+    def test_graph_autoload_refs(self, session: GodotIQSession) -> None:
+        """Autoload references are listed."""
+        result = _build_dependency_graph(session, "res://scripts/autoloads/progression_manager.gd")
+        autoload_names = [r["autoload_name"] for r in result["references_autoloads"]]
+        assert "GameManager" in autoload_names
+        assert "EconomyManager" in autoload_names
+
+    def test_graph_referenced_by(self, session: GodotIQSession) -> None:
+        """Referenced-by shows scripts that depend on this file."""
+        result = _build_dependency_graph(session, "res://scripts/entities/printer.gd")
+        # printer.gd is the GameManager autoload script
+        assert "referenced_by" in result
+
+    def test_graph_signal_listeners(self, session: GodotIQSession) -> None:
+        """Signal emissions are cross-referenced with listeners."""
+        result = _build_dependency_graph(session, "res://scripts/autoloads/economy_manager.gd")
+        assert "emits_signals" in result
+        signal_names = [s["signal_name"] for s in result["emits_signals"]]
+        assert "cash_changed" in signal_names
+
+
+class TestDependencyGraphDepth:
+    def test_graph_depth_1(self, session: GodotIQSession) -> None:
+        """Depth 1 shows only direct dependencies."""
+        result = _build_dependency_graph(session, "res://scripts/autoloads/progression_manager.gd", depth=1)
+        assert result.get("depth_trace") is None or result.get("depth_trace") == {}
+
+
+class TestDependencyGraphImpact:
+    def test_graph_impact_rating_autoload(self, session: GodotIQSession) -> None:
+        """Autoload scripts get higher impact rating."""
+        result = _build_dependency_graph(session, "res://scripts/autoloads/economy_manager.gd")
+        assert result["impact_rating"] in ("medium", "high", "critical")
+
+    def test_graph_impact_rating_entity(self, session: GodotIQSession) -> None:
+        """Entity scripts get appropriate impact rating."""
+        result = _build_dependency_graph(session, "res://scripts/entities/worker.gd")
+        assert result["impact_rating"] in ("low", "medium", "high", "critical")
+
+    def test_graph_missing_file(self, session: GodotIQSession) -> None:
+        """Missing file returns error."""
+        result = _build_dependency_graph(session, "res://nonexistent.gd")
+        assert "error" in result
