You are a senior code reviewer. Review the following code changes.

## Specification

No specification provided. Focus on correctness, tests, and integration.





## Code Changes

```diff
diff --git a/expert_build/prompts.py b/expert_build/prompts.py
index 8144a2d..53ff11f 100644
--- a/expert_build/prompts.py
+++ b/expert_build/prompts.py
@@ -73,6 +73,8 @@
 - Include commands, paths, config values when relevant
 - Do NOT include opinions or subjective assessments
 - Aim for 3-8 beliefs per entry (not every sentence is a belief)
+- Set "accept" to true if the claim is well-supported by the source material, \
+false if it is vague, speculative, or poorly supported
 
 ---
 
@@ -83,7 +85,7 @@
 ---
 
 Respond with ONLY this JSON array (no other text):
-[{{"id": "<kebab-case-id>", "claim": "<one-line factual claim>", "source": "<path to entry file>", "source_url": "<url from SOURCE_URL in header, or empty string>"}}]
+[{{"id": "<kebab-case-id>", "claim": "<one-line factual claim>", "accept": true, "source": "<path to entry file>", "source_url": "<url from SOURCE_URL in header, or empty string>"}}]
 """
 
 EXAM_ANSWER = """\
diff --git a/expert_build/propose.py b/expert_build/propose.py
index b7cd699..8de129b 100644
--- a/expert_build/propose.py
+++ b/expert_build/propose.py
@@ -252,10 +252,11 @@ def _build_dedup_context(
 
 
 def auto_accept_proposals(filepath: str):
-    """Rewrite all [ACCEPT/REJECT] markers to [ACCEPT] in a proposals file."""
+    """Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file."""
     path = Path(filepath)
     text = path.read_text()
     text = re.sub(r'\[ACCEPT/REJECT\]', '[ACCEPT]', text)
+    text = re.sub(r'\[REJECT\]', '[ACCEPT]', text)
     path.write_text(text)
 
 
@@ -364,7 +365,7 @@ def cmd_propose_beliefs(args):
     if not appended:
         with output.open("w") as f:
             f.write("# Proposed Beliefs\n\n")
-            f.write("Edit each entry: change `[ACCEPT/REJECT]` to `[ACCEPT]` or `[REJECT]`.\n")
+            f.write("Review each entry: change `[REJECT]` to `[ACCEPT]` to keep, or vice versa.\n")
             f.write("Then run: `expert-build accept-beliefs`\n\n")
             f.write("---\n\n")
             f.write(f"**Generated:** {date.today().isoformat()}\n")
@@ -440,7 +441,8 @@ async def run_batches():
                 claim = b.get("claim", "")
                 source = b.get("source", "")
                 source_url = b.get("source_url", "")
-                f.write(f"### [ACCEPT/REJECT] {bid}\n")
+                verdict = "[ACCEPT]" if b.get("accept", True) else "[REJECT]"
+                f.write(f"### {verdict} {bid}\n")
                 f.write(f"{claim}\n")
                 f.write(f"- Source: {source}\n")
                 f.write(f"- Source URL: {source_url or 'none'}\n\n")
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
index 1ce7775..798c976 100644
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -92,6 +92,19 @@ def test_preserves_already_accepted(self, tmp_path):
         assert text.count("[ACCEPT]") == 2
         assert "[ACCEPT/REJECT]" not in text
 
+    def test_converts_reject_to_accept(self, tmp_path):
+        f = tmp_path / "proposals.md"
+        f.write_text(
+            "### [ACCEPT] good-belief\n"
+            "Text\n"
+            "### [REJECT] weak-belief\n"
+            "Text\n"
+        )
+        auto_accept_proposals(str(f))
+        text = f.read_text()
+        assert text.count("[ACCEPT]") == 2
+        assert "[REJECT]" not in text
+
     def test_no_markers_is_noop(self, tmp_path):
         f = tmp_path / "proposals.md"
         original = "### ACCEPT belief-one\nText\n"
diff --git a/tests/test_propose.py b/tests/test_propose.py
index e96eced..42fe6ec 100644
--- a/tests/test_propose.py
+++ b/tests/test_propose.py
@@ -37,10 +37,10 @@ def make_args(input_dir, output="proposed-beliefs.md", batch_size=2, model="test
     )
 
 
-def _json_beliefs(*beliefs):
+def _json_beliefs(*beliefs, accept=True):
     """Helper to build a JSON response from (id, claim) tuples."""
     return json.dumps([
-        {"id": b[0], "claim": b[1], "source": "entry.md", "source_url": ""}
+        {"id": b[0], "claim": b[1], "accept": accept, "source": "entry.md", "source_url": ""}
         for b in beliefs
     ])
 
@@ -315,3 +315,45 @@ def invoke_side_effect(prompt, model=None, timeout=None):
     assert "belief-1" in content
     assert "belief-2" in content
     assert "belief-3" in content
+
+
+def test_accept_verdict_from_llm(entries_dir, work_dir):
+    """LLM's accept=true produces [ACCEPT] in output."""
+    (entries_dir / "entry0.md").write_text("# Entry\nContent")
+
+    output = work_dir / "proposed-beliefs.md"
+    args = make_args(entries_dir, output=str(output), batch_size=5)
+
+    def invoke_side_effect(prompt, model=None, timeout=None):
+        return _json_beliefs(("good-belief", "A solid claim."), accept=True)
+
+    with patch("expert_build.propose.check_model_available", return_value=True), \
+         patch("expert_build.propose.invoke", new_callable=AsyncMock, side_effect=invoke_side_effect), \
+         patch("expert_build.propose._load_existing_beliefs", return_value=[]), \
+         patch("expert_build.propose._has_embeddings", return_value=False):
+        cmd_propose_beliefs(args)
+
+    content = output.read_text()
+    assert "### [ACCEPT] good-belief" in content
+    assert "### [REJECT]" not in content
+
+
+def test_reject_verdict_from_llm(entries_dir, work_dir):
+    """LLM's accept=false produces [REJECT] in output."""
+    (entries_dir / "entry0.md").write_text("# Entry\nContent")
+
+    output = work_dir / "proposed-beliefs.md"
+    args = make_args(entries_dir, output=str(output), batch_size=5)
+
+    def invoke_side_effect(prompt, model=None, timeout=None):
+        return _json_beliefs(("weak-belief", "A vague claim."), accept=False)
+
+    with patch("expert_build.propose.check_model_available", return_value=True), \
+         patch("expert_build.propose.invoke", new_callable=AsyncMock, side_effect=invoke_side_effect), \
+         patch("expert_build.propose._load_existing_beliefs", return_value=[]), \
+         patch("expert_build.propose._has_embeddings", return_value=False):
+        cmd_propose_beliefs(args)
+
+    content = output.read_text()
+    assert "### [REJECT] weak-belief" in content
+    assert "### [ACCEPT]" not in content

```

