# Project export: Deep Dive Skill

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: An adversarially-verified deep-research CLI skill that runs on 15 different AI coding agents — including Codex — and refuses to trust any claim until independent verifier agents fail to refute it.
- Devpost: https://devpost.com/software/deep-dive-skill
- GitHub: https://github.com/Bhllcoder1/deep-dive-skill
- Video: https://www.youtube.com/embed/NM1zU5cvBgg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Behlul (7 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Deep Dive Skill

<p align="center">
  <img src="assets/cover.png" alt="Deep Dive Skill" width="360">
</p>

<p align="center">
  <img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg">
  <img alt="Python" src="https://img.shields.io/badge/python-3.9%2B-blue.svg">
  <img alt="Platforms" src="https://img.shields.io/badge/runtime%20adapters-15-brightgreen.svg">
  <img alt="Dependencies" src="https://img.shields.io/badge/dependencies-zero%20(stdlib%20%2B%20curl)-lightgrey.svg">
  <img alt="Built with Codex" src="https://img.shields.io/badge/hardened%20with-OpenAI%20Codex%20(GPT--5.6)-412991.svg">
</p>

**Deep Dive** is a universal, adversarially verified deep-research agent that runs as a portable *skill* on top of AI coding and agent platforms. Point it at a question; it returns a claim-by-claim, source-cited report where each accepted claim has survived independent cross-examination.

```
Pipeline: Scope → Search → Fetch → Verify (Adversarial) → Synthesize
```

## Why it's different

Most research agents summarize the first few search results. Deep Dive treats extracted claims as guilty until proven otherwise: independent verifier calls try to refute each claim, and only claims with the required non-refutation support enter the final report. Ties, incomplete votes, malformed model output, and uncited findings remain unverified rather than being presented as facts.

The pipeline in `core/` is shared across all runtimes. `runtime/` selects an adapter automatically or via `DR_RUNTIME`; the generic path uses an OpenAI-compatible API plus DuckDuckGo HTML search, optional Google Custom Search, or optional SearXNG.

## Quick Start

```bash
# 1. Clone
git clone https://github.com/YOUR_USERNAME/deep-dive-skill.git
cd deep-dive-skill

# 2. Set an API key for the generic fallback
export DEEPSEEK_API_KEY="sk-..."

# 3. Run research
python3 harness.py "Compare Storj vs Filebase decentralized storage"
```

`requests` is optional. Without it, the generic runtime uses `curl`; if neither is available, it reports the missing client instead of starting a partial run.

## Features

- **15 runtime adapters** — Hermes, Claude Code, generic Python, Aider, Codex, Cline/Roo, Cursor, Gemini, GitHub Copilot, Amazon Q, Windsurf, Kimi, GLM, MiniMax, and Antigravity
- **Adversarial verification** — verifier calls attempt to refute every selected claim; ties and incomplete support are not confirmed
- **Structured output** — JSON or readable text reports with confidence, sources, refuted claims, unverified claims, caveats, and run statistics
- **Configurable scope and cost** — tier presets plus bounded overrides for search angles, fetches, claims, votes, refutations, and concurrency
- **Safe fallbacks** — malformed JSON, bad URLs, failed requests, and failed workers degrade to partial structured results rather than crashing the whole run
- **Live terminal dashboard** — `panel.sh` launches the bundled dashboard

## Platform Support

All 15 adapters are registered in `runtime/__init__.py`. The ten CLI adapters send each agent prompt to the local CLI on standard input, parse JSON from its output, and run those CLI calls in a bounded local worker pool; if the CLI is missing, fails, or returns invalid JSON, they fall back to `GenericRuntime`.

