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/ftl_project_expert/cli.py b/ftl_project_expert/cli.py
index 1410f17..6b82565 100644
--- a/ftl_project_expert/cli.py
+++ b/ftl_project_expert/cli.py
@@ -182,7 +182,9 @@ def cli(ctx, quiet, model, timeout):
 @click.argument("target", type=str)
 @click.option("--domain", "-d", default=None, help="One-line project description")
 @click.option("--jira-url", default=None, help="Jira base URL (for jira platform)")
-def init(platform, target, domain, jira_url):
+@click.option("--github-repo", default=None,
+              help="GitHub owner/repo for cross-platform PR lookups (e.g., owner/repo)")
+def init(platform, target, domain, jira_url, github_repo):
     """Bootstrap a project-expert knowledge base.
 
     TARGET is owner/repo for GitHub/GitLab, or project key for Jira.
@@ -232,6 +234,12 @@ def init(platform, target, domain, jira_url):
         config["project"] = target
         config["jira_url"] = jira_url or os.environ.get("JIRA_URL", "")
 
+    if github_repo:
+        if not shutil.which("gh"):
+            click.echo("Error: gh CLI not found. Install from https://cli.github.com", err=True)
+            sys.exit(1)
+        config["github_repo"] = github_repo
+
     _save_config(config)
 
     # Create entries dir
@@ -1383,7 +1391,8 @@ def _extract_issue_refs(text: str) -> list[dict]:
     return refs
 
 
-def _fetch_artifacts(refs: list[dict], source, config: dict) -> str:
+def _fetch_artifacts(refs: list[dict], source, config: dict,
+                     github_source=None) -> str:
     """Fetch current state for each issue/PR reference."""
     parts = []
     platform = config["platform"]
@@ -1397,12 +1406,25 @@ def _fetch_artifacts(refs: list[dict], source, config: dict) -> str:
                     continue
                 issue = source.get_issue(ref["key"])
                 parts.append(issue.to_prompt_text())
-            elif ref["type"] == "pr" and hasattr(source, "get_pr"):
-                pr = source.get_pr(ref["number"])
-                parts.append(pr.to_prompt_text())
+            elif ref["type"] == "pr":
+                if hasattr(source, "get_pr"):
+                    pr = source.get_pr(ref["number"])
+                    parts.append(pr.to_prompt_text())
+                elif github_source:
+                    pr = github_source.get_pr(ref["number"])
+                    parts.append(pr.to_prompt_text())
+                else:
+                    parts.append(f"(Could not fetch pr {ref_label} — no GitHub repo configured)")
             elif ref["type"] == "issue":
-                issue = source.get_issue(ref["number"])
-                parts.append(issue.to_prompt_text())
+                if platform == "jira" and isinstance(ref.get("number"), int):
+                    if github_source:
+                        issue = github_source.get_issue(ref["number"])
+                        parts.append(issue.to_prompt_text())
+                    else:
+                        parts.append(f"(Skipping numeric ref #{ref['number']} on Jira — no GitHub repo configured)")
+                else:
+                    issue = source.get_issue(ref["number"])
+                    parts.append(issue.to_prompt_text())
             else:
                 parts.append(f"(Could not fetch {ref['type']} {ref_label})")
         except Exception as e:
@@ -1513,6 +1535,12 @@ def research(ctx, belief_id, negative, high_impact, select_limit, max_parallel):
 
     source = _get_source(config)
 
+    github_source = None
+    gh_repo = config.get("github_repo")
+    if gh_repo and config["platform"] != "github":
+        github_source = GitHubSource(gh_repo)
+        click.echo(f"Using GitHub repo {gh_repo} for PR lookups", err=True)
+
     def _research_one(bid: str) -> str | None:
         """Build a research prompt for a single belief."""
         info = _get_belief_info(bid)
@@ -1541,7 +1569,7 @@ def _research_one(bid: str) -> str | None:
         refs = _extract_issue_refs(all_text)
         click.echo(f"  Found {len(refs)} reference(s)", err=True)
 
-        artifacts = _fetch_artifacts(refs, source, config)
+        artifacts = _fetch_artifacts(refs, source, config, github_source=github_source)
 
         # Get dependent beliefs
         dependents = _get_dependent_beliefs(bid, network)
@@ -2040,6 +2068,8 @@ def status():
         click.echo(f"Target:   {config.get('repo', config.get('project', 'unknown'))}")
         click.echo(f"Domain:   {config.get('domain', 'unknown')}")
         click.echo(f"Created:  {config.get('created', 'unknown')}")
+        if config.get("github_repo"):
+            click.echo(f"GitHub:   {config['github_repo']}")
     else:
         click.echo("Not initialized. Run: project-expert init <platform> <target>")
         return

```

## Observation Results

You previously requested observations. Here are the results:

```json
{
  "fetch_artifacts_callers": {
    "symbol": "_fetch_artifacts",
    "production_callers": [
      {
        "file": "ftl_project_expert/cli.py",
        "line": 1394,
        "text": "def _fetch_artifacts(refs: list[dict], source, config: dict,",
        "context_function": "_extract_issue_refs",
        "context_snippet": "   1391:     return refs\n   1392: \n   1393: \n>> 1394: def _fetch_artifacts(refs: list[dict], source, config: dict,\n   1395:                      github_source=None) -> str:\n   1396:     \"\"\"Fetch current state for each issue/PR reference.\"\"\"\n   1397:     parts = []"
      },
      {
        "file": "ftl_project_expert/cli.py",
        "line": 1572,
        "text": "artifacts = _fetch_artifacts(refs, source, config, github_source=github_source)",
        "context_function": "_research_one",
        "context_snippet": "   1569:         refs = _extract_issue_refs(all_text)\n   1570:         click.echo(f\"  Found {len(refs)} reference(s)\", err=True)\n   1571: \n>> 1572:         artifacts = _fetch_artifacts(refs, source, config, github_source=github_source)\n   1573: \n   1574:         # Get dependent beliefs\n   1575:         dependents = _get_dependent_beliefs(bid, network)"
      }
    ],
    "test_callers": [],
    "production_count": 2,
    "test_count": 0,
    "total_count": 2
  },
  "fetch_artifacts_full": {
    "function": "_fetch_artifacts",
    "file": "ftl_project_expert/cli.py",
    "start_line": 1394,
    "end_line": 1433,
    "source": "def _fetch_artifacts(refs: list[dict], source, config: dict,\n                     github_source=None) -> str:\n    \"\"\"Fetch current state for each issue/PR reference.\"\"\"\n    parts = []\n    platform = config[\"platform\"]\n\n    for ref in refs:\n        ref_label = ref.get(\"key\") or f\"#{ref.get('number')}\"\n        try:\n            if \"key\" in ref:\n                if platform != \"jira\":\n                    parts.append(f\"(Skipping Jira key {ref['key']} on {platform})\")\n                    continue\n                issue = source.get_issue(ref[\"key\"])\n                parts.append(issue.to_prompt_text())\n            elif ref[\"type\"] == \"pr\":\n                if hasattr(source, \"get_pr\"):\n                    pr = source.get_pr(ref[\"number\"])\n                    parts.append(pr.to_prompt_text())\n                elif github_source:\n                    pr = github_source.get_pr(ref[\"number\"])\n                    parts.append(pr.to_prompt_text())\n                else:\n                    parts.append(f\"(Could not fetch pr {ref_label} \u2014 no GitHub repo configured)\")\n            elif ref[\"type\"] == \"issue\":\n                if platform == \"jira\" and isinstance(ref.get(\"number\"), int):\n                    if github_source:\n                        issue = github_source.get_issue(ref[\"number\"])\n                        parts.append(issue.to_prompt_text())\n                    else:\n                        parts.append(f\"(Skipping numeric ref #{ref['number']} on Jira \u2014 no GitHub repo configured)\")\n                else:\n                    issue = source.get_issue(ref[\"number\"])\n                    parts.append(issue.to_prompt_text())\n            else:\n                parts.append(f\"(Could not fetch {ref['type']} {ref_label})\")\n        except Exception as e:\n            parts.append(f\"(Error fetching {ref['type']} {ref_label}: {e})\")\n\n    return \"\\n\\n---\\n\\n\".join(parts) if parts else \"(No artifacts fetched)\""
  },
  "extract_issue_refs_body": {
    "function": "_extract_issue_refs",
    "file": "ftl_project_expert/cli.py",
    "start_line": 1356,
    "end_line": 1391,
    "source": "def _extract_issue_refs(text: str) -> list[dict]:\n    \"\"\"Extract issue and PR references from text.\n\n    Returns list of dicts with 'type' (issue/pr) and 'number' or 'key'.\n    \"\"\"\n    refs = []\n    seen = set()\n\n    # GH-123, PR-123, GL-123, MR-123\n    for match in re.finditer(r\"\\b(GH|PR|GL|MR)-(\\d+)\\b\", text):\n        prefix = match.group(1)\n        num = int(match.group(2))\n        kind = \"pr\" if prefix in (\"PR\", \"MR\") else \"issue\"\n        key = (kind, num)\n        if key not in seen:\n            seen.add(key)\n            refs.append({\"type\": kind, \"number\": num})\n\n    # #123 (common GitHub/GitLab shorthand)\n    for match in re.finditer(r\"(?<!\\w)#(\\d+)\\b\", text):\n        num = int(match.group(1))\n        key = (\"issue\", num)\n        if key not in seen:\n            seen.add(key)\n            refs.append({\"type\": \"issue\", \"number\": num})\n\n    # PROJ-123 (Jira-style keys)\n    for match in re.finditer(r\"\\b([A-Z][A-Z0-9]+-\\d+)\\b\", text):\n        jira_key = match.group(1)\n        if jira_key.startswith((\"GH-\", \"PR-\", \"GL-\", \"MR-\")):\n            continue\n        if jira_key not in seen:\n            seen.add(jira_key)\n            refs.append({\"type\": \"issue\", \"key\": jira_key})\n\n    return refs"
  },
  "github_source_class": {
    "function": "research",
    "file": "ftl_project_expert/cli.py",
    "calls": [
      {
        "line": 1480,
        "name": "cli.command"
      },
      {
        "line": 1481,
        "name": "click.argument"
      },
      {
        "line": 1482,
        "name": "click.option"
      },
      {
        "line": 1503,
        "name": "_load_config"
      },
      {
        "line": 1520,
        "name": "_load_network"
      },
      {
        "line": 1536,
        "name": "_get_source"
      },
      {
        "line": 1539,
        "name": "config.get"
      },
      {
        "line": 1604,
        "name": "click.echo"
      },
      {
        "line": 1621,
        "name": "zip"
      },
      {
        "line": 1506,
        "name": "sys.exit"
      },
      {
        "line": 1511,
        "name": "_has_reasons"
      },
      {
        "line": 1515,
        "name": "check_model_available"
      },
      {
        "line": 1524,
        "name": "_select_beliefs_for_research"
      },
      {
        "line": 1541,
        "name": "GitHubSource"
      },
      {
        "line": 1546,
        "name": "_get_belief_info"
      },
      {
        "line": 1557,
        "name": "info.get"
      },
      {
        "line": 1569,
        "name": "_extract_issue_refs"
      },
      {
        "line": 1572,
        "name": "_fetch_artifacts"
      },
      {
        "line": 1575,
        "name": "_get_dependent_beliefs"
      },
      {
        "line": 1576,
        "name": "len"
      },
      {
        "line": 1580,
        "name": "RESEARCH_PROMPT.format"
      },
      {
        "line": 1592,
        "name": "_research_one"
      },
      {
        "line": 1607,
        "name": "invoke_concurrent_sync"
      },
      {
        "line": 1611,
        "name": "enumerate"
      },
      {
        "line": 1622,
        "name": "isinstance"
      },
      {
        "line": 1628,
        "name": "result.splitlines"
      },
      {
        "line": 1639,
        "name": "_create_entry"
      },
      {
        "line": 1641,
        "name": "_emit"
      },
      {
        "line": 1559,
        "name": "Path"
      },
      {
        "line": 1560,
        "name": "entry_path.is_file"
      },
      {
        "line": 1594,
        "name": "prompts_with_ids.append"
      },
      {
        "line": 1629,
        "name": "line.strip"
      },
      {
        "line": 1630,
        "name": "line.startswith"
      },
      {
        "line": 1638,
        "name": "re.sub"
      },
      {
        "line": 1561,
        "name": "entry_path.read_text"
      },
      {
        "line": 1614,
        "name": "invoke_sync"
      },
      {
        "line": 1615,
        "name": "results.append"
      },
      {
        "line": 1631,
        "name": "strip"
      }
    ]
  },
  "github_source_get_pr": {
    "function": "get_pr",
    "file": "ftl_project_expert/sources/github.py",
    "explicit_raises": [
      "RuntimeError"
    ],
    "calls": [
      "RuntimeError",
      "strip",
      "run",
      "loads",
      "_normalize_pr",
      "str"
    ]
  },
  "github_source_get_issue": {
    "function": "get_issue",
    "file": "ftl_project_expert/sources/github.py",
    "explicit_raises": [
      "RuntimeError"
    ],
    "calls": [
      "_normalize",
      "RuntimeError",
      "strip",
      "run",
      "loads",
      "str"
    ]
  },
  "github_source_init": {
    "function": "__init__",
    "file": "ftl_project_expert/sources/github.py",
    "start_line": 14,
    "end_line": 19,
    "source": "    def __init__(self, repo: str):\n        \"\"\"Args:\n            repo: owner/repo slug (e.g., \"benthomasson/ftl-code-expert\")\n        \"\"\"\n        self.repo = repo\n        self.platform = \"github\"",
    "class_name": "GitHubSource"
  },
  "cli_test_coverage": {
    "source_file": "ftl_project_expert/cli.py",
    "test_files": [],
    "test_count": 0
  },
  "file_imports_cli": {
    "file": "ftl_project_expert/cli.py",
    "imports": [
      "asyncio",
      "json",
      "os",
      "re",
      "shutil",
      "subprocess",
      "sys",
      "click"
    ],
    "from_imports": [
      {
        "module": "datetime",
        "names": [
          "date",
          "datetime"
        ]
      },
      {
        "module": "pathlib",
        "names": [
          "Path"
        ]
      },
      {
        "module": "llm",
        "names": [
          "check_model_available",
          "invoke",
          "invoke_concurrent_sync",
          "invoke_sync"
        ]
      },
      {
        "module": "prompts",
        "names": [
          "PROPOSE_BELIEFS_PROJECT",
          "RESEARCH_PROMPT",
          "build_explore_prompt",
          "build_scan_prompt",
          "build_summary_prompt"
        ]
      },
      {
        "module": "sources",
        "names": [
          "GitHubSource",
          "GitLabSource",
          "Issue",
          "JiraSource"
        ]
      },
      {
        "module": "topics",
        "names": [
          "Topic",
          "add_topics",
          "load_queue",
          "parse_topics_from_response",
          "pending_count",
          "pop_at",
          "pop_multiple",
          "pop_next",
          "skip_topic"
        ]
      }
    ],
    "import_section": "\"\"\"Command-line interface for project expert.\"\"\"\n\nimport asyncio\nimport json\nimport os\nimport re\nimport shutil\nimport subprocess\nimport sys\nfrom datetime import date, datetime\nfrom pathlib import Path\n\nimport click\n\nfrom .llm import check_model_available, invoke, invoke_concurrent_sync, invoke_sync\nfrom .prompts import (\n    PROPOSE_BELIEFS_PROJECT,\n    RESEARCH_PROMPT,\n    build_explore_prompt,\n    build_scan_prompt,\n    build_summary_prompt,\n)\nfrom .sources import GitHubSource, GitLabSource, Issue, JiraSource\nfrom .topics import (\n    Topic,\n    add_topics,\n    load_queue,\n    parse_topics_from_response,\n    pending_count,\n    pop_at,\n    pop_multiple,\n    pop_next,\n    skip_topic,\n)\n\nPROJECT_DIR = \".project-expert\"\n\n\n# --- Config helpers ---\n\n\ndef _load_config() -> dict | None:\n    config_path = Path.cwd() / PROJECT_DIR / \"config.json\"\n    if config_path.is_file():\n        return json.loads(config_path.read_text())\n    return None\n\n\ndef _save_config(config: dict) -> None:\n    config_dir = Path.cwd() / PROJECT_DIR\n    config_dir.mkdir(parents=True, exist_ok=True)\n    (config_dir / \"config.json\").write_text(json.dumps(config, indent=2))\n\n\ndef _get_project_dir() -> str:\n    return str(Path.cwd() / PROJECT_DIR)\n\n\n# --- Source helpers ---\n\n\ndef _get_source(config: dict) -> GitHubSource | GitLabSource | JiraSource:\n    \"\"\"Create the appropriate source from config.\"\"\""
  },
  "research_full": {
    "function": "research",
    "file": "ftl_project_expert/cli.py",
    "start_line": 1480,
    "end_line": 1641,
    "source": "@cli.command(\"research\")\n@click.argument(\"belief_id\", required=False)\n@click.option(\"--negative\", is_flag=True, default=False,\n              help=\"Select from OUT/negative beliefs\")\n@click.option(\"--high-impact\", is_flag=True, default=False,\n              help=\"Select by dependent count (highest first)\")\n@click.option(\"--limit\", \"select_limit\", type=int, default=1,\n              help=\"Number of beliefs to research (default: 1)\")\n@click.option(\"--parallel\", \"max_parallel\", type=int, default=1,\n              help=\"Max concurrent LLM calls (default: 1, try 3 for speed)\")\n@click.pass_context\ndef research(ctx, belief_id, negative, high_impact, select_limit, max_parallel):\n    \"\"\"Verify a belief against its source system.\n\n    Fetches the current state of issues/PRs referenced by a belief,\n    compares against the belief's claims, and reports discrepancies.\n\n    Examples:\n        project-expert research my-belief-id\n        project-expert research --negative\n        project-expert research --high-impact --limit 5\n        project-expert research --high-impact --limit 5 --parallel 3\n    \"\"\"\n    config = _load_config()\n    if not config:\n        click.echo(\"Not initialized. Run: project-expert init <platform> <target>\")\n        sys.exit(1)\n\n    model = ctx.obj[\"model\"]\n    timeout = ctx.obj[\"timeout\"]\n\n    if not _has_reasons():\n        click.echo(\"Error: reasons CLI required. Install with: uv tool install ftl-reasons\", err=True)\n        sys.exit(1)\n\n    if not check_model_available(model):\n        click.echo(f\"Error: Model '{model}' CLI not available\", err=True)\n        sys.exit(1)\n\n    # Determine which beliefs to research\n    network = _load_network()\n    if belief_id:\n        belief_ids = [belief_id]\n    else:\n        belief_ids = _select_beliefs_for_research(\n            network, negative=negative, high_impact=high_impact,\n            limit=select_limit,\n        )\n        if not belief_ids:\n            label = \"negative \" if negative else \"\"\n            click.echo(f\"No {label}beliefs found to research.\")\n            return\n        click.echo(f\"Selected {len(belief_ids)} belief(s) for research:\", err=True)\n        for bid in belief_ids:\n            click.echo(f\"  {bid}\", err=True)\n\n    source = _get_source(config)\n\n    github_source = None\n    gh_repo = config.get(\"github_repo\")\n    if gh_repo and config[\"platform\"] != \"github\":\n        github_source = GitHubSource(gh_repo)\n        click.echo(f\"Using GitHub repo {gh_repo} for PR lookups\", err=True)\n\n    def _research_one(bid: str) -> str | None:\n        \"\"\"Build a research prompt for a single belief.\"\"\"\n        info = _get_belief_info(bid)\n        if not info:\n            click.echo(f\"Belief not found: {bid}\", err=True)\n            return None\n\n        click.echo(f\"\\nResearching: {bid}\", err=True)\n        click.echo(f\"  Claim: {info['text'][:100]}\", err=True)\n        click.echo(f\"  Status: {info['status']}\", err=True)\n\n        # Read source entry\n        source_entry = \"(No source entry found)\"\n        source_path = info.get(\"source\", \"\")\n        if source_path:\n            entry_path = Path(source_path)\n            if entry_path.is_file():\n                content = entry_path.read_text()\n                if len(content) > 15000:\n                    content = content[:15000] + \"\\n[Truncated]\"\n                source_entry = content\n                click.echo(f\"  Source: {source_path}\", err=True)\n\n        # Extract and fetch artifacts\n        all_text = f\"{info['text']}\\n{source_entry}\"\n        refs = _extract_issue_refs(all_text)\n        click.echo(f\"  Found {len(refs)} reference(s)\", err=True)\n\n        artifacts = _fetch_artifacts(refs, source, config, github_source=github_source)\n\n        # Get dependent beliefs\n        dependents = _get_dependent_beliefs(bid, network)\n        dep_count = len(info.get(\"dependents\", []))\n        if dep_count:\n            click.echo(f\"  {dep_count} dependent belief(s)\", err=True)\n\n        return RESEARCH_PROMPT.format(\n            belief_id=bid,\n            belief_text=info[\"text\"],\n            belief_status=info[\"status\"],\n            source_entry=source_entry,\n            artifacts=artifacts,\n            dependents=dependents,\n        )\n\n    # Build prompts\n    prompts_with_ids = []\n    for bid in belief_ids:\n        prompt = _research_one(bid)\n        if prompt:\n            prompts_with_ids.append((bid, prompt))\n\n    if not prompts_with_ids:\n        click.echo(\"No beliefs could be researched.\")\n        return\n\n    # Invoke LLM\n    ids = [p[0] for p in prompts_with_ids]\n    prompts = [p[1] for p in prompts_with_ids]\n\n    click.echo(f\"\\nRunning {model} on {len(prompts)} belief(s)...\", err=True)\n\n    if max_parallel > 1 and len(prompts) > 1:\n        results = invoke_concurrent_sync(prompts, model=model, timeout=timeout,\n                                         max_concurrent=max_parallel)\n    else:\n        results = []\n        for i, prompt in enumerate(prompts):\n            click.echo(f\"  [{i + 1}/{len(prompts)}] {ids[i]}...\", err=True)\n            try:\n                result = invoke_sync(prompt, model=model, timeout=timeout)\n                results.append(result)\n            except Exception as e:\n                click.echo(f\"  ERROR: {e}\", err=True)\n                results.append(e)\n\n    # Process results\n    for bid, result in zip(ids, results):\n        if isinstance(result, Exception):\n            click.echo(f\"\\nERROR researching {bid}: {result}\", err=True)\n            continue\n\n        # Extract verdict\n        verdict = \"UNKNOWN\"\n        for line in result.splitlines():\n            line = line.strip()\n            if line.startswith(\"VERDICT:\"):\n                verdict = line[8:].strip()\n                break\n\n        click.echo(f\"\\n{'=' * 40}\", err=True)\n        click.echo(f\"  {bid}: {verdict}\", err=True)\n        click.echo(f\"{'=' * 40}\", err=True)\n\n        safe_id = re.sub(r\"[^a-zA-Z0-9_-]\", \"-\", bid)[:80]\n        _create_entry(f\"research-{safe_id}\", f\"Research: {bid} [{verdict}]\", result)\n\n        _emit(ctx, result)"
  },
  "save_config_body": {
    "function": "_save_config",
    "file": "ftl_project_expert/cli.py",
    "start_line": 49,
    "end_line": 52,
    "source": "def _save_config(config: dict) -> None:\n    config_dir = Path.cwd() / PROJECT_DIR\n    config_dir.mkdir(parents=True, exist_ok=True)\n    (config_dir / \"config.json\").write_text(json.dumps(config, indent=2))"
  },
  "get_source_body": {
    "function": "_get_source",
    "file": "ftl_project_expert/cli.py",
    "start_line": 62,
    "end_line": 75,
    "source": "def _get_source(config: dict) -> GitHubSource | GitLabSource | JiraSource:\n    \"\"\"Create the appropriate source from config.\"\"\"\n    platform = config[\"platform\"]\n    if platform == \"github\":\n        return GitHubSource(config[\"repo\"])\n    elif platform == \"gitlab\":\n        return GitLabSource(config[\"repo\"])\n    elif platform == \"jira\":\n        return JiraSource(\n            config[\"project\"],\n            url=config.get(\"jira_url\"),\n        )\n    else:\n        raise ValueError(f\"Unknown platform: {platform}\")"
  }
}
```

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.