## Observation Results

You previously requested observations. Here are the results:

```json
{
  "auto_accept_callers": {
    "symbol": "auto_accept_proposals",
    "production_callers": [
      {
        "file": "expert_build/propose.py",
        "line": 254,
        "text": "def auto_accept_proposals(filepath: str):",
        "context_function": "_build_dedup_context",
        "context_snippet": "   251: # --- Commands ---\n   252: \n   253: \n>> 254: def auto_accept_proposals(filepath: str):\n   255:     \"\"\"Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file.\"\"\"\n   256:     path = Path(filepath)\n   257:     text = path.read_text()"
      },
      {
        "file": "expert_build/pipeline.py",
        "line": 156,
        "text": "from .propose import auto_accept_proposals",
        "context_function": "_stage_extract",
        "context_snippet": "   153: \n   154:     proposals_path = Path(\"proposed-beliefs.md\")\n   155:     if proposals_path.exists():\n>> 156:         from .propose import auto_accept_proposals\n   157:         auto_accept_proposals(str(proposals_path))\n   158:         print(\"Auto-accepted all proposed beliefs\", file=sys.stderr)\n   159: "
      },
      {
        "file": "expert_build/pipeline.py",
        "line": 157,
        "text": "auto_accept_proposals(str(proposals_path))",
        "context_function": "_stage_extract",
        "context_snippet": "   154:     proposals_path = Path(\"proposed-beliefs.md\")\n   155:     if proposals_path.exists():\n   156:         from .propose import auto_accept_proposals\n>> 157:         auto_accept_proposals(str(proposals_path))\n   158:         print(\"Auto-accepted all proposed beliefs\", file=sys.stderr)\n   159: \n   160:         accept_args = SimpleNamespace(file=\"proposed-beliefs.md\")"
      }
    ],
    "test_callers": [
      {
        "file": "tests/test_pipeline.py",
        "line": 22,
        "text": "from expert_build.propose import auto_accept_proposals",
        "context_function": null,
        "context_snippet": "   19:     _load_state,\n   20:     STATE_FILE,\n   21: )\n>> 22: from expert_build.propose import auto_accept_proposals\n   23: \n   24: \n   25: @pytest.fixture"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 66,
        "text": "# --- auto_accept_proposals ---",
        "context_function": "make_drr_args",
        "context_snippet": "   63:     return types.SimpleNamespace(**defaults)\n   64: \n   65: \n>> 66: # --- auto_accept_proposals ---\n   67: \n   68: class TestAutoAcceptProposals:\n   69:     def test_replaces_markers(self, tmp_path):"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 77,
        "text": "auto_accept_proposals(str(f))",
        "context_function": "test_replaces_markers",
        "context_snippet": "   74:             \"### [ACCEPT/REJECT] belief-two\\n\"\n   75:             \"Text two\\n\"\n   76:         )\n>> 77:         auto_accept_proposals(str(f))\n   78:         text = f.read_text()\n   79:         assert \"[ACCEPT/REJECT]\" not in text\n   80:         assert text.count(\"[ACCEPT]\") == 2"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 90,
        "text": "auto_accept_proposals(str(f))",
        "context_function": "test_preserves_already_accepted",
        "context_snippet": "   87:             \"### [ACCEPT/REJECT] needs-accept\\n\"\n   88:             \"Text\\n\"\n   89:         )\n>> 90:         auto_accept_proposals(str(f))\n   91:         text = f.read_text()\n   92:         assert text.count(\"[ACCEPT]\") == 2\n   93:         assert \"[ACCEPT/REJECT]\" not in text"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 103,
        "text": "auto_accept_proposals(str(f))",
        "context_function": "test_converts_reject_to_accept",
        "context_snippet": "   100:             \"### [REJECT] weak-belief\\n\"\n   101:             \"Text\\n\"\n   102:         )\n>> 103:         auto_accept_proposals(str(f))\n   104:         text = f.read_text()\n   105:         assert text.count(\"[ACCEPT]\") == 2\n   106:         assert \"[REJECT]\" not in text"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 112,
        "text": "auto_accept_proposals(str(f))",
        "context_function": "test_no_markers_is_noop",
        "context_snippet": "   109:         f = tmp_path / \"proposals.md\"\n   110:         original = \"### ACCEPT belief-one\\nText\\n\"\n   111:         f.write_text(original)\n>> 112:         auto_accept_proposals(str(f))\n   113:         assert f.read_text() == original\n   114: \n   115: "
      }
    ],
    "production_count": 3,
    "test_count": 6,
    "total_count": 9
  },
  "json_beliefs_usages": {
    "symbol": "_json_beliefs",
    "usages": [
      {
        "file": "tests/test_propose.py",
        "line": 40,
        "text": "def _json_beliefs(*beliefs, accept=True):"
      },
      {
        "file": "tests/test_propose.py",
        "line": 62,
        "text": "return _json_beliefs((f\"belief-from-batch-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 86,
        "text": "return _json_beliefs((f\"belief-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 108,
        "text": "return _json_beliefs("
      },
      {
        "file": "tests/test_propose.py",
        "line": 138,
        "text": "return _json_beliefs((f\"belief-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 169,
        "text": "return _json_beliefs((\"retried-belief\", \"A belief from retry.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 190,
        "text": "return '```json\\n' + _json_beliefs((\"fenced-belief\", \"A belief.\")) + '\\n```'"
      },
      {
        "file": "tests/test_propose.py",
        "line": 214,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 237,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 260,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 280,
        "text": "return _json_beliefs((\"new-belief\", \"Fresh.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 305,
        "text": "return _json_beliefs((f\"belief-{call_count}\", f\"A belief from batch {call_count}.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 328,
        "text": "return _json_beliefs((\"good-belief\", \"A solid claim.\"), accept=True)"
      },
      {
        "file": "tests/test_propose.py",
        "line": 349,
        "text": "return _json_beliefs((\"weak-belief\", \"A vague claim.\"), accept=False)"
      }
    ],
    "production_usages": [],
    "test_usages": [
      {
        "file": "tests/test_propose.py",
        "line": 40,
        "text": "def _json_beliefs(*beliefs, accept=True):"
      },
      {
        "file": "tests/test_propose.py",
        "line": 62,
        "text": "return _json_beliefs((f\"belief-from-batch-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 86,
        "text": "return _json_beliefs((f\"belief-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 108,
        "text": "return _json_beliefs("
      },
      {
        "file": "tests/test_propose.py",
        "line": 138,
        "text": "return _json_beliefs((f\"belief-{call_count}\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 169,
        "text": "return _json_beliefs((\"retried-belief\", \"A belief from retry.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 190,
        "text": "return '```json\\n' + _json_beliefs((\"fenced-belief\", \"A belief.\")) + '\\n```'"
      },
      {
        "file": "tests/test_propose.py",
        "line": 214,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 237,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 260,
        "text": "return _json_beliefs((\"test-belief\", \"A belief.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 280,
        "text": "return _json_beliefs((\"new-belief\", \"Fresh.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 305,
        "text": "return _json_beliefs((f\"belief-{call_count}\", f\"A belief from batch {call_count}.\"))"
      },
      {
        "file": "tests/test_propose.py",
        "line": 328,
        "text": "return _json_beliefs((\"good-belief\", \"A solid claim.\"), accept=True)"
      },
      {
        "file": "tests/test_propose.py",
        "line": 349,
        "text": "return _json_beliefs((\"weak-belief\", \"A vague claim.\"), accept=False)"
      }
    ],
    "production_count": 0,
    "test_count": 14,
    "total_count": 14
  },
  "cmd_propose_beliefs_body": {
    "function": "cmd_propose_beliefs",
    "file": "expert_build/propose.py",
    "start_line": 263,
    "end_line": 459,
    "source": "def cmd_propose_beliefs(args):\n    \"\"\"Extract candidate beliefs from entries for human review.\"\"\"\n    from .caffeinate import hold as _caffeinate\n    _caffeinate()\n    input_dir = Path(args.input_dir)\n    if not input_dir.exists():\n        print(f\"Entries directory not found: {input_dir}\")\n        sys.exit(1)\n\n    if not check_model_available(args.model):\n        print(f\"Model not available: {args.model}\")\n        sys.exit(1)\n\n    # Collect entries\n    if hasattr(args, 'entry') and args.entry:\n        entries = [Path(p) for p in args.entry]\n    else:\n        entries = sorted(input_dir.rglob(\"*.md\"))\n\n    if not entries:\n        print(f\"No .md files found.\")\n        return\n\n    # Filter out already-processed entries (unless --all or --entry)\n    processed_path = Path(PROJECT_DIR) / \"proposed-entries.json\"\n    processed = _load_processed(processed_path)\n    process_all = getattr(args, 'all', False)\n    has_entry_flag = hasattr(args, 'entry') and args.entry\n\n    if not process_all and not has_entry_flag:\n        total = len(entries)\n        entries = _filter_unprocessed(entries, processed)\n        skipped = total - len(entries)\n        if skipped:\n            print(f\"Skipping {skipped} already-processed entries (use --all to reprocess)\")\n        if not entries:\n            print(\"No new entries to process.\")\n            return\n\n    # Load existing beliefs for dedup context\n    existing_beliefs = _load_existing_beliefs()\n    existing_ids = {b[\"id\"] for b in existing_beliefs}\n\n    if existing_ids:\n        print(f\"Found {len(existing_ids)} existing beliefs (will skip duplicates)\")\n\n    # Compute belief embeddings once (if fastembed available)\n    belief_vectors = None\n    if existing_beliefs and _has_embeddings():\n        print(\"Computing belief embeddings for semantic dedup...\")\n        cache_path = Path(PROJECT_DIR) / \"belief-vectors.json\"\n        belief_vectors = _get_belief_embeddings(existing_beliefs, cache_path)\n        print(f\"  {len(belief_vectors)} belief vectors ready\")\n    elif existing_beliefs:\n        print(\"(install fastembed for semantic dedup: uv pip install 'expert-agent-builder[embeddings]')\")\n\n    print(f\"Reading {len(entries)} entries...\")\n\n    # Batch entries \u2014 track paths per batch for relevance scoring\n    batches = []\n    batch_paths = []\n    current_batch = []\n    current_paths = []\n    for entry_path in entries:\n        content = entry_path.read_text()\n        if len(content) > 10000:\n            content = content[:10000] + \"\\n[Truncated]\"\n        source_url = \"\"\n        if content.startswith(\"---\"):\n            end = content.find(\"---\", 3)\n            if end != -1:\n                for line in content[3:end].splitlines():\n                    if line.startswith(\"source_url:\"):\n                        source_url = line.split(\":\", 1)[1].strip()\n                    elif line.startswith(\"source:\"):\n                        val = line.split(\":\", 1)[1].strip()\n                        if not source_url and val.startswith((\"http://\", \"https://\")):\n                            source_url = val\n        header = f\"--- FILE: {entry_path}\"\n        if source_url:\n            header += f\" | SOURCE_URL: {source_url}\"\n        header += \" ---\"\n        current_batch.append(f\"{header}\\n{content}\")\n        current_paths.append(str(entry_path))\n        if len(current_batch) >= args.batch_size:\n            batches.append(\"\\n\\n\".join(current_batch))\n            batch_paths.append(current_paths)\n            current_batch = []\n            current_paths = []\n    if current_batch:\n        batches.append(\"\\n\\n\".join(current_batch))\n        batch_paths.append(current_paths)\n\n    print(f\"Processing {len(batches)} batches (batch size: {args.batch_size})...\")\n\n    source_desc = (\", \".join(str(e) for e in entries)\n                   if has_entry_flag\n                   else f\"{len(entries)} entries from {input_dir}/\")\n    output = Path(args.output)\n\n    # Write header before first batch if starting a new file\n    appended = output.exists() and output.stat().st_size > 0\n    if not appended:\n        with output.open(\"w\") as f:\n            f.write(\"# Proposed Beliefs\\n\\n\")\n            f.write(\"Review each entry: change `[REJECT]` to `[ACCEPT]` to keep, or vice versa.\\n\")\n            f.write(\"Then run: `expert-build accept-beliefs`\\n\\n\")\n            f.write(\"---\\n\\n\")\n            f.write(f\"**Generated:** {date.today().isoformat()}\\n\")\n            f.write(f\"**Source:** {source_desc}\\n\")\n            f.write(f\"**Model:** {args.model}\\n\\n\")\n    else:\n        with output.open(\"a\") as f:\n            f.write(f\"\\n---\\n\\n\")\n            f.write(f\"**Generated:** {date.today().isoformat()}\\n\")\n            f.write(f\"**Source:** {source_desc}\\n\")\n            f.write(f\"**Model:** {args.model}\\n\\n\")\n\n    async def _process_batch(i, batch_text, semaphore):\n        \"\"\"Process one batch under concurrency limit. Returns (filtered, paths) or None.\"\"\"\n        async with semaphore:\n            print(f\"  Batch {i + 1}/{len(batches)}...\")\n            existing_context = _build_dedup_context(\n                existing_beliefs, batch_paths[i], batch_text,\n                belief_vectors=belief_vectors,\n            )\n            prompt = PROPOSE_BELIEFS.format(entries=batch_text) + existing_context\n            try:\n                result = await invoke(prompt, model=args.model, timeout=600)\n            except Exception as e:\n                print(f\"  ERROR: {e}\")\n                return None\n\n            beliefs = extract_json(result)\n            if not isinstance(beliefs, list):\n                print(\"    WARN: response not valid JSON, retrying...\", file=sys.stderr)\n                try:\n                    retry_response = await invoke(\n                        prompt + \"\\n\\n\" + result + \"\\n\\n\" + RETRY_JSON,\n                        model=args.model, timeout=600,\n                    )\n                    beliefs = extract_json(retry_response)\n                except Exception:\n                    pass\n            if not isinstance(beliefs, list):\n                print(\"    WARN: could not parse beliefs JSON, skipping batch\", file=sys.stderr)\n                return None\n\n            filtered = []\n            skipped = 0\n            for b in beliefs:\n                bid = b.get(\"id\", \"\")\n                if bid in existing_ids:\n                    skipped += 1\n                    continue\n                filtered.append(b)\n            return filtered, batch_paths[i], skipped\n\n    parallel = max(1, getattr(args, \"parallel\", 1))\n    semaphore = asyncio.Semaphore(parallel)\n\n    async def run_batches():\n        tasks = [_process_batch(i, bt, semaphore) for i, bt in enumerate(batches)]\n        return await asyncio.gather(*tasks)\n\n    batch_results = asyncio.run(run_batches())\n\n    total_skipped = 0\n    successful_entries = []\n    for result in batch_results:\n        if result is None:\n            continue\n        filtered, paths, skipped = result\n        total_skipped += skipped\n\n        with output.open(\"a\") as f:\n            for b in filtered:\n                bid = b.get(\"id\", \"unknown\")\n                claim = b.get(\"claim\", \"\")\n                source = b.get(\"source\", \"\")\n                source_url = b.get(\"source_url\", \"\")\n                verdict = \"[ACCEPT]\" if b.get(\"accept\", True) else \"[REJECT]\"\n                f.write(f\"### {verdict} {bid}\\n\")\n                f.write(f\"{claim}\\n\")\n                f.write(f\"- Source: {source}\\n\")\n                f.write(f\"- Source URL: {source_url or 'none'}\\n\\n\")\n\n        successful_entries.extend(Path(p) for p in paths)\n        _save_processed(processed_path, successful_entries, processed)\n\n    if total_skipped:\n        print(f\"  Filtered {total_skipped} already-accepted beliefs\")\n\n    print(f\"\\n{'Appended to' if appended else 'Wrote'} {output}\")\n\n    print(\"Review the file, mark entries as [ACCEPT] or [REJECT], then run:\")\n    print(\"  expert-build accept-beliefs\")"
  },
  "accept_reject_migration": {
    "old_name": "ACCEPT/REJECT",
    "old_name_usages": [
      {
        "file": "tests/test_pipeline.py",
        "line": 72,
        "text": "\"### [ACCEPT/REJECT] belief-one\\n\""
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 74,
        "text": "\"### [ACCEPT/REJECT] belief-two\\n\""
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 79,
        "text": "assert \"[ACCEPT/REJECT]\" not in text"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 87,
        "text": "\"### [ACCEPT/REJECT] needs-accept\\n\""
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 93,
        "text": "assert \"[ACCEPT/REJECT]\" not in text"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 170,
        "text": "proposals.write_text(\"### [ACCEPT/REJECT] test-belief\\nText\\n- Source: test\\n\")"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 181,
        "text": "assert \"[ACCEPT/REJECT]\" not in text"
      },
      {
        "file": "expert_build/propose.py",
        "line": 255,
        "text": "\"\"\"Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file.\"\"\""
      },
      {
        "file": "expert_build/propose.py",
        "line": 258,
        "text": "text = re.sub(r'\\[ACCEPT/REJECT\\]', '[ACCEPT]', text)"
      },
      {
        "file": "expert_build/propose.py",
        "line": 483,
        "text": "print(\"Edit the file and change [ACCEPT/REJECT] to [ACCEPT] for beliefs to keep.\")"
      }
    ],
    "old_name_count": 10,
    "stale_references": [
      {
        "file": "expert_build/propose.py",
        "line": 255,
        "text": "\"\"\"Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file.\"\"\""
      },
      {
        "file": "expert_build/propose.py",
        "line": 258,
        "text": "text = re.sub(r'\\[ACCEPT/REJECT\\]', '[ACCEPT]', text)"
      },
      {
        "file": "expert_build/propose.py",
        "line": 483,
        "text": "print(\"Edit the file and change [ACCEPT/REJECT] to [ACCEPT] for beliefs to keep.\")"
      }
    ],
    "stale_count": 3,
    "migration_complete": false,
    "new_name": "ACCEPT]",
    "new_name_usages": [
      {
        "file": "tests/test_propose.py",
        "line": 276,
        "text": "output.write_text(\"# Proposed Beliefs\\n\\n### [ACCEPT] prior-belief\\nOld.\\n\\n\")"
      },
      {
        "file": "tests/test_propose.py",
        "line": 321,
        "text": "\"\"\"LLM's accept=true produces [ACCEPT] in output.\"\"\""
      },
      {
        "file": "tests/test_propose.py",
        "line": 337,
        "text": "assert \"### [ACCEPT] good-belief\" in content"
      },
      {
        "file": "tests/test_propose.py",
        "line": 359,
        "text": "assert \"### [ACCEPT]\" not in content"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 80,
        "text": "assert text.count(\"[ACCEPT]\") == 2"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 85,
        "text": "\"### [ACCEPT] already-good\\n\""
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 92,
        "text": "assert text.count(\"[ACCEPT]\") == 2"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 98,
        "text": "\"### [ACCEPT] good-belief\\n\""
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 105,
        "text": "assert text.count(\"[ACCEPT]\") == 2"
      },
      {
        "file": "tests/test_pipeline.py",
        "line": 180,
        "text": "assert \"[ACCEPT]\" in text"
      },
      {
        "file": "expert_build/propose.py",
        "line": 255,
        "text": "\"\"\"Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file.\"\"\""
      },
      {
        "file": "expert_build/propose.py",
        "line": 258,
        "text": "text = re.sub(r'\\[ACCEPT/REJECT\\]', '[ACCEPT]', text)"
      },
      {
        "file": "expert_build/propose.py",
        "line": 259,
        "text": "text = re.sub(r'\\[REJECT\\]', '[ACCEPT]', text)"
      },
      {
        "file": "expert_build/propose.py",
        "line": 368,
        "text": "f.write(\"Review each entry: change `[REJECT]` to `[ACCEPT]` to keep, or vice versa.\\n\")"
      },
      {
        "file": "expert_build/propose.py",
        "line": 444,
        "text": "verdict = \"[ACCEPT]\" if b.get(\"accept\", True) else \"[REJECT]\""
      },
      {
        "file": "expert_build/propose.py",
        "line": 458,
        "text": "print(\"Review the file, mark entries as [ACCEPT] or [REJECT], then run:\")"
      },
      {
        "file": "expert_build/propose.py",
        "line": 472,
        "text": "# Parse accepted beliefs \u2014 tolerate both ### [ACCEPT] and ### ACCEPT"
      },
      {
        "file": "expert_build/propose.py",
        "line": 482,
        "text": "print(\"No [ACCEPT] entries found in proposals file.\")"
      },
      {
        "file": "expert_build/propose.py",
        "line": 483,
        "text": "print(\"Edit the file and change [ACCEPT/REJECT] to [ACCEPT] for beliefs to keep.\")"
      },
      {
        "file": "expert_build/pipeline.py",
        "line": 150,
        "text": "print(\"Review proposed-beliefs.md, mark entries as [ACCEPT], then run:\", file=sys.stderr)"
      }
    ],
    "new_name_count": 20
  },
  "accept_beliefs_body": {
    "function": "cmd_accept_beliefs",
    "file": "expert_build/propose.py",
    "start_line": 462,
    "end_line": 513,
    "source": "def cmd_accept_beliefs(args):\n    \"\"\"Import accepted beliefs from proposals file.\"\"\"\n    proposals_file = Path(args.file)\n    if not proposals_file.exists():\n        print(f\"Proposals file not found: {proposals_file}\")\n        print(\"Run: expert-build propose-beliefs\")\n        sys.exit(1)\n\n    text = proposals_file.read_text()\n\n    # Parse accepted beliefs \u2014 tolerate both ### [ACCEPT] and ### ACCEPT\n    pattern = re.compile(\n        r\"### \\[?ACCEPT\\]? (\\S+)\\n\"\n        r\"(.+?)\\n\"\n        r\"- Source: (.+?)\\n\"\n        r\"(?:- Source URL: (.+?)\\n)?\"\n    )\n    matches = pattern.findall(text)\n\n    if not matches:\n        print(\"No [ACCEPT] entries found in proposals file.\")\n        print(\"Edit the file and change [ACCEPT/REJECT] to [ACCEPT] for beliefs to keep.\")\n        return\n\n    print(f\"Found {len(matches)} accepted beliefs\")\n\n    added = 0\n    failed = 0\n    for match in matches:\n        belief_id, claim_text, source = match[0], match[1], match[2]\n        source_url = match[3] if len(match) > 3 else \"\"\n        if source_url and source_url.lower() == \"none\":\n            source_url = \"\"\n        try:\n            add_node(\n                node_id=belief_id,\n                text=claim_text.strip(),\n                source=source.strip(),\n                source_url=source_url.strip() if source_url else \"\",\n                db_path=REASONS_DB,\n            )\n            print(f\"  Added: {belief_id}\")\n            added += 1\n        except Exception as e:\n            err = str(e)\n            if \"already exists\" in err.lower() or \"duplicate\" in err.lower():\n                print(f\"  EXISTS: {belief_id}\")\n            else:\n                print(f\"  FAIL: {belief_id}: {err}\")\n                failed += 1\n\n    print(f\"\\nAccepted {added} beliefs ({failed} failed)\")"
  },
  "auto_accept_full_body": {
    "function": "auto_accept_proposals",
    "file": "expert_build/propose.py",
    "start_line": 254,
    "end_line": 260,
    "source": "def auto_accept_proposals(filepath: str):\n    \"\"\"Rewrite all [ACCEPT/REJECT] and [REJECT] markers to [ACCEPT] in a proposals file.\"\"\"\n    path = Path(filepath)\n    text = path.read_text()\n    text = re.sub(r'\\[ACCEPT/REJECT\\]', '[ACCEPT]', text)\n    text = re.sub(r'\\[REJECT\\]', '[ACCEPT]', text)\n    path.write_text(text)"
  },
  "propose_tests": {
    "source_file": "expert_build/propose.py",
    "test_files": [
      {
        "path": "tests/test_pipelinepy",
        "exists": false
      },
      {
        "path": "tests/test_propose.py",
        "exists": true,
        "line_count": 359
      },
      {
        "path": "tests/test_proposepy",
        "exists": false
      }
    ],
    "test_count": 3
  }
}
```