| Platform | Runtime name | Selection | Agent execution path | Search/fetch path |
|----------|--------------|-----------|----------------------|-------------------|
| Hermes Agent | `hermes` | `HERMES_AGENT` or `DR_RUNTIME=hermes` | Hermes runtime; no platform CLI shell-out | Adapter HTTP/curl path |
| Claude Code | `claude_code` | `CLAUDE_CODE=1` or `DR_RUNTIME=claude_code` | Emits bridge markers, then uses the generic API fallback because the bundled bridge is request-only | Emits bridge markers, then curl fallback |
| Generic Python | `generic` | Default or `DR_RUNTIME=generic` | OpenAI-compatible API via `requests` or `curl` | DuckDuckGo HTML; optional Google Custom Search or SearXNG |
| Aider | `aider` | `AIDER_CHAT_MODE`/`AIDER_VERSION` or forced | **Generic-only** (`GenericRuntime`); no Aider CLI shell-out | Generic search/fetch |
| Codex CLI | `codex` | `CODEX_CLI` or forced | Local `codex` CLI, else generic fallback | Generic search/fetch |
| Cline / Roo Code | `cline` | `CLINE_MCP` or forced | **Generic-only** (`GenericRuntime`); no Cline/Roo CLI shell-out | Generic search/fetch; `MCP_WEB_SEARCH_URL` is only reported during setup |
| Cursor Agent | `cursor` | `CURSOR_CLI` or forced | Local `cursor-agent` CLI, else generic fallback | Generic search/fetch |
| Gemini CLI | `gemini` | `GEMINI_CLI` or forced | Local `gemini` CLI, else generic fallback | Generic search/fetch |
| GitHub Copilot | `copilot` | `COPILOT_CLI` or forced | Local `copilot`, then `gh copilot`, else generic fallback | Generic search/fetch |
| Amazon Q Developer | `amazon_q` | `AMAZON_Q_CLI` or forced | Local `q` CLI, else generic fallback | Generic search/fetch |
| Windsurf | `windsurf` | `WINDSURF_CLI` or forced | Local `windsurf` CLI, else generic fallback | Generic search/fetch |
| Kimi Code | `kimi` | `KIMI_CLI` or forced | Local `kimi`, then `~/.kimi-code/bin/kimi`, else generic fallback | Generic search/fetch |
| GLM Code (Z.ai) | `glm` | `GLM_CLI` or forced | Local `glm`, then `zai`, else generic fallback | Generic search/fetch |
| MiniMax Code | `minimax` | `MINIMAX_CLI` or forced | Local `minimax` CLI, else generic fallback | Generic search/fetch |
| Google Antigravity CLI | `antigravity` | `ANTIGRAVITY_CLI` or forced | Local `antigravity` CLI, else generic fallback; its interactive `/agent` and `/agents` commands are not used | Generic search/fetch |

All adapters expose bounded parallel work to the pipeline; `DR_MAX_WORKERS` controls the verifier concurrency and is hard-capped at 20.

## CLI Usage

```bash
# Basic research
python3 harness.py "What are the pros and cons of Rust vs Go for CLI tools?"

# JSON report on stdout (progress goes to stderr)
python3 harness.py "..." --format json

# More thorough research
python3 harness.py "..." --max-fetch 20 --max-claims 15 --votes 3

# Force a specific runtime
python3 harness.py "..." --runtime codex
DR_RUNTIME=generic python3 harness.py "..."

# Save the report to a chosen file
python3 harness.py "..." --output my-report.json

# Live control panel
./panel.sh
./panel.sh "research question"
```

The Python API accepts the same `tier`, `angles`, `max_fetch`, `max_verify_claims`, `votes_per_claim`, `refutations_required`, and `max_workers` settings as keyword arguments. Each `deep_research()` call temporarily applies its settings and restores the process environment afterward.

## Cost Tiers

Instead of tuning individual knobs, pick a tier. It bounds total agent calls so a run never silently spawns hundreds of agents:

```bash
python3 harness.py "..." --tier low       # quick sanity check
python3 harness.py "..." --tier medium    # decent coverage, light verification
python3 harness.py "..." --tier high      # default — thorough, adversarially verified
python3 harness.py "..." --tier ultra     # deep, wide research
DR_COST_TIER=ultra python3 harness.py "..."
```

Total agent calls are approximately `2 (scope + synthesize) + angles + max_fetch + (max_verify_claims × votes_per_claim)`. `max_workers` separately caps concurrent verifier calls (hard ceiling: 20).

| Tier | Angles | Max Fetch | Max Verify Claims | Votes/Claim | Refutations Required | Max Concurrent Workers | ~Agent Calls |
|------|--------|-----------|-------------------|-------------|----------------------|------------------------|--------------|
| `low` | 2 | 3 | 2 | 1 | 1 | 2 | ~9 |
| `medium` | 4 | 8 | 6 | 2 | 2 | 4 | ~26 |
| `high` *(default)* | 5 | 15 | 12 | 2 | 2 | 6 | ~46 |
| `ultra` | 10 | 30 | 35 | 3 | 2 | 10 | ~147 |

