### expert_build/propose.py:_save_processed
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: UNTESTED
INTEGRATION: WIRED
REASONING: The O(n^2) fix is correct. Old code copied `existing` into `updated` and re-hashed all accumulated entries every call. New code mutates `existing` in-place and only hashes the `new_entries` for this batch. Since the caller passes the same `processed` dict each time under a lock, the dict accumulates correctly across batches, and each `write_text` call serializes the full state to disk. The in-place mutation is an intentional part of the design — it's what makes the incremental approach work. No test callers exist for this function (observation confirms `test_count: 0`).

---

### expert_build/propose.py:_process_batch
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. `successful_entries` is dead code.** It's accumulated under the write lock (line 439) but never read after `asyncio.run(run_batches())` completes. It served a purpose in the old sequential loop (passed to `_save_processed`), but now that each batch saves only its own entries, the accumulation is pointless. Should be removed to avoid confusion.

**2. Non-deterministic output ordering.** When `parallel > 1`, batches complete in arbitrary order. The old code wrote results in batch index order (sequential post-`gather` loop); the new code writes in completion order (whoever grabs `write_lock` first). This means the output file's belief ordering is no longer reproducible across runs. Probably acceptable given the "write immediately" goal, but worth an explicit acknowledgment since it changes observable behavior.

The variable scoping is fine — `filtered` and `skipped` are defined inside `async with semaphore:` but Python doesn't have block scoping, and the early `return` statements exit the whole function before the `write_lock` block is reached, so there's no risk of `UnboundLocalError`.

The `write_lock` correctly serializes both the file I/O and the `processed` dict mutation, which is sufficient for asyncio's single-threaded concurrency model.

---

### expert_build/propose.py:cmd_propose_beliefs (orchestration)
VERDICT: PASS
CORRECTNESS: VALID
SPEC_COMPLIANCE: N/A
ISSUE_COMPLIANCE: N/A
BELIEF_COMPLIANCE: N/A
TEST_COVERAGE: PARTIAL
INTEGRATION: WIRED
REASONING: The restructure from "gather-then-write" to "write-as-you-go" is well-executed. The `asyncio.Lock` + `nonlocal` pattern is idiomatic for coordinating async coroutines. `asyncio.run(run_batches())` no longer captures a return value since results are written as side effects — clean simplification. The `total_skipped` counter works correctly via `nonlocal` under the lock. Tests exist in `tests/test_propose.py` (359 lines) but the diff includes no test changes, so coverage of the new write-immediately behavior is unverified.

---

### SELF_REVIEW
LIMITATIONS: Did not read `tests/test_propose.py` to verify whether existing tests exercise the batch-processing path or would break due to changed return types / side-effect ordering. Could not verify whether any other code outside this file reads `successful_entries` (e.g., via a closure or module-level reference). Did not see the full import list to confirm `asyncio.Lock` is available in the execution context.

---

### FEATURE_REQUESTS
- Include related test file contents (or at least test function signatures) when the test file exists, so reviewers can verify whether existing tests break due to signature/return-type changes.
- Flag dead code automatically when a variable is written but never read after its last write site.

---

**Summary:** The core O(n^2) fix is correct and well-motivated. The main concern is `successful_entries` becoming dead code — it should be removed. The non-deterministic output ordering with `parallel > 1` is a minor behavioral change worth documenting. No test updates accompany the change.