Use these results to inform your review. Do not request the same observations again.


## Instructions

For each significant change (new file, modified function, etc.), provide a structured verdict.

Use this exact format for each change:

### <file_path or file_path:function_name>
VERDICT: PASS | CONCERN | BLOCK
CORRECTNESS: VALID | QUESTIONABLE | BROKEN
SPEC_COMPLIANCE: MEETS | PARTIAL | VIOLATES | N/A
ISSUE_COMPLIANCE: ADDRESSES | PARTIAL | UNRELATED | N/A
BELIEF_COMPLIANCE: CONSISTENT | VIOLATES | N/A
TEST_COVERAGE: COVERED | PARTIAL | UNTESTED
INTEGRATION: WIRED | PARTIAL | MISSING
REASONING: <brief explanation of your assessment>
---

## Review Criteria

1. **CORRECTNESS**: Does the code do what it claims? Is the logic sound?
   - VALID: Logic is correct, no bugs apparent
   - QUESTIONABLE: Logic may have edge cases or unclear behavior
   - BROKEN: Clear bugs or incorrect behavior

2. **SPEC_COMPLIANCE**: Does it meet MUST requirements from the spec?
   - MEETS: All relevant spec requirements satisfied
   - PARTIAL: Some requirements met, others missing or incomplete
   - VIOLATES: Contradicts spec requirements
   - N/A: No spec provided or not applicable

