### expert_build/propose.py:_process_batch
VERDICT: CONCERN
CORRECTNESS: QUESTIONABLE
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: WIRED
REASONING: The refactor from "gather-then-write" to "write-immediately" is structurally sound. The `write_lock` prevents concurrent file writes, the semaphore is correctly released before acquiring the write lock (good — frees the slot for another batch to start processing), and early returns on error properly skip the write phase.

Two issues worth noting:

1. **Non-deterministic output ordering.** The original code used `asyncio.gather` which preserves input order in its return list, then iterated sequentially — so batches always appeared in order in the output file. The new code writes as each batch completes, so with `parallel > 1`, batch 3 could write before batch 1. For a human-review file this is probably acceptable, but it's a behavioral change that isn't called out in the commit message.

2. **`_save_processed` is called with the full `successful_entries` list on every batch completion.** It re-reads every previously-successful entry's file content to recompute SHA-256 hashes (lines 74-76 of `_save_processed`). This is O(n^2) across batches. This existed in the original code too — not a regression — but the incremental-write framing of this PR would be a natural place to fix it (e.g., pass only the new entries and merge).

Tests exist in `tests/test_propose.py` (359 lines, 3 tests) but I cannot verify they exercise the concurrent write path or check output ordering.

---

### expert_build/propose.py (overall change)
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: WIRED
REASONING: The motivating goal — writing batch results to disk immediately so partial progress survives crashes — is achieved correctly. The `asyncio.Lock` is appropriate here (single-threaded event loop). The `nonlocal total_skipped` is safe under asyncio's cooperative scheduling and is additionally protected by the lock. The `return` (previously `return None`) change is clean — callers no longer inspect results since `asyncio.gather` return value is now discarded. No callers outside this function are affected.

---

### SELF_REVIEW
LIMITATIONS: Could not read `tests/test_propose.py` to verify whether existing test assertions break due to the ordering change or whether the concurrent-write path is tested at all. Could not see the `invoke` function to confirm it's truly async-safe. Did not verify whether any downstream consumers of the output file depend on batch ordering.

---

### FEATURE_REQUESTS
- Include related test file contents in observations when test files are detected, not just existence/line-count
- Show `git log --oneline` for the branch to understand the commit narrative and whether behavioral changes are intentional
---
