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/propose.py b/expert_build/propose.py
index d38db8a..4320712 100644
--- a/expert_build/propose.py
+++ b/expert_build/propose.py
@@ -332,6 +332,10 @@ def cmd_propose_beliefs(args):
                 for line in content[3:end].splitlines():
                     if line.startswith("source_url:"):
                         source_url = line.split(":", 1)[1].strip()
+                    elif line.startswith("source:"):
+                        val = line.split(":", 1)[1].strip()
+                        if not source_url and val.startswith(("http://", "https://")):
+                            source_url = val
         header = f"--- FILE: {entry_path}"
         if source_url:
             header += f" | SOURCE_URL: {source_url}"
diff --git a/expert_build/summarize.py b/expert_build/summarize.py
index e0d6e0b..e88ef7a 100644
--- a/expert_build/summarize.py
+++ b/expert_build/summarize.py
@@ -1,8 +1,8 @@
 """Summarize source documents into entries using an LLM."""
 
 import re
-import subprocess
 import sys
+from datetime import date
 from pathlib import Path
 
 from .llm import check_model_available, invoke_sync
@@ -63,6 +63,9 @@ def cmd_summarize(args):
                 for line in frontmatter.splitlines():
                     if line.startswith("source_url:"):
                         source_url = line.split(":", 1)[1].strip()
+                    elif line.startswith("source:"):
+                        if not source_url:
+                            source_url = line.split(":", 1)[1].strip()
                     elif line.startswith("source_id:"):
                         source_id = line.split(":", 1)[1].strip()
                 content = content[end + 3:].strip()
@@ -96,41 +99,21 @@ def cmd_summarize(args):
         title = title_match.group(1) if title_match else source_path.stem.replace("-", " ").title()
         topic = source_path.stem
 
-        # Create entry via entry CLI
-        entry_path = None
-        try:
-            result = subprocess.run(
-                ["entry", "create", topic, title, "--content", summary],
-                capture_output=True, text=True,
-            )
-            if result.returncode == 0:
-                entry_path = result.stdout.strip().replace("Created ", "")
-                print(f"  -> {result.stdout.strip()}")
-            else:
-                # Try alternative invocation
-                result = subprocess.run(
-                    ["entry", "create", topic, title],
-                    input=summary,
-                    capture_output=True, text=True,
-                )
-                if result.returncode == 0:
-                    entry_path = result.stdout.strip().replace("Created ", "")
-                    print(f"  -> {result.stdout.strip()}")
-                else:
-                    print(f"  WARN: entry create failed: {result.stderr.strip()}")
-        except FileNotFoundError:
-            print("  ERROR: entry CLI not found. Install with: uv tool install entry")
-            sys.exit(1)
-
-        # Prepend source provenance frontmatter to the entry file
-        if entry_path and source_url:
-            ep = Path(entry_path)
-            if ep.exists():
-                fm = f"---\nsource_url: {source_url}\n"
-                if source_id:
-                    fm += f"source_id: {source_id}\n"
-                fm += "---\n\n"
-                ep.write_text(fm + ep.read_text())
+        # Write entry directly with provenance frontmatter
+        today = date.today()
+        entry_dir = Path("entries") / str(today.year) / f"{today.month:02d}" / f"{today.day:02d}"
+        entry_dir.mkdir(parents=True, exist_ok=True)
+        entry_path = entry_dir / f"{topic}.md"
+
+        fm_lines = [f"source: {source_path}"]
+        if source_url:
+            fm_lines.append(f"source_url: {source_url}")
+        if source_id:
+            fm_lines.append(f"source_id: {source_id}")
+        frontmatter = "---\n" + "\n".join(fm_lines) + "\n---\n\n"
+
+        entry_path.write_text(frontmatter + summary + "\n")
+        print(f"  -> Created {entry_path}")
 
         # Record as done
         with manifest.open("a") as f:
diff --git a/tests/test_propose.py b/tests/test_propose.py
index 77cc30a..ecadd13 100644
--- a/tests/test_propose.py
+++ b/tests/test_propose.py
@@ -198,6 +198,75 @@ def invoke_side_effect(prompt, model=None, timeout=None):
     assert "fenced-belief" in content
 
 
+def test_source_url_extracted_from_entry_frontmatter(entries_dir, work_dir):
+    """source_url from entry frontmatter is passed in batch header."""
+    fm = "---\nsource_url: https://example.com/doc\n---\n\n# Entry\nContent"
+    (entries_dir / "entry0.md").write_text(fm)
+
+    output = work_dir / "proposed-beliefs.md"
+    args = make_args(entries_dir, output=str(output), batch_size=5)
+
+    captured_prompt = None
+    def invoke_side_effect(prompt, model=None, timeout=None):
+        nonlocal captured_prompt
+        captured_prompt = prompt
+        return _json_beliefs(("test-belief", "A belief."))
+
+    with patch("expert_build.propose.check_model_available", return_value=True), \
+         patch("expert_build.propose.invoke_sync", 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)
+
+    assert "SOURCE_URL: https://example.com/doc" in captured_prompt
+
+
+def test_source_key_url_extracted_from_entry_frontmatter(entries_dir, work_dir):
+    """source: with URL value is used as SOURCE_URL in batch header."""
+    fm = "---\nsource: https://example.com/page\n---\n\n# Entry\nContent"
+    (entries_dir / "entry0.md").write_text(fm)
+
+    output = work_dir / "proposed-beliefs.md"
+    args = make_args(entries_dir, output=str(output), batch_size=5)
+
+    captured_prompt = None
+    def invoke_side_effect(prompt, model=None, timeout=None):
+        nonlocal captured_prompt
+        captured_prompt = prompt
+        return _json_beliefs(("test-belief", "A belief."))
+
+    with patch("expert_build.propose.check_model_available", return_value=True), \
+         patch("expert_build.propose.invoke_sync", 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)
+
+    assert "SOURCE_URL: https://example.com/page" in captured_prompt
+
+
+def test_source_key_local_path_not_used_as_url(entries_dir, work_dir):
+    """source: with local file path is NOT used as SOURCE_URL."""
+    fm = "---\nsource: sources/doc.md\n---\n\n# Entry\nContent"
+    (entries_dir / "entry0.md").write_text(fm)
+
+    output = work_dir / "proposed-beliefs.md"
+    args = make_args(entries_dir, output=str(output), batch_size=5)
+
+    captured_prompt = None
+    def invoke_side_effect(prompt, model=None, timeout=None):
+        nonlocal captured_prompt
+        captured_prompt = prompt
+        return _json_beliefs(("test-belief", "A belief."))
+
+    with patch("expert_build.propose.check_model_available", return_value=True), \
+         patch("expert_build.propose.invoke_sync", 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)
+
+    assert "| SOURCE_URL:" not in captured_prompt
+
+
 def test_appends_to_existing_output_file(entries_dir, work_dir):
     """When output file already exists, new proposals are appended."""
     (entries_dir / "entry0.md").write_text("# Entry\nContent")
diff --git a/tests/test_summarize.py b/tests/test_summarize.py
index 5d4d0c1..ff3457d 100644
--- a/tests/test_summarize.py
+++ b/tests/test_summarize.py
@@ -2,7 +2,7 @@
 
 import types
 from pathlib import Path
-from unittest.mock import patch, MagicMock
+from unittest.mock import patch
 
 import pytest
 
@@ -33,6 +33,13 @@ def make_args(input_dir, model="test-model", limit=None):
     return types.SimpleNamespace(input_dir=str(input_dir), model=model, limit=limit)
 
 
+def _find_entry(work_dir):
+    """Find the generated entry file under entries/."""
+    entries = list((work_dir / "entries").rglob("*.md"))
+    assert len(entries) == 1, f"Expected 1 entry, found {len(entries)}: {entries}"
+    return entries[0]
+
+
 # --- File discovery tests ---
 
 def test_discovers_md_files(source_dir, work_dir):
@@ -40,12 +47,11 @@ def test_discovers_md_files(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Topic Title\nSummary"), \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/doc.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Topic Title\nSummary"):
         cmd_summarize(args)
 
-    assert mock_run.called
+    entry = _find_entry(work_dir)
+    assert "Summary" in entry.read_text()
 
 
 def test_discovers_py_files(source_dir, work_dir):
