You are a senior code reviewer preparing to review code changes.

## Code Changes

```diff
diff --git a/ftl_project_expert/cli.py b/ftl_project_expert/cli.py
index c3a19c5..445a0dd 100644
--- a/ftl_project_expert/cli.py
+++ b/ftl_project_expert/cli.py
@@ -2013,6 +2013,82 @@ def derive(ctx, output, auto_add, exhaust, max_rounds, dry_run):
     click.echo("Or re-run with --auto to add automatically.")
 
 
+# --- review-beliefs ---
+
+
+@cli.command("review-beliefs")
+@click.option("--auto-retract", is_flag=True, default=False,
+              help="Automatically retract beliefs found invalid")
+@click.option("--sample", type=int, default=None,
+              help="Randomly sample N beliefs to review")
+@click.option("--min-depth", type=int, default=None,
+              help="Only review beliefs at this depth or deeper")
+@click.option("--dry-run", is_flag=True, default=False,
+              help="Report findings without taking action")
+@click.option("--output", "-o", default=None,
+              help="Write findings to markdown file")
+@click.pass_context
+def review_beliefs(ctx, auto_retract=False, sample=None, min_depth=None, dry_run=False, output=None):
+    """Review derived beliefs for validity using LLM evaluation."""
+    if not _has_reasons():
+        click.echo("Error: reasons CLI required. Install with: uv tool install ftl-reasons", err=True)
+        sys.exit(1)
+
+    model = ctx.obj["model"]
+    timeout = ctx.obj["timeout"]
+
+    cmd = ["reasons", "review-beliefs", "-m", model, "--timeout", str(timeout)]
+    if auto_retract:
+        cmd.append("--auto-retract")
+    if sample is not None:
+        cmd.extend(["--sample", str(sample)])
+    if min_depth is not None:
+        cmd.extend(["--min-depth", str(min_depth)])
+    if dry_run:
+        cmd.append("--dry-run")
+    if output:
+        cmd.extend(["-o", output])
+
+    click.echo(f"Reviewing beliefs with {model}...", err=True)
+    result = subprocess.run(cmd, text=True)
+    if result.returncode != 0:
+        sys.exit(result.returncode)
+
+    _reasons_export()
+
+
+# --- repair ---
+
+
+@cli.command("repair")
+@click.option("--review-file", default=None,
+              help="Path to review-beliefs JSON report")
+@click.option("--dry-run", is_flag=True, default=False,
+              help="Report findings without applying changes")
+@click.pass_context
+def repair(ctx, review_file=None, dry_run=False):
+    """Repair beliefs flagged by review-beliefs."""
+    if not _has_reasons():
+        click.echo("Error: reasons CLI required. Install with: uv tool install ftl-reasons", err=True)
+        sys.exit(1)
+
+    model = ctx.obj["model"]
+    timeout = ctx.obj["timeout"]
+
+    cmd = ["reasons", "repair", "-m", model, "--timeout", str(timeout)]
+    if review_file:
+        cmd.extend(["--review-file", review_file])
+    if dry_run:
+        cmd.append("--dry-run")
+
+    click.echo(f"Repairing beliefs with {model}...", err=True)
+    result = subprocess.run(cmd, text=True)
+    if result.returncode != 0:
+        sys.exit(result.returncode)
+
+    _reasons_export()
+
+
 # --- summary ---
 
 
@@ -2578,10 +2654,11 @@ def _load_update_checkpoint(project_dir: str) -> str | None:
               help="Max concurrent LLM calls (default: 1, try 3 for speed)")
 @click.pass_context
 def update(ctx, since, since_last, state, limit, all_pages, max_explore, max_parallel):
-    """Automated update pipeline: scan, explore, extract beliefs, derive, summarize.
+    """Automated update pipeline: scan, explore, extract beliefs, derive, review, repair, summarize.
 
     Pulls all issues/PRs updated since a date, explores them, proposes and
-    accepts beliefs, derives logical consequences, and generates a summary.
+    accepts beliefs, derives logical consequences, reviews and repairs
+    derived beliefs, and generates a summary.
 
     Examples:
         project-expert update --since 2026-04-01
@@ -2748,9 +2825,39 @@ def update(ctx, since, since_last, state, limit, all_pages, max_explore, max_par
         errors.append(f"derive: {e}")
         click.echo(f"WARN: derive failed: {e}, continuing...", err=True)
 
-    # --- Step 7: Summary ---
+    # --- Step 7: Review beliefs ---
+    click.echo(f"\n{'=' * 40}", err=True)
+    click.echo("Step 7: Reviewing derived beliefs", err=True)
+    click.echo(f"{'=' * 40}", err=True)
+
+    try:
+        ctx.invoke(review_beliefs, auto_retract=True)
+    except SystemExit as e:
+        if e.code and e.code != 0:
+            errors.append(f"review-beliefs exited with code {e.code}")
+            click.echo(f"WARN: review-beliefs failed (exit {e.code}), continuing...", err=True)
+    except Exception as e:
+        errors.append(f"review-beliefs: {e}")
+        click.echo(f"WARN: review-beliefs failed: {e}, continuing...", err=True)
+
+    # --- Step 8: Repair ---
+    click.echo(f"\n{'=' * 40}", err=True)
+    click.echo("Step 8: Repairing flagged beliefs", err=True)
+    click.echo(f"{'=' * 40}", err=True)
+
+    try:
+        ctx.invoke(repair)
+    except SystemExit as e:
+        if e.code and e.code != 0:
+            errors.append(f"repair exited with code {e.code}")
+            click.echo(f"WARN: repair failed (exit {e.code}), continuing...", err=True)
+    except Exception as e:
+        errors.append(f"repair: {e}")
+        click.echo(f"WARN: repair failed: {e}, continuing...", err=True)
+
+    # --- Step 9: Summary ---
     click.echo(f"\n{'=' * 40}", err=True)
-    click.echo("Step 7: Generating summary", err=True)
+    click.echo("Step 9: Generating summary", err=True)
     click.echo(f"{'=' * 40}", err=True)
 
     try:

```

