## Code Review

### ftl_project_expert/cli.py:_reasons_export
VERDICT: PASS
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: ADDRESSES
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Switching from stdout capture to `-o` flag is the right fix — capturing 3.9 MB of subprocess stdout is fragile (buffering, pipe limits, partial reads). However, there's a subtle behavioral change: the old code only wrote the file when `returncode == 0` (`beliefs_path.write_text(result.stdout)`), but now `reasons export -o` writes the file as a side effect of the subprocess itself, before the return code is checked. If the `reasons` command partially fails after opening the file for write, it could leave a truncated/empty file on disk. The new `_load_network()` error handling mitigates this for `network.json`, but `beliefs.md` has no equivalent guard. Low risk in practice since `reasons` likely writes atomically, but worth noting.

---

### ftl_project_expert/cli.py:_load_network
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: PARTIAL
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: The error handling and recovery logic is well-structured and addresses the core issue (JSON parse errors on corrupt `network.json`). Two concerns:

1. **Uncaught `FileNotFoundError`**: After the "file doesn't exist" branch exports with `-o` and gets `returncode == 0`, control falls through to `json.loads(network_path.read_text())`. If the export succeeds (returncode 0) but doesn't actually create the file (edge case — e.g., empty database, weird filesystem state), `read_text()` raises `FileNotFoundError`, which is **not** caught by `(json.JSONDecodeError, ValueError)`. The fix is simple — either add `FileNotFoundError` to the except tuple, or re-check `network_path.exists()` before reading.

2. **Issue suggestion #4 not addressed**: The issue specifically calls out that `except Exception as e` at the derive level (line ~1866) should distinguish model failures from data loading failures. This change prevents the `JSONDecodeError` from reaching that handler, which is good, but if a future corruption path is introduced, the same confusing error message will recur.

All 7 production callers use `network.get("nodes", {})`, so the `{"nodes": {}}` fallback is safe across the codebase.

---

### SELF_REVIEW
LIMITATIONS: No test files were available to verify coverage (observation confirmed `test_count: 0`). Could not verify the `reasons export -o` CLI contract — specifically whether it writes atomically, what happens on partial failure, and whether returncode 0 guarantees the file exists. Could not see the `except Exception as e` block in `_derive_once` / `derive` (issue suggestion #4) to assess whether it still needs attention.

---

### FEATURE_REQUESTS
- Include the `reasons` CLI's `-o` flag documentation or implementation to verify atomicity guarantees when reviewing code that depends on it
- When the issue lists numbered suggestions, auto-check each one against the diff and flag unaddressed items