3. **ISSUE_COMPLIANCE** (only when an issue is provided): Do the changes address the problem or feature described in the issue?
   - ADDRESSES: Changes directly solve the issue's stated problem or implement the requested feature
   - PARTIAL: Changes partially address the issue but leave some aspects unresolved
   - UNRELATED: Changes do not appear related to the issue
   - N/A: No issue provided

4. **TEST_COVERAGE**: Are there tests for the new/changed code?
   - COVERED: Tests exist and cover the changes
   - PARTIAL: Some tests exist but coverage is incomplete
   - UNTESTED: No tests for the changes

5. **INTEGRATION**: Are callers updated? Is the feature usable end-to-end?
   - WIRED: Feature is fully integrated and usable
   - PARTIAL: Interface exists but callers not updated, or integration incomplete
   - MISSING: No integration with existing code

6. **BELIEF_COMPLIANCE** (only when beliefs are provided): Do the changes respect known architectural invariants, contracts, and rules?
   - CONSISTENT: Changes align with or reinforce known beliefs
   - VIOLATES: Changes contradict a specific belief — cite the belief ID
   - N/A: No beliefs provided or no relevant beliefs apply

## Verdict Guidelines

- **BLOCK**: Security issues, broken functionality, spec violations, or missing critical integration
- **CONCERN**: Missing tests, partial integration, questionable patterns, or unclear logic
- **PASS**: Correct, tested, well-integrated code

## Important

- Full function bodies for modified functions may be available in the observations section — use them to verify the complete logic, not just the diff hunks
- Related test files (prefixed with ``related_test:``) may be included in observations — check whether existing test assertions still match modified return types, signatures, or behavior. Flag any test that would break due to the changes
- If duplicate test coverage is detected (multiple test files covering the same source), note it in your review
- Focus on actual issues, not style preferences
- If a method signature is added but callers aren't updated, that's PARTIAL integration
- Be specific in reasoning - reference line numbers or function names
- When in doubt, use CONCERN rather than PASS

## Self-Review

After completing your review, add a brief self-assessment:

### SELF_REVIEW
LIMITATIONS: <what context were you missing that affected review quality?>
---

Examples of limitations:
- "Could not see full class to verify no other methods access the modified field"
- "Test file not included in diff - cannot verify coverage claims"
- "Spec file referenced but not provided"


## Feature Requests

If this review tool could be improved to help you do a better job, suggest features:

### FEATURE_REQUESTS
- <suggestion 1>
- <suggestion 2>
---

Examples:
- "Include full file context for modified functions, not just diff hunks"
- "Show callers of modified methods to verify integration"
- "Include test file alongside implementation changes"

Only include this section if you have specific suggestions. Skip if none.