Any individual `DR_*` setting below overrides its tier value, so you can start from a tier and adjust one bound (for example, `--tier medium --votes 3`). `DR_REFUTATIONS_REQUIRED` cannot exceed `DR_VOTES_PER_CL

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 53 recognized source files, 292 KB.
- JavaScript (language) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (55 of 55)

```
.gitignore
CODEX_CHANGES.md
CODEX_CHANGES/agent.md
CODEX_CHANGES/antigravity_and_delegation.md
CODEX_CHANGES/engine.md
CODEX_CHANGES/harness.md
CODEX_CHANGES/monitor.md
CODEX_CHANGES/parallel.md
CODEX_CHANGES/readme_and_cleanup.md
CODEX_CHANGES/runtime_amazon_q.md
CODEX_CHANGES/runtime_base.md
CODEX_CHANGES/runtime_claude_code.md
CODEX_CHANGES/runtime_copilot.md
CODEX_CHANGES/runtime_cursor.md
CODEX_CHANGES/runtime_gemini.md
CODEX_CHANGES/runtime_generic.md
CODEX_CHANGES/runtime_glm.md
CODEX_CHANGES/runtime_hermes.md
CODEX_CHANGES/runtime_kimi.md
CODEX_CHANGES/runtime_minimax.md
CODEX_CHANGES/runtime_windsurf.md
CODEX_CHANGES/web.md
core/__init__.py
core/agent.py
core/dashboard.py
core/engine.py
core/monitor.py
core/parallel.py
core/schemas.py
core/tiers.py
core/web.py
harness.py
LICENSE
panel.sh
README.md
runtime/__init__.py
runtime/_cli_parallel.py
runtime/adapters/claude-code-workflow.js
runtime/adapters/claude-code-wrapper.js
runtime/aider.py
runtime/amazon_q.py
runtime/antigravity.py
runtime/base.py
runtime/claude_code.py
runtime/cline.py
runtime/codex.py
runtime/copilot.py
runtime/cursor.py
runtime/gemini.py
runtime/generic.py
runtime/glm.py
runtime/hermes.py
runtime/kimi.py
runtime/minimax.py
runtime/windsurf.py
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Revert "Add index.md for GitHub Pages"
- Revert "Move Pages source to docs/ folder with Jekyll theme"
- Revert "Fix clone URL in README and docs/index.md"
- Revert "Replace docs/ with split-panel index.html (README left + file tree right)"
- Revert "Server-rendered split-panel: README (left) + file tree (right), zero JS dependencies"
- Server-rendered split-panel: README (left) + file tree (right), zero JS dependencies
- Replace docs/ with split-panel index.html (README left + file tree right)
- Fix clone URL in README and docs/index.md
- Move Pages source to docs/ folder with Jekyll theme
- Add index.md for GitHub Pages
- Add README badges (license, python, adapter count, Codex)
- Add Antigravity adapter; give every CLI adapter real parallel delegation
- Comprehensive README accuracy pass, remove unrelated third-party files
- Add 8 new platform runtime adapters (Cursor, Gemini, Copilot, Amazon Q, Windsurf, Kimi, GLM, MiniMax)
- Codex hardening pass: fix concurrency, JSON parsing, and fetch-safety bugs
- Add cover image to README
- Initial commit: Deep Dive Skill — universal adversarially-verified research agent

## Key source files (fetched from GitHub, selected and truncated for size)

### CODEX_CHANGES.md