## Your Task

Analyze the diff and identify what additional information you need to render confident verdicts.
Do NOT render verdicts yet. Only request observations.

## Available Observation Tools

| Tool | Purpose | When to use |
|------|---------|-------------|
| `exception_hierarchy` | Show exception MRO and subclasses | Retry logic, exception handling |
| `raises_analysis` | What exceptions a function raises | New function calls, error paths |
| `call_graph` | What a function calls | Impact analysis |
| `find_usages` | Where a symbol is used (with prod/test split) | Quick integration lookup |
| `find_callers` | Caller analysis with prod/test split and calling context | Method signature changes, return type changes, constructor modifications, integration verification |
| `test_coverage` | Find tests for a file (uses coverage-map if available) | Test coverage claims |
| `coverage_map_tests` | Find tests covering a file (from coverage-map.json) | Precise test coverage from actual execution |
| `coverage_map_files` | Find files covered by tests matching a pattern | Impact analysis for test changes |
| `function_body` | Full source of a function/method | Need complete function context beyond diff hunks |
| `file_imports` | Extract imports from a file | Verify import changes, check dependencies |
| `project_dependencies` | Get pyproject.toml/requirements.txt | Verify new imports have dependencies |
| `related_test_files` | Find test files for a source file | Discover tests by naming, imports, and coverage map |
| `class_hierarchy` | Show base classes and their `__init__` signatures | Class changes its parent, modifies `__init__`, or uses `super()` |
| `symbol_migration` | Check if a rename is complete across the repo | Symbol renamed in diff — verify old name is fully removed |
| `generator_info` | Report whether a function uses `yield` | Function might be a generator — affects return value semantics |

## What to Look For

1. **Exception handling**: Any `retry_if_exception_type`, `except`, or exception class references
2. **New dependencies**: Calls to external libraries where you don't know the error behavior
3. **Behavioral changes**: Modified logic where you need to verify callers/callees
4. **Test claims**: References to tests you can't see in the diff
5. **Inheritance changes**: Class definition changes, new base classes, `super()` calls
6. **Renames**: Symbols that appear to have been renamed in the diff
7. **Factory methods**: Calls to `@classmethod` / `@staticmethod` constructors (e.g. `Result.error(...)`) — request `function_body` to see their implementation

## Output Format

Output a JSON array of observation requests:

```json
[
  {"name": "descriptive_name", "tool": "tool_name", "params": {"param": "value"}},
  ...
]
```

If you don't need any observations (simple changes, all context is in the diff), output:

```json
[]
```

## Examples

For a diff containing `retry_if_exception_type((OSError, httpx.TransportError))`:
```json
[
  {"name": "oserror_subclasses", "tool": "exception_hierarchy", "params": {"class_name": "builtins.OSError"}},
  {"name": "transport_errors", "tool": "exception_hierarchy", "params": {"class_name": "httpx.TransportError"}}
]
```

For a diff adding a new function that calls `oauth_client.get_access_token()`:
```json
[
  {"name": "oauth_exceptions", "tool": "raises_analysis", "params": {"file_path": "src/auth/oauth.py", "function_name": "get_access_token"}}
]
```

For a diff modifying a method but you need the full function to verify:
```json
[
  {"name": "full_getattr", "tool": "function_body", "params": {"file_path": "src/proxy.py", "function_name": "__getattr__"}}
]
```

For a diff changing a method signature or return type (verify all callers):
```json
[
  {"name": "handle_request_callers", "tool": "find_callers", "params": {"symbol": "handle_request"}}
]
```

For a diff adding new imports (e.g., `import httpx`):
```json
[
  {"name": "file_imports", "tool": "file_imports", "params": {"file_path": "src/client.py"}},
  {"name": "project_deps", "tool": "project_dependencies", "params": {}}
]
```

For a diff calling a factory method like `ModuleResult.error_result(msg)`:
```json
[
  {"name": "error_result_body", "tool": "function_body", "params": {"file_path": "src/models.py", "function_name": "error_result"}}
]
```

For a diff where a class changes its parent class:
```json
[
  {"name": "client_hierarchy", "tool": "class_hierarchy", "params": {"class_name": "MyClient", "file_path": "src/client.py"}}
]
```

For a diff that renames a symbol (e.g., `OldClient` to `NewClient`):
```json
[
  {"name": "client_rename", "tool": "symbol_migration", "params": {"old_name": "OldClient", "new_name": "NewClient"}}
]
```

For a diff modifying a function that might be a generator:
```json
[
  {"name": "process_gen", "tool": "generator_info", "params": {"file_path": "src/pipeline.py", "function_name": "process_items"}}
]
```

Now analyze the diff above and output your observation requests as JSON:
