### ftl_code_expert/prompts/research.py
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Clean prompt template. Properly instructs the LLM to output a JSON array with clear constraints (1-5 files, relative paths, exclude already-explored). Format placeholders match usage in the research command.
---

### ftl_code_expert/prompts/__init__.py
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Standard re-export addition. Added to both the import and `__all__`.
---

### ftl_code_expert/cli.py:_get_explored_files
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Scans first 5 lines of each entry for `# File:` header — matches the entry format used by `_create_entry`. OSError is caught per-file so one unreadable entry won't crash the scan. Returns a set for O(1) lookup.
---

### ftl_code_expert/cli.py:_parse_review_candidates
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: The function itself is fine, but callers access `c['id']` and `c['comment']` without checking those keys exist. If a review result lacks `id` or `comment`, the command crashes with a KeyError. Low risk since the input is from `review-beliefs` output which should always have those fields, but a `.get()` with a fallback or early validation would be more defensive at this system boundary (reading external JSON).
---

### ftl_code_expert/cli.py:_parse_inferred_files
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Regex `\[.*?\]` with `re.DOTALL` correctly handles multiline JSON arrays. Non-greedy match grabs the first array, which is the right behavior given the prompt asks for exactly one. Type-checks both the parsed result and individual elements. Edge case: nested arrays would be mis-parsed, but the prompt constrains output to a flat string array.
---

### ftl_code_expert/cli.py:research
VERDICT: CONCERN
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: The command is well-structured and follows established patterns (concurrent exploration, `_finalize_topic`, `ctx.invoke(propose_beliefs)`). Two concerns:

1. **No tests.** This is a 130-line command with multiple stages (parse, infer, explore, propose). The helper functions `_parse_review_candidates`, `_parse_inferred_files`, and `_get_explored_files` are all easily unit-testable but have zero coverage.

2. **Sequential path calls `sys.exit(1)` on error.** When `parallel <= 1`, the code calls `_run_file_topic` which exits on any LLM error (observed at line 1024). This means the first file failure kills the entire research run, losing any results from previously explored files. The concurrent path handles this gracefully via `return_exceptions=True`. Consider wrapping the sequential call in a try/except to match the concurrent path's resilience.

Minor: `ctx.invoke(propose_beliefs, auto_accept=True)` is a correct use of Click's invoke mechanism — the `SystemExit` catch is appropriate since `propose_beliefs` calls `sys.exit()` on some error paths.
---

### SELF_REVIEW
LIMITATIONS: Could not verify the `Topic` dataclass/namedtuple fields — the observation showed no `__init__` signature, so I cannot confirm the constructor accepts `title`, `kind`, `target`, `source` kwargs (though usage is consistent with `_explore_topics_concurrent` which accesses `.kind` and `.target`). Could not see `invoke_concurrent_sync` implementation to verify it handles the prompts list and returns results in the same order. Could not see the review-beliefs output format to confirm `id` and `comment` fields are always present.
---

### FEATURE_REQUESTS
- Include dataclass/namedtuple field definitions when a class observation returns no `__init__` signature
- When `invoke_concurrent_sync` is imported from another module but not found in the target file, auto-search the source module
- Include the JSON schema or sample output of upstream commands (like `review-beliefs`) when the code under review parses their output
---