@@ -53,12 +59,11 @@ def test_discovers_py_files(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Module\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/module.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Module\nSummary"):
         cmd_summarize(args)
 
-    assert mock_llm.called
+    entry = _find_entry(work_dir)
+    assert "Summary" in entry.read_text()
 
 
 def test_discovers_both_md_and_py(source_dir, work_dir):
@@ -66,14 +71,13 @@ def test_discovers_both_md_and_py(source_dir, work_dir):
     (source_dir / "beta.py").write_text("x = 1")
     args = make_args(source_dir)
 
-    calls = []
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/x.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary") as mock_llm:
         cmd_summarize(args)
 
     assert mock_llm.call_count == 2
+    entries = list((work_dir / "entries").rglob("*.md"))
+    assert len(entries) == 2
 
 
 def test_ignores_other_extensions(source_dir, work_dir):
@@ -95,9 +99,7 @@ def test_uses_summarize_code_for_py(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Module\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/module.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Module\nSummary") as mock_llm:
         cmd_summarize(args)
 
     prompt = mock_llm.call_args[0][0]
@@ -109,9 +111,7 @@ def test_uses_summarize_for_md(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Doc Title\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/doc.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Doc Title\nSummary") as mock_llm:
         cmd_summarize(args)
 
     prompt = mock_llm.call_args[0][0]
@@ -125,9 +125,7 @@ def test_truncation_warning_for_large_file(source_dir, work_dir, capsys):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Big Doc\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/big.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Big Doc\nSummary"):
         cmd_summarize(args)
 
     captured = capsys.readouterr()
@@ -140,9 +138,7 @@ def test_truncation_content_is_capped(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Big\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/big.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Big\nSummary") as mock_llm:
         cmd_summarize(args)
 
     prompt = mock_llm.call_args[0][0]
@@ -155,9 +151,7 @@ def test_no_truncation_warning_for_small_file(source_dir, work_dir, capsys):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Small\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/small.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Small\nSummary"):
         cmd_summarize(args)
 
     captured = capsys.readouterr()
@@ -184,9 +178,7 @@ def test_manifest_records_processed_file(source_dir, work_dir):
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary"), \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/doc.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary"):
         cmd_summarize(args)
 
     manifest = work_dir / ".summarized"
@@ -197,14 +189,26 @@ def test_manifest_records_processed_file(source_dir, work_dir):
 # --- Frontmatter stripping tests ---
 
 def test_strips_frontmatter_before_summarizing(source_dir, work_dir):
+    content = "---\nsource: https://example.com\n---\n\nActual content here"
+    (source_dir / "doc.md").write_text(content)
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary") as mock_llm:
+        cmd_summarize(args)
+
+    prompt = mock_llm.call_args[0][0]
+    assert "source:" not in prompt
+    assert "Actual content here" in prompt
+
+
+def test_strips_source_url_frontmatter(source_dir, work_dir):
     content = "---\nsource_url: https://example.com\n---\n\nActual content here"
     (source_dir / "doc.md").write_text(content)
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
-         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary") as mock_llm, \
-         patch("subprocess.run") as mock_run:
-        mock_run.return_value = MagicMock(returncode=0, stdout="Created entries/doc.md", stderr="")
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary") as mock_llm:
         cmd_summarize(args)
 
     prompt = mock_llm.call_args[0][0]
@@ -213,7 +217,7 @@ def test_strips_frontmatter_before_summarizing(source_dir, work_dir):
 
 
 def test_skips_empty_content_after_frontmatter(source_dir, work_dir, capsys):
-    (source_dir / "empty.md").write_text("---\nsource_url: https://example.com\n---\n\n")
+    (source_dir / "empty.md").write_text("---\nsource: https://example.com\n---\n\n")
     args = make_args(source_dir)
 
     with patch("expert_build.summarize.check_model_available", return_value=True), \
@@ -225,6 +229,83 @@ def test_skips_empty_content_after_frontmatter(source_dir, work_dir, capsys):
     assert "SKIP" in captured.out
 
 
+# --- Provenance frontmatter tests ---
+
+def test_entry_has_source_frontmatter(source_dir, work_dir):
+    """Generated entry includes source path in frontmatter."""
+    (source_dir / "doc.md").write_text("# Hello\nContent")
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary"):
+        cmd_summarize(args)
+
+    entry = _find_entry(work_dir)
+    content = entry.read_text()
+    assert content.startswith("---\n")
+    assert f"source: {source_dir}/doc.md" in content
+
+
+def test_entry_has_source_url_from_fetch_frontmatter(source_dir, work_dir):
+    """source: URL from fetch-docs frontmatter propagates as source_url."""
+    fm = "---\nsource: https://example.com/docs/page\nfetched: 2026-06-04\n---\n\nDoc content"
+    (source_dir / "page.md").write_text(fm)
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## Page\nSummary"):
+        cmd_summarize(args)
+
+    entry = _find_entry(work_dir)
+    content = entry.read_text()
+    assert "source_url: https://example.com/docs/page" in content
+
+
+def test_entry_has_source_id_when_present(source_dir, work_dir):
+    """source_id propagates from source frontmatter to entry."""
+    fm = "---\nsource_url: https://example.com\nsource_id: abc123\n---\n\nContent"
+    (source_dir / "doc.md").write_text(fm)
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary"):
+        cmd_summarize(args)
+
+    entry = _find_entry(work_dir)
+    content = entry.read_text()
+    assert "source_url: https://example.com" in content
+    assert "source_id: abc123" in content
+
+
+def test_entry_contains_llm_summary(source_dir, work_dir):
+    """The LLM summary is written as the entry body."""
+    (source_dir / "doc.md").write_text("# Hello\nContent")
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## My Title\nDetailed summary here"):
+        cmd_summarize(args)
+
+    entry = _find_entry(work_dir)
+    content = entry.read_text()
+    assert "Detailed summary here" in content
+
+
+def test_entry_directory_structure(source_dir, work_dir):
+    """Entries are written to entries/YYYY/MM/DD/topic.md."""
+    (source_dir / "my-topic.md").write_text("# Topic\nContent")
+    args = make_args(source_dir)
+
+    with patch("expert_build.summarize.check_model_available", return_value=True), \
+         patch("expert_build.summarize.invoke_sync", return_value="## Title\nSummary"):
+        cmd_summarize(args)
+
+    entry = _find_entry(work_dir)
+    assert entry.name == "my-topic.md"
+    parts = entry.relative_to(work_dir).parts
+    assert parts[0] == "entries"
+
+
 # --- Prompt template tests ---
 
 def test_summarize_template_requests_descriptive_title():

```

## Observation Results

You previously requested observations. Here are the results:

```json
{
  "cmd_summarize_full": {
    "function": "cmd_summarize",
    "file": "expert_build/summarize.py",
    "start_line": 12,
    "end_line": 125,
    "source": "def cmd_summarize(args):\n    \"\"\"Generate entries from source documents.\"\"\"\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\"Source directory not found: {input_dir}\")\n        print(\"Run: expert-build fetch-docs <url>\")\n        sys.exit(1)\n\n    if not check_model_available(args.model):\n        print(f\"Model not available: {args.model}\")\n        print(\"Install claude CLI or specify --model\")\n        sys.exit(1)\n\n    sources = sorted(\n        [*input_dir.glob(\"*.md\"), *input_dir.glob(\"*.py\")],\n        key=lambda p: p.name,\n    )\n    if not sources:\n        print(f\"No .md or .py files in {input_dir}\")\n        return\n\n    if args.limit:\n        sources = sources[:args.limit]\n\n    # Track what's been summarized\n    manifest = Path(\".summarized\")\n    done = set()\n    if manifest.exists():\n        done = set(manifest.read_text().strip().split(\"\\n\"))\n\n    processed = 0\n    skipped = 0\n\n    for source_path in sources:\n        if str(source_path) in done:\n            skipped += 1\n            continue\n\n        print(f\"Summarizing: {source_path.name}\")\n\n        content = source_path.read_text()\n\n        # Extract and strip frontmatter\n        source_url = None\n        source_id = None\n        if content.startswith(\"---\"):\n            end = content.find(\"---\", 3)\n            if end != -1:\n                frontmatter = content[3:end]\n                for line in frontmatter.splitlines():\n                    if line.startswith(\"source_url:\"):\n                        source_url = line.split(\":\", 1)[1].strip()\n                    elif line.startswith(\"source:\"):\n                        if not source_url:\n                            source_url = line.split(\":\", 1)[1].strip()\n                    elif line.startswith(\"source_id:\"):\n                        source_id = line.split(\":\", 1)[1].strip()\n                content = content[end + 3:].strip()\n\n        if not content.strip():\n            print(f\"  SKIP (empty)\")\n            continue\n\n        # Truncate very long documents\n        if len(content) > 30000:\n            original_len = len(content)\n            content = content[:30000] + \"\\n\\n[Truncated \u2014 original was longer]\"\n            if source_path.suffix == \".pdf\":\n                print(f\"  WARN: truncated from {original_len} to 30000 chars. \"\n                      f\"Consider: expert-build chunk-pdf {source_path}\")\n            else:\n                print(f\"  WARN: truncated from {original_len} to 30000 chars. \"\n                      f\"Large documents may lose tail content.\")\n\n        template = SUMMARIZE_CODE if source_path.suffix == \".py\" else SUMMARIZE\n        prompt = template.format(content=content)\n\n        try:\n            summary = invoke_sync(prompt, model=args.model)\n        except Exception as e:\n            print(f\"  ERROR: {e}\")\n            continue\n\n        # Extract a title from the summary or source filename\n        title_match = re.search(r\"^#+ (.+)$\", summary, re.MULTILINE)\n        title = title_match.group(1) if title_match else source_path.stem.replace(\"-\", \" \").title()\n        topic = source_path.stem\n\n        # Write entry directly with provenance frontmatter\n        today = date.today()\n        entry_dir = Path(\"entries\") / str(today.year) / f\"{today.month:02d}\" / f\"{today.day:02d}\"\n        entry_dir.mkdir(parents=True, exist_ok=True)\n        entry_path = entry_dir / f\"{topic}.md\"\n\n        fm_lines = [f\"source: {source_path}\"]\n        if source_url:\n            fm_lines.append(f\"source_url: {source_url}\")\n        if source_id:\n            fm_lines.append(f\"source_id: {source_id}\")\n        frontmatter = \"---\\n\" + \"\\n\".join(fm_lines) + \"\\n---\\n\\n\"\n\n        entry_path.write_text(frontmatter + summary + \"\\n\")\n        print(f\"  -> Created {entry_path}\")\n\n        # Record as done\n        with manifest.open(\"a\") as f:\n            f.write(f\"{source_path}\\n\")\n        done.add(str(source_path))\n\n        processed += 1\n\n    print(f\"\\nSummarized {processed} sources ({skipped} already done)\")"
  },
  "cmd_propose_beliefs_full": {
    "function": "cmd_propose_beliefs",
    "file": "expert_build/propose.py",
    "start_line": 261,
    "end_line": 443,
    "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(\"Edit each entry: change `[ACCEPT/REJECT]` to `[ACCEPT]` or `[REJECT]`.\\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    total_skipped = 0\n    successful_entries = []\n    for i, batch_text in enumerate(batches):\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 = invoke_sync(prompt, model=args.model, timeout=600)\n        except Exception as e:\n            print(f\"  ERROR: {e}\")\n            continue\n\n        # Parse JSON response\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 = invoke_sync(\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            continue\n\n        # Filter out proposals whose IDs already exist\n        skipped = 0\n        filtered = []\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        total_skipped += skipped\n\n        # Write this batch's proposals as markdown for human review\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                f.write(f\"### [ACCEPT/REJECT] {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        # Record this batch's entries as processed\n        successful_entries.extend(Path(p) for p in batch_paths[i])\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\")"
  },
  "summarize_imports": {
    "file": "expert_build/summarize.py",
    "imports": [
      "re",
      "sys"
    ],
    "from_imports": [
      {
        "module": "datetime",
        "names": [
          "date"
        ]
      },
      {
        "module": "pathlib",
        "names": [
          "Path"
        ]
      },
      {
        "module": "llm",
        "names": [
          "check_model_available",
          "invoke_sync"
        ]
      },
      {
        "module": "prompts",
        "names": [
          "SUMMARIZE",
          "SUMMARIZE_CODE"
        ]
      }
    ],
    "import_section": "\"\"\"Summarize source documents into entries using an LLM.\"\"\"\n\nimport re\nimport sys\nfrom datetime import date\nfrom pathlib import Path\n\nfrom .llm import check_model_available, invoke_sync\nfrom .prompts import SUMMARIZE, SUMMARIZE_CODE\n\n\ndef cmd_summarize(args):\n    \"\"\"Generate entries from source documents.\"\"\"\n    from .caffeinate import hold as _caffeinate"
  },
  "cmd_summarize_callers": {
    "symbol": "cmd_summarize",
    "production_callers": [
      {
        "file": "expert_build/summarize.py",
        "line": 12,
        "text": "def cmd_summarize(args):"
      },
      {
        "file": "expert_build/cli.py",
        "line": 132,
        "text": "\"summarize\": lambda a: _lazy(\"summarize\", \"cmd_summarize\")(a),"
      },
      {
        "file": "expert_build/pipeline.py",
        "line": 124,
        "text": "from .summarize import cmd_summarize"
      },
      {
        "file": "expert_build/pipeline.py",
        "line": 130,
        "text": "cmd_summarize(sum_args)"
      }
    ],
    "test_callers": [
      {
        "file": "tests/test_summarize.py",
        "line": 9,
        "text": "from expert_build.summarize import cmd_summarize",
        "context_function": null,
        "context_snippet": "   6: \n   7: import pytest\n   8: \n>> 9: from expert_build.summarize import cmd_summarize\n   10: from expert_build.prompts import SUMMARIZE, SUMMARIZE_CODE\n   11: \n   12: "
      },
      {
        "file": "tests/test_summarize.py",
        "line": 51,
        "text": "cmd_summarize(args)",
        "context_function": "test_discovers_md_files",
        "context_snippet": "   48: \n   49:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   50:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Topic Title\\nSummary\"):\n>> 51:         cmd_summarize(args)\n   52: \n   53:     entry = _find_entry(work_dir)\n   54:     assert \"Summary\" in entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 63,
        "text": "cmd_summarize(args)",
        "context_function": "test_discovers_py_files",
        "context_snippet": "   60: \n   61:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   62:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Module\\nSummary\"):\n>> 63:         cmd_summarize(args)\n   64: \n   65:     entry = _find_entry(work_dir)\n   66:     assert \"Summary\" in entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 76,
        "text": "cmd_summarize(args)",
        "context_function": "test_discovers_both_md_and_py",
        "context_snippet": "   73: \n   74:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   75:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\") as mock_llm:\n>> 76:         cmd_summarize(args)\n   77: \n   78:     assert mock_llm.call_count == 2\n   79:     entries = list((work_dir / \"entries\").rglob(\"*.md\"))"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 90,
        "text": "cmd_summarize(args)",
        "context_function": "test_ignores_other_extensions",
        "context_snippet": "   87: \n   88:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   89:          patch(\"expert_build.summarize.invoke_sync\") as mock_llm:\n>> 90:         cmd_summarize(args)\n   91: \n   92:     assert not mock_llm.called\n   93: "
      },
      {
        "file": "tests/test_summarize.py",
        "line": 103,
        "text": "cmd_summarize(args)",
        "context_function": "test_uses_summarize_code_for_py",
        "context_snippet": "   100: \n   101:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   102:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Module\\nSummary\") as mock_llm:\n>> 103:         cmd_summarize(args)\n   104: \n   105:     prompt = mock_llm.call_args[0][0]\n   106:     assert \"source code\" in prompt.lower()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 115,
        "text": "cmd_summarize(args)",
        "context_function": "test_uses_summarize_for_md",
        "context_snippet": "   112: \n   113:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   114:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Doc Title\\nSummary\") as mock_llm:\n>> 115:         cmd_summarize(args)\n   116: \n   117:     prompt = mock_llm.call_args[0][0]\n   118:     assert \"documentation page\" in prompt.lower()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 129,
        "text": "cmd_summarize(args)",
        "context_function": "test_truncation_warning_for_large_file",
        "context_snippet": "   126: \n   127:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   128:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Big Doc\\nSummary\"):\n>> 129:         cmd_summarize(args)\n   130: \n   131:     captured = capsys.readouterr()\n   132:     assert \"WARN: truncated from 50000 to 30000 chars\" in captured.out"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 142,
        "text": "cmd_summarize(args)",
        "context_function": "test_truncation_content_is_capped",
        "context_snippet": "   139: \n   140:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   141:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Big\\nSummary\") as mock_llm:\n>> 142:         cmd_summarize(args)\n   143: \n   144:     prompt = mock_llm.call_args[0][0]\n   145:     assert \"[Truncated\" in prompt"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 155,
        "text": "cmd_summarize(args)",
        "context_function": "test_no_truncation_warning_for_small_file",
        "context_snippet": "   152: \n   153:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   154:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Small\\nSummary\"):\n>> 155:         cmd_summarize(args)\n   156: \n   157:     captured = capsys.readouterr()\n   158:     assert \"WARN\" not in captured.out"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 171,
        "text": "cmd_summarize(args)",
        "context_function": "test_skips_already_summarized",
        "context_snippet": "   168: \n   169:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   170:          patch(\"expert_build.summarize.invoke_sync\") as mock_llm:\n>> 171:         cmd_summarize(args)\n   172: \n   173:     assert not mock_llm.called\n   174: "
      },
      {
        "file": "tests/test_summarize.py",
        "line": 182,
        "text": "cmd_summarize(args)",
        "context_function": "test_manifest_records_processed_file",
        "context_snippet": "   179: \n   180:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   181:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\"):\n>> 182:         cmd_summarize(args)\n   183: \n   184:     manifest = work_dir / \".summarized\"\n   185:     assert manifest.exists()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 198,
        "text": "cmd_summarize(args)",
        "context_function": "test_strips_frontmatter_before_summarizing",
        "context_snippet": "   195: \n   196:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   197:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\") as mock_llm:\n>> 198:         cmd_summarize(args)\n   199: \n   200:     prompt = mock_llm.call_args[0][0]\n   201:     assert \"source:\" not in prompt"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 212,
        "text": "cmd_summarize(args)",
        "context_function": "test_strips_source_url_frontmatter",
        "context_snippet": "   209: \n   210:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   211:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\") as mock_llm:\n>> 212:         cmd_summarize(args)\n   213: \n   214:     prompt = mock_llm.call_args[0][0]\n   215:     assert \"source_url\" not in prompt"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 225,
        "text": "cmd_summarize(args)",
        "context_function": "test_skips_empty_content_after_frontmatter",
        "context_snippet": "   222: \n   223:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   224:          patch(\"expert_build.summarize.invoke_sync\") as mock_llm:\n>> 225:         cmd_summarize(args)\n   226: \n   227:     assert not mock_llm.called\n   228:     captured = capsys.readouterr()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 241,
        "text": "cmd_summarize(args)",
        "context_function": "test_entry_has_source_frontmatter",
        "context_snippet": "   238: \n   239:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   240:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\"):\n>> 241:         cmd_summarize(args)\n   242: \n   243:     entry = _find_entry(work_dir)\n   244:     content = entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 257,
        "text": "cmd_summarize(args)",
        "context_function": "test_entry_has_source_url_from_fetch_frontmatter",
        "context_snippet": "   254: \n   255:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   256:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Page\\nSummary\"):\n>> 257:         cmd_summarize(args)\n   258: \n   259:     entry = _find_entry(work_dir)\n   260:     content = entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 272,
        "text": "cmd_summarize(args)",
        "context_function": "test_entry_has_source_id_when_present",
        "context_snippet": "   269: \n   270:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   271:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\"):\n>> 272:         cmd_summarize(args)\n   273: \n   274:     entry = _find_entry(work_dir)\n   275:     content = entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 287,
        "text": "cmd_summarize(args)",
        "context_function": "test_entry_contains_llm_summary",
        "context_snippet": "   284: \n   285:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   286:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## My Title\\nDetailed summary here\"):\n>> 287:         cmd_summarize(args)\n   288: \n   289:     entry = _find_entry(work_dir)\n   290:     content = entry.read_text()"
      },
      {
        "file": "tests/test_summarize.py",
        "line": 301,
        "text": "cmd_summarize(args)",
        "context_function": "test_entry_directory_structure",
        "context_snippet": "   298: \n   299:     with patch(\"expert_build.summarize.check_model_available\", return_value=True), \\\n   300:          patch(\"expert_build.summarize.invoke_sync\", return_value=\"## Title\\nSummary\"):\n>> 301:         cmd_summarize(args)\n   302: \n   303:     entry = _find_entry(work_dir)\n   304:     assert entry.name == \"my-topic.md\""
      }
    ],
    "production_count": 4,
    "test_count": 20,
    "total_count": 24
  },
  "subprocess_usage": {
    "symbol": "subprocess",
    "usages": [
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_eventloop.py",
        "line": 46,
        "text": "from ._subprocesses import Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/__init__.py",
        "line": 36,
        "text": "from ._subprocesses import Process as Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 11,
        "text": "\"\"\"An asynchronous version of :class:`subprocess.Popen`.\"\"\""
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 29,
        "text": ".. seealso:: :meth:`subprocess.Popen.terminate`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 40,
        "text": ".. seealso:: :meth:`subprocess.Popen.kill`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 46,
        "text": "Send a signal to the subprocess."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 48,
        "text": ".. seealso:: :meth:`subprocess.Popen.send_signal`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 11,
        "text": "import subprocess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 20,
        "text": "from ._core._subprocesses import open_process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 151,
        "text": "command, stdin=subprocess.PIPE, stdout=subprocess.PIPE"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 1072,
        "text": "_process: asyncio.subprocess.Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 2593,
        "text": "process = await asyncio.create_subprocess_shell("
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 2601,
        "text": "process = await asyncio.create_subprocess_exec("
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 7,
        "text": "from subprocess import PIPE, CalledProcessError, CompletedProcess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 42,
        "text": "Run an external command in a subprocess and wait until it completes."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 44,
        "text": ".. seealso:: :func:`subprocess.run`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 48,
        "text": ":param input: bytes passed to the standard input of the subprocess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 49,
        "text": ":param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 51,
        "text": ":param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 53,
        "text": ":param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 54,
        "text": ":data:`subprocess.STDOUT`, a file-like object, or `None`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 55,
        "text": ":param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 61,
        "text": ":param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 64,
        "text": "subprocess (see :class:`subprocess.Popen` for the specifics)"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 66,
        "text": "child process prior to the execution of the subprocess. (POSIX only)"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 71,
        "text": ":param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 76,
        "text": ":raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 146,
        "text": "Start an external command in a subprocess."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 148,
        "text": ".. seealso:: :class:`subprocess.Popen`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 152,
        "text": ":param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a"
      }
    ],
    "production_usages": [
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_eventloop.py",
        "line": 46,
        "text": "from ._subprocesses import Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/__init__.py",
        "line": 36,
        "text": "from ._subprocesses import Process as Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 11,
        "text": "\"\"\"An asynchronous version of :class:`subprocess.Popen`.\"\"\""
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 29,
        "text": ".. seealso:: :meth:`subprocess.Popen.terminate`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 40,
        "text": ".. seealso:: :meth:`subprocess.Popen.kill`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 46,
        "text": "Send a signal to the subprocess."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/abc/_subprocesses.py",
        "line": 48,
        "text": ".. seealso:: :meth:`subprocess.Popen.send_signal`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 11,
        "text": "import subprocess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 20,
        "text": "from ._core._subprocesses import open_process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/to_process.py",
        "line": 151,
        "text": "command, stdin=subprocess.PIPE, stdout=subprocess.PIPE"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 1072,
        "text": "_process: asyncio.subprocess.Process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 2593,
        "text": "process = await asyncio.create_subprocess_shell("
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py",
        "line": 2601,
        "text": "process = await asyncio.create_subprocess_exec("
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 7,
        "text": "from subprocess import PIPE, CalledProcessError, CompletedProcess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 42,
        "text": "Run an external command in a subprocess and wait until it completes."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 44,
        "text": ".. seealso:: :func:`subprocess.run`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 48,
        "text": ":param input: bytes passed to the standard input of the subprocess"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 49,
        "text": ":param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 51,
        "text": ":param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 53,
        "text": ":param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 54,
        "text": ":data:`subprocess.STDOUT`, a file-like object, or `None`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 55,
        "text": ":param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 61,
        "text": ":param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 64,
        "text": "subprocess (see :class:`subprocess.Popen` for the specifics)"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 66,
        "text": "child process prior to the execution of the subprocess. (POSIX only)"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 71,
        "text": ":param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9,"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 76,
        "text": ":raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 146,
        "text": "Start an external command in a subprocess."
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 148,
        "text": ".. seealso:: :class:`subprocess.Popen`"
      },
      {
        "file": ".venv/lib/python3.14/site-packages/anyio/_core/_subprocesses.py",
        "line": 152,
        "text": ":param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a"
      }
    ],
    "test_usages": [],
    "production_count": 55,
    "test_count": 0,
    "total_count": 55
  },
  "summarize_tests": {
    "source_file": "expert_build/summarize.py",
    "test_files": [
      {
        "path": "tests/test_summarize.py",
        "exists": true,
        "line_count": 324
      },
      {
        "path": "tests/test_summarizepy",
        "exists": false
      }
    ],
    "test_count": 2
  }
}
```

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.
