### ftl_project_expert/llm.py:invoke_concurrent
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Clean async concurrency implementation. Semaphore correctly limits parallelism, `return_exceptions=True` preserves result ordering and converts failures to in-band values. Callers correctly check `isinstance(result, Exception)`. No nested `asyncio.run` risk since `invoke_concurrent_sync` is the only sync entry point.
---

### ftl_project_expert/llm.py:invoke_concurrent_sync
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Thin wrapper matching the existing `invoke_sync` pattern. Called from three sites in `cli.py`.
---

### ftl_project_expert/cli.py:_build_topic_prompt
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Clean extraction from `_run_topic`. Logic is identical to the pre-refactor version. Used by both `_run_topic` (sequential) and `_explore_loop_parallel` (concurrent). The separation is necessary to build prompts before batched invocation.
---

### ftl_project_expert/cli.py:_run_topic
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Now delegates prompt construction to `_build_topic_prompt`. Behavior is unchanged from the caller's perspective.
---

### ftl_project_expert/cli.py:_explore_loop_parallel
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Two issues. (1) `_build_topic_prompt` calls `_get_source(config)` and `source.get_issue()` for each topic in the batch — these are sequential network calls that happen during prompt construction, so the speedup only applies to the LLM invocations, not issue fetching. This is a performance gap, not a bug, but users may be surprised. (2) More substantively: failed topics (`isinstance(result, Exception)`) still increment `explored`, so `--loop 10 --parallel 3` with failures will attempt exactly 10 topics, not 10 successful ones. This is defensible behavior but differs from the sequential path where `_run_topic` returns on error without counting — wait, actually `_explore_loop` always increments `explored` before calling `_run_topic`, so the behavior matches. Concern is primarily about zero test coverage for concurrent code paths.
---

### ftl_project_expert/cli.py:_explore_loop
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Simple dispatch — delegates to `_explore_loop_parallel` when `max_parallel > 1`, preserves original sequential path otherwise. Default parameter `max_parallel=1` maintains backward compatibility.
---

### ftl_project_expert/cli.py:explore
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: PARTIAL
REASONING: `--parallel` is silently ignored when `--loop` is not provided. Using `explore --parallel 3` (single topic or `--pick`) accepts the flag but processes sequentially through `_run_topic`. Should either warn, error, or apply parallelism to multi-pick scenarios.
---

### ftl_project_expert/cli.py:scan (--per-issue)
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: The `per_issue` path forces `all_pages = True` and calls `_cache_issues` per page. But `_cache_issues` (line 422-449) overwrites the entire cache file each page, so multi-page runs lose all but the last page's issues. When those topics are later explored, `_build_topic_prompt` falls back to the title-only path for uncached issues, degrading prompt quality. The overwrite bug is pre-existing but `--per-issue` makes it the primary data path. Additionally, the `from .topics import Topic, add_topics` inside `_scan_page` is redundant — both are already imported at the module level.
---

### ftl_project_expert/cli.py:propose_beliefs
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Clean dual-path (parallel vs sequential) with the `if max_parallel > 1 and len(prompts) > 1` guard. Error handling differs slightly between paths (parallel uses `isinstance` check, sequential uses try/except) but both correctly skip failed batches. Prompts are pre-built before either path, avoiding duplication.
---

### ftl_project_expert/cli.py:review_proposals
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Same parallel pattern as `propose_beliefs`. Prompts pre-built, then dispatched. `all_decisions.update(decisions)` is order-independent since belief IDs are unique keys.
---

### ftl_project_expert/cli.py:update
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: Good simplification — the inline exploration loop was replaced with `_explore_loop(ctx, project_dir, topic_limit, max_parallel)`, eliminating duplicated code. The `--parallel` flag is correctly threaded through to steps 2 (explore), 3 (propose-beliefs), and 4 (review-proposals) via `ctx.invoke`.
---

## Summary

The refactoring is well-structured: prompt construction extracted cleanly, concurrent invocation layered on top of existing `invoke`, and the parallel/sequential paths share prompt-building logic. The main issues are:

1. **Zero test coverage** for all changed code — both source files have no tests. Concurrent code is especially risky to ship untested.
2. **`_cache_issues` overwrites on each page** — pre-existing, but `--per-issue` makes it the critical data path for subsequent exploration.
3. **`--parallel` silently ignored without `--loop`** — minor UX issue.

### SELF_REVIEW
LIMITATIONS: Could not verify the `Topic` dataclass fields (the observation for `Topic.__init__` errored) to confirm the constructor call in `_scan_page`'s `per_issue` path uses correct field names. Could not see the full module-level imports to definitively confirm the redundant import. No test files exist to check for broken assertions.
---

### FEATURE_REQUESTS
- Include the full module-level import block in observations when reviewing files with local re-imports, to catch redundancy.
- When test coverage is zero, flag it prominently in a summary section rather than requiring per-function annotation.
---