```markdown
# Codex Hardening Pass

For OpenAI Build Week, [OpenAI Codex](https://openai.com/index/introducing-codex/) (GPT-5.6-Terra) was run against this repository file-by-file: for each core module, Codex was told to read the file plus `README.md` for context, understand its exact role in the pipeline, and do a genuine engineering pass — not a cosmetic rewrite. Ten files were covered in independent sessions, each restricted to its own file so the diffs stay auditable per-module. Per-file notes are in [`CODEX_CHANGES/`](CODEX_CHANGES/).

## What Codex actually fixed

- **Real concurrency bugs.** `core/parallel.py`'s hand-rolled thread throttle could spawn more threads than `max_workers` and silently dropped worker exceptions. Replaced with a bounded `ThreadPoolExecutor` with ordered results and full traceback logging. The same unbounded-thread pattern was fixed in `runtime/generic.py`, `runtime/hermes.py`, and `runtime/claude_code.py`.
- **A security-relevant fetch gap.** `core/engine.py`'s extractor was previously being handed only URL/title metadata instead of the actual fetched page text, and untrusted source text had no guard against being interpreted as instructions. Direct fetches to local/private-IP targets are now rejected, and only HTTP(S) URLs are accepted anywhere a fetch happens.
- **Fragile JSON parsing.** Every LLM-facing module (`core/agent.py`, `core/engine.py`, `runtime/*.py`) used greedy/naive JSON extraction that accepted truncated or malformed model output. Replaced with decoder-based parsing that requires a complete, schema-valid object before it reaches pipeline logic.
- **Cross-call state leakage in `harness.py`.** `DR_*` environment overrides from one `deep_research()` call could leak into a later call because `core.engine` reads its config at import time. Now isolated with a process lock + temporary environment overlay + module reload per call.
- **Thread-safety in `core/monitor.py`.** Phase/agent status mutation had no locking, so concurrent workers from `parallel.py` could produce duplicate IDs and corrupted progress counts. Now guarded with a re-entrant lock and atomic ID allocation; cost accounting no longer mis-prices every agent as the wrong provider.
- **A dead search backend in `core/web.py`.** DuckDuckGo's Instant Answer API doesn't reliably return ordinary web results — swapped for its HTML result page with a tolerant parser, kept Google Custom Search as configured fallback.
- **`runtime/codex.py`** went from an unimplemented stub to actually detecting and shelling out to a local `codex` CLI when present, with a clean fallback to the generic API runtime when it isn't.

## What Codex did NOT change

Per instructions given to every session: no cosmetic-only rewrites, no public CLI/API signature changes unless a real bug required it (none did — `deep_research()`, `agent()`, `search()`, `fetch()`, `run()` all kept their original signatures), and the multi-platform runtime architecture was left intact.

## New platform adapte
[truncated — 1308 more characters]
```

### CODEX_CHANGES/runtime_gemini.md

```markdown
# Gemini runtime adapter

Added `GeminiRuntime`, which prefers `GEMINI_CLI` or the local `gemini` binary and falls back to `GenericRuntime` when the CLI is unavailable or fails.

```

### panel.sh

```shell
#!/bin/bash
# deep-dive-skill — Kontrol Paneli Başlatıcı
# Bu script'i kendi terminalinde çalıştır, panel açılsın.
# 
# Kullanım:
#   ./panel.sh                    # Panel aç
#   ./panel.sh "araştırma sorusu" # Pipeline + Panel

cd "$(dirname "$0")"

# API key kontrol
if [ -z "$DEEPSEEK_API_KEY" ]; then
    if [ -f ~/.bashrc ]; then
        source ~/.bashrc
    fi
fi

if [ -z "$DEEPSEEK_API_KEY" ]; then
    echo "❌ DEEPSEEK_API_KEY bulunamadı."
    echo "  export DEEPSEEK_API_KEY='sk-...'"
    exit 1
fi

echo "━━━ Deep Dive Skill Control Panel ───"
echo "  ↑↓ navigate · r research · h history · m model · q quit"
echo ""

if [ -z "$1" ]; then
    # Sadece panel aç
    python3 -m core.dashboard
else
    # Pipeline + Panel
    python3 -c "
from core.dashboard import Dashboard
from harness import deep_research
import threading, time

dash = Dashboard()
dash.subtitle = '$1'

def run_pipeline():
    result = deep_research('$1', show_dashboard=False)
    dash.pipeline.result = result

t = threading.Thread(target=run_pipeline, daemon=True)
t.start()
dash.run()
"
fi

```

### harness.py

```python
#!/usr/bin/env python3
"""CLI and Python API entrypoint for the Deep Dive research pipeline."""

import contextlib
import importlib
import json
import os
import sys
import threading
from typing import Any, Dict, Mapping, Optional


_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
if _THIS_DIR not in sys.path:
    sys.path.insert(0, _THIS_DIR)


_CONFIG_MAP = {
    "tier": "DR_COST_TIER",
    "angles": "DR_ANGLES",
    "max_fetch": "DR_MAX_FETCH",
    "max_verify_claims": "DR_MAX_VERIFY_CLAIMS",
    "votes_per_claim": "DR_VOTES_PER_CLAIM",
    "refutations_required": "DR_REFUTATIONS_REQUIRED",
    "max_workers": "DR_MAX_WORKERS",
}
_RUNTIMES = {
    "auto", "hermes", "claude_code", "generic", "aider", "codex", "cline",
    "cursor", "gemini", "copilot", "amazon_q", "windsurf", "kimi", "glm", "minimax", "antigravity",
}
_CONFIG_LOCK = threading.RLock()


def _error_result(question: str, message: str) -> Dict[str, Any]:
    return {"error": message, "question": question}


def _coerce_positive_int(name: str, value: Any) -> int:
    if isinstance(value, bool):
        raise ValueError(f"{name} must be a positive integer.")
    try:
        number = int(value)
    except (TypeError, ValueError) as exc:
        raise ValueError(f"{name} must be a positive integer.") from exc
    if number < 1:
        raise ValueError(f"{name} must be a positive integer.")
    return number


def _resolve_config(overrides: Mapping[str, Any]) -> Dict[str, str]:
    unknown = set(overrides) - set(_CONFIG_MAP)
    if unknown:
        raise ValueError(f"Unsupported configuration option(s): {', '.join(sorted(unknown))}.")

    from core.tiers import HARD_MAX_WORKERS, resolve_tier

    raw_tier = overrides.get("tier", os.environ.get("DR_COST_TIER", ""))
    tier = str(raw_tier or "high").strip().lower()
    if tier not in {"low", "medium", "high", "ultra"}:
        raise ValueError("tier must be one of: low, medium, high, ultra.")

    tier_config = resolve_tier(tier)
    resolved = {"DR_COST_TIER": tier}
    for option, env_key in _CONFIG_MAP.items():
        if option == "tier":
            continue
        raw_value = overrides.get(option, os.environ.get(env_key, tier_config[option]))
        resolved[env_key] = str(_coerce_positive_int(option, raw_value))

    votes = int(resolved["DR_VOTES_PER_CLAIM"])
    refutations = int(resolved["DR_REFUTATIONS_REQUIRED"])
    if refutations > votes:
        raise ValueError("refutations_required cannot exceed votes_per_claim.")
    if int(resolved["DR_MAX_WORKERS"]) > HARD_MAX_WORKERS:
        raise ValueError(f"max_workers cannot exceed the hard limit of {HARD_MAX_WORKERS}.")
    return resolved


@contextlib.contextmanager
def _temporary_environ(values: Mapping[str, str]):
    previous = {key: os.environ.get(key) for key in values}
    os.environ.update(values)
    try:
        yield
    finally:
        for key, value in previous.items():
            if value is None:
                os.environ.pop(key, None)
            else:
                os.environ[key] = value


def _configure_dashboard(question: str, config: Mapping[str, str]) -> Any:
    """Adapt the bundled Dashboard to the small interface expected by engine.run."""
    from core.dashboard import Dashboard

    dashboard = Dashboard()
    dashboard.title = "bdeep-research"
    dashboard.subtitle = question[:60]
    totals = {
        "Scope": 1,
        "Search": int(config["DR_ANGLES"]),
        "Fetch": int(config["DR_MAX_FETCH"]),
        "Verify": int(config["DR_MAX_VERIFY_CLAIMS"]),
        "Synthesize": 1,
    }
    for phase in dashboard.phases:
        phase["total"] = totals.get(phase["name"], phase["total"])
        phase["completed"] = 0
        phase["status"] = "idle"
        phase["agents"] = []

    def select_phase(name: str) -> None:
        target = name.split("—", 1)[0].strip()
        for index, phase in enumerate(dashboard.phases):
            if phase["name"] == target:
                if phase["status"] == "idle":
                    phase["status"] = "running"
                dashboard.selected_idx = index
                return

    dashboard.select_phase = select_phase
    return dashboard


def _start_dashboard(question: str, config: Mapping[str, str], runtime: Any) -> tuple[Any, Optional[threading.Event], Optional[threading.Thread]]:
    if not sys.stdout.isatty():
        return None, None, None
    try:
        dashboard = _configure_dashboard(question, config)
        runtime._dashboard = dashboard
        stop_event = threading.Event()

        def render_loop() -> None:
            try:
                sys.stdout.write("\033[2J\033[H")
                while not stop_event.is_set():
                    sys.stdout.write("\033[H" + dashboard.render())
                    sys.stdout.flush()
                    stop_event.wait(0.5)
            except (BrokenPipeError, OSError):
                stop_event.set()

        thread = threading.Thread(target=render_loop, name="deep-research-dashboard", daemon=True)
        thread.start()
        return dashboard, stop_event, thread
    except Exception as exc:
        print(f"  [dashboard] Could not start: {exc}", file=sys.stderr)
        return None, None, None


def _stop_dashboard(stop_event: Optional[threading.Event], thread: Optional[threading.Thread]) -> None:
    if stop_event is not None:
        stop_event.set()
    if thread is not None:
        thread.join(timeout=1)


def deep_research(question: str, runtime_name: Optional[str] = None,
                  show_dashboard: bool = True, **kwargs) -> Dict[str, Any]:
    """Run the pipeline and return its structured research report.

    Keyword configuration mirrors the documented ``DR_*`` settings. Each call
    is isolated: overrides apply while its pipeline is running and are restored
    before this function returns.
    """
    if not isinstance(question, str):
        return _error_result("", "Research question must be a string.")
    question = question.strip()
    if not
[truncated — 8710 more characters]
```

### core/__init__.py

```python
"""
Deep Dive Skill — Core engine.
Universal pipeline: Scope → Search → Fetch → Verify → Synthesize.
Works on any platform (Hermes, Claude Code, Aider, Codex, etc.)
"""

from .engine import run as deep_research
from .monitor import PipelineMonitor, ModelConfig, AgentStatus, PhaseStatus
from .monitor import AgentRecord, PhaseRecord

__all__ = [
    "deep_research",
    "PipelineMonitor", "ModelConfig",
    "AgentStatus", "PhaseStatus",
    "AgentRecord", "PhaseRecord",
]

```

### runtime/aider.py

```python
"""
Aider Runtime.
Aider'ın gücü: chat mode, file editing, terminal.
Kendi web search tool'u yok -> generic fallback kullanır.
Özel: --model ile farklı LLM kullanabilir.
"""

from .generic import GenericRuntime


class AiderRuntime(GenericRuntime):
    """Aider için runtime. Generic ile aynı, sadece isim farklı."""

    @property
    def name(self) -> str:
        return "aider"

    def setup(self) -> bool:
        result = super().setup()
        if result:
            print("[aider] ⚠ Web araması için DDG scraping kullanılacak (built-in web search yok)")
        return result

```

### runtime/_cli_parallel.py

```python
"""Shared bounded parallel execution for local CLI runtime calls."""

from typing import Any, Callable, List

from core.parallel import _threaded_parallel, _validate_max_workers


def run_cli_parallel(fn_list: List[Callable[[], Any]], max_workers: int = 3) -> List[Any]:
    """Run CLI-invoking callables concurrently while preserving order and failures."""
    if not isinstance(fn_list, (list, tuple)):
        raise TypeError("fn_list must be a list or tuple of callables")
    _validate_max_workers(max_workers)
    functions = list(fn_list)
    for index, fn in enumerate(functions):
        if not callable(fn):
            raise TypeError(f"fn_list[{index}] must be callable")
    return _threaded_parallel(functions, max_workers)

```

### runtime/cline.py

```python
"""
Cline / Roo Code Runtime.
MCP tabanlı CLI. Web search için MCP server gerekli.
Eğer MCP web search server varsa onu kullanır, yoksa generic fallback.
"""

import os
from .generic import GenericRuntime


class ClineRuntime(GenericRuntime):
    @property
    def name(self) -> str:
        return "cline"

    def setup(self) -> bool:
        result = super().setup()
        if result:
            # MCP web search server var mı kontrol et (ileride)
            mcp_search = os.environ.get("MCP_WEB_SEARCH_URL", "")
            if mcp_search:
                print(f"[cline] ✅ MCP web search server: {mcp_search}")
            else:
                print("[cline] ⚠ MCP web search server bulunamadı, DDG scraping kullanılacak")
        return result

```

### core/tiers.py

```python
"""
Cost tiers — bound how many agent calls a single research run can spawn.

Total agent calls ≈ 2 (scope + synthesize) + angles + max_fetch + (max_verify_claims × votes_per_claim)
  angles            → Scope: how many parallel search angles get generated
  max_fetch         → Search/Fetch: how many URLs get fetched + claim-extracted
  max_verify_claims → Verify: how many claims get adversarially checked
  votes_per_claim   → Verify: independent verifiers per claim

max_workers bounds how many of those calls may run CONCURRENTLY at once — this is the
actual "don't spawn 200 agents simultaneously" knob, independent of the total budget above.
"""

from typing import Dict

TIER_PRESETS: Dict[str, Dict[str, int]] = {
    # ~8 agent calls — sanity check / cheap fact lookup, single-vote verification
    "low": {
        "angles": 2, "max_fetch": 3, "max_verify_claims": 2,
        "votes_per_claim": 1, "refutations_required": 1, "max_workers": 2,
    },
    # ~26 agent calls — decent coverage, light adversarial check
    "medium": {
        "angles": 4, "max_fetch": 8, "max_verify_claims": 6,
        "votes_per_claim": 2, "refutations_required": 2, "max_workers": 4,
    },
    # ~46 agent calls — original hardcoded defaults, kept as the default tier
    "high": {
        "angles": 5, "max_fetch": 15, "max_verify_claims": 12,
        "votes_per_claim": 2, "refutations_required": 2, "max_workers": 6,
    },
    # ~147 agent calls — deep, wide research with 3-vote adversarial verification
    "ultra": {
        "angles": 10, "max_fetch": 30, "max_verify_claims": 35,
        "votes_per_claim": 3, "refutations_required": 2, "max_workers": 10,
    },
}

DEFAULT_TIER = "high"  # preserves pre-existing default behavior

# Hard ceiling: no tier or manual env override may push concurrent agent
# threads past this, no matter how large max_verify_claims/votes_per_claim get.
HARD_MAX_WORKERS = 20


def resolve_tier(tier_name: str) -> Dict[str, int]:
    key = (tier_name or DEFAULT_TIER).strip().lower()
    return TIER_PRESETS.get(key, TIER_PRESETS[DEFAULT_TIER])

```

### core/schemas.py

```python
"""
JSON Schemas for structured research output.
Mirrors Claude Code's deep-research workflow schemas exactly.
"""

SCOPE_SCHEMA = {
    "type": "object",
    "required": ["question", "angles", "summary"],
    "properties": {
        "question": {"type": "string"},
        "summary": {"type": "string"},
        "angles": {
            "type": "array",
            "minItems": 3,
            "maxItems": 6,
            "items": {
                "type": "object",
                "required": ["label", "query"],
                "properties": {
                    "label": {"type": "string"},
                    "query": {"type": "string"},
                    "rationale": {"type": "string"},
                },
            },
        },
    },
}

SEARCH_SCHEMA = {
    "type": "object",
    "required": ["results"],
    "properties": {
        "results": {
            "type": "array",
            "maxItems": 6,
            "items": {
                "type": "object",
                "required": ["url", "title", "relevance"],
                "properties": {
                    "url": {"type": "string"},
                    "title": {"type": "string"},
                    "snippet": {"type": "string"},
                    "relevance": {"enum": ["high", "medium", "low"]},
                },
            },
        },
    },
}

EXTRACT_SCHEMA = {
    "type": "object",
    "required": ["claims", "sourceQuality"],
    "properties": {
        "sourceQuality": {
            "enum": ["primary", "secondary", "blog", "forum", "unreliable"],
        },
        "publishDate": {"type": "string"},
        "claims": {
            "type": "array",
            "maxItems": 5,
            "items": {
                "type": "object",
                "required": ["claim", "quote", "importance"],
                "properties": {
                    "claim": {"type": "string"},
                    "quote": {"type": "string"},
                    "importance": {"enum": ["central", "supporting", "tangential"]},
                },
            },
        },
    },
}

VERDICT_SCHEMA = {
    "type": "object",
    "required": ["refuted", "evidence", "confidence"],
    "properties": {
        "refuted": {"type": "boolean"},
        "evidence": {"type": "string"},
        "confidence": {"enum": ["high", "medium", "low"]},
        "counterSource": {"type": "string"},
    },
}

REPORT_SCHEMA = {
    "type": "object",
    "required": ["summary", "findings", "caveats"],
    "properties": {
        "summary": {"type": "string"},
        "findings": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["claim", "confidence", "sources", "evidence"],
                "properties": {
                    "claim": {"type": "string"},
                    "confidence": {"enum": ["high", "medium", "low"]},
                    "sources": {"type": "array", "items": {"type": "string"}},
                    "evidence": {"type": "string"},
                    "vote": {"type": "string"},
                },
            },
        },
        "caveats": {"type": "string"},
        "openQuestions": {"type": "array", "items": {"type": "string"}},
    },
}

```

[42 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]