Now I have the full picture. Here's the review:

---

### expert_build/summarize.py
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: PARTIAL
REASONING:

**Race condition on manifest file (CONCERN):** `_summarize_one` appends to the `.summarized` manifest file (`manifest.open("a")`) from multiple concurrent coroutines. While Python's GIL protects in-memory `done` set mutations, concurrent file appends without coordination could interleave writes if a single `write()` call doesn't flush atomically at the OS level. In practice on most POSIX systems with short lines this is likely fine, but it's fragile — a proper fix would be to write the manifest once after `gather()` completes, or use an `asyncio.Lock`.

**Race condition on entry directory/file writes (CONCERN):** When `parallel > 1`, multiple coroutines call `_write_entry` concurrently. Two source files with the same stem processed on the same day would write to the same `entry_path`, with the last writer winning silently. This existed in the sequential version too (same stem = overwrite), but parallelism makes it non-deterministic which one wins.

**`_prepare_source` uses `print()` (minor):** The function prints truncation warnings as a side effect, which is fine but means output ordering is non-deterministic under parallelism. Not a bug, just noisy.

**`parallel=0` handling:** `max(1, getattr(args, "parallel", 1))` correctly prevents `Semaphore(0)` deadlock — good fix from commit `3d4e70f`.

---

### expert_build/summarize.py:_prepare_source
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: COVERED
INTEGRATION: WIRED
REASONING: Clean extraction of the existing frontmatter-parsing and truncation logic into a pure-ish helper. Returns `None` for empty content, tuple otherwise. All existing tests exercise this path indirectly through `cmd_summarize`. Logic is identical to the pre-refactor version.

---

### expert_build/summarize.py:_write_entry
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: COVERED
INTEGRATION: WIRED
REASONING: Straightforward extraction. Creates directory, writes frontmatter + summary. Tested indirectly by all tests that check entry content/structure.

---

### expert_build/summarize.py:_summarize_one
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: WIRED
REASONING: Correctly acquires semaphore before doing work. Error handling catches exceptions from `invoke` and returns `False` without crashing the gather. The `done` set mutation is safe under asyncio (single-threaded event loop). No test directly verifies that an `invoke` exception is handled gracefully in the parallel path (the old sequential code had the same gap).

---

### expert_build/summarize.py:cmd_summarize
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: PARTIAL
REASONING:

**Pipeline caller missing `parallel` and `recursive` attributes:** `expert_build/pipeline.py:125-130` builds a `SimpleNamespace` for `cmd_summarize` without `parallel` or `recursive` keys. The code handles `recursive` via `getattr(args, "recursive", False)` but accesses `parallel` via `getattr(args, "parallel", 1)` — so this is safe at runtime. However, it's a maintenance trap; the pipeline should be updated to pass these explicitly.

**`asyncio.run()` inside a synchronous function:** If `cmd_summarize` is ever called from an already-running event loop (e.g., a Jupyter notebook, or if another command adds async support), `asyncio.run()` will raise `RuntimeError: cannot be called when another event loop is running`. This is fine for CLI usage today but worth noting.

---

### expert_build/cli.py
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: COVERED
INTEGRATION: WIRED
REASONING: Adds `--parallel` argument with sensible default of 1 and `type=int`. Correctly placed alongside the other summarize subparser arguments.

---

### tests/test_summarize.py
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: WIRED
REASONING: All existing tests correctly migrated from `invoke_sync` to `invoke` with `AsyncMock`. Two new parallel-specific tests added: `test_parallel_summarizes_multiple_files` (4 files, parallel=2) and `test_parallel_default_is_sequential`. Missing: no test for error handling under parallelism (one file fails, others succeed), no test verifying actual concurrency behavior (e.g., that semaphore limits concurrent calls), no test for `parallel=0` edge case.

---

## Summary of actionable findings

1. **CONCERN — pipeline.py not updated:** `_stage_summarize` in `pipeline.py:125` doesn't pass `parallel` in its `SimpleNamespace`. Safe due to `getattr` fallback, but should be updated for consistency. Same pattern as the existing `recursive` omission.

2. **CONCERN — manifest file concurrent writes:** Under `parallel > 1`, multiple coroutines append to `.summarized` without synchronization. Low risk in practice but architecturally fragile.

3. **CONCERN — missing parallel error-handling test:** No test verifies that when one file's `invoke` raises, the other files still complete successfully.

---

### SELF_REVIEW
LIMITATIONS: Could not run the test suite to confirm all tests pass. Did not see the full `pipeline.py` to check if there are other `SimpleNamespace` constructions that might need updating. Could not verify whether `invoke`'s subprocess spawning behaves correctly under `asyncio.gather` with real concurrency (multiple subprocesses).

---

### FEATURE_REQUESTS
- Include the full source of functions that call modified functions (callers), not just their locations — the pipeline.py `SimpleNamespace` construction was critical context I had to fetch manually.
- Show git log for recent commits on the branch to understand the evolution of the change (e.g., the `parallel=0` fix was already applied but only visible from commit messages).
