Metadata-Version: 2.4
Name: llm-context-router
Version: 0.6.0
Summary: Scope your AI context before it explodes
Author-email: Salvatore Pesti <sal@saildev.org>
License-Expression: MIT
Project-URL: Homepage, https://github.com/SalDevX/context-router
Project-URL: Issues, https://github.com/SalDevX/context-router/issues
Keywords: ai,llm,context,routing,agents,developer-tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: click>=8.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-randomly; extra == "dev"

# context-router

## Problem

Working with AI tools on large codebases causes context explosion.
Every task loads the entire codebase into the prompt.

### What context-router does

Routes a task to the agent that owns it.
Scopes the prompt to only the relevant files.
Keeps a lightweight map of who owns what.

### What context-router does NOT do

Does not call any LLM.
Does not automate anything.
Does not execute code.

## Without context-router:
  "fix the auth bug" → loads 847 files → model touches 6 modules it shouldn't

## With context-router:
  context-router ask "fix the auth bug" --file src/auth/token.py
  → Agent: backend-engineer
  → Touch only: src/auth/token.py, src/auth/session.py
  → Do not modify: src/api/, src/db/, src/frontend/

## Install

```sh
pip install context-router
```

Local dev:

```sh
pip install -e .
```

## Usage

**1. Define your agents**

`context-router init` creates 3 core agents automatically:

```
agents/
├── dispatcher.agent.yaml      ← routes all tasks (do not edit)
├── memory-keeper.agent.yaml   ← syncs memory after commits (do not edit)
├── validator.agent.yaml       ← validates diffs before applying (do not edit)
└── default.agent.yaml         ← template — copy and rename for your domains
```

Add one agent per domain in your project.

Create an `agents/` directory in your project and add one YAML file per agent:

```yaml
# agents/backend-engineer.agent.yaml
name: backend-engineer
owned_paths:
  - src/auth/*.py
  - src/api/*.py
  - src/db/*.py
description: Handles backend logic including authentication, API routes, and database models.
```

**2. Initialize**

```sh
context-router init
```

Specify your AI tool:
```sh
context-router init --tool claude    # Claude Code
context-router init --tool cursor    # Cursor
context-router init --tool copilot   # GitHub Copilot
context-router init --tool codex     # Codex / OpenAI
context-router init --tool windsurf  # Windsurf
context-router init --tool aider     # Aider
context-router init --tool all       # generate all
```

If you skip `--tool`, context-router detects your tool automatically or asks you to choose.

| Tool | Config file generated |
|---|---|
| Claude Code | `CLAUDE.md` + `AGENTS.md` |
| Cursor | `.cursorrules` |
| GitHub Copilot | `.github/copilot-instructions.md` |
| Codex | `AGENTS.md` |
| Windsurf | `.windsurfrules` |
| Aider | `.aider.conf.yml` |

Scans all `*.agent.yaml` files in `agents/`, writes `.context-router/project_map.json`, and
scaffolds the full memory structure.

After `context-router init` your project contains:

```
.context-router/
├── CORE.md                    ← global invariants (always loaded)
├── CORE_LITE.md               ← minimal version for quick tasks
├── project_map.json           ← agent ownership map (auto-generated)
└── memory/
    ├── MEMORY.md              ← status, architecture, recent fixes
    ├── MEMORY_MAP.md          ← file/function ownership
    ├── MEMORY_CHANGELOG.md    ← full fix history (load on demand)
    ├── MEMORY_INTELLIGENCE.md ← domain knowledge (load on demand)
    └── MEMORY_REFERENCE.md   ← lessons learned, roadmap (load on demand)
```

Session start prompt for your AI tool:
```
Read: .context-router/CORE.md
Read: .context-router/memory/MEMORY.md
Read: .context-router/memory/MEMORY_MAP.md
Then paste: context-router ask "your task" --file path/to/file
```

For quick/simple tasks use `CORE_LITE.md` instead of `CORE.md`.
Load `MEMORY_CHANGELOG.md` only when debugging old behavior.
Load `MEMORY_INTELLIGENCE.md` only for domain-specialist tasks.

```
Initialized. 3 agents registered.
```

**3. Route a task**

```sh
context-router ask "fix the auth token expiry bug" --file src/auth/token.py
```

```
Agent:   backend-engineer
Files:   src/auth/token.py, src/auth/session.py
Prompt:
  Read: agents/backend-engineer.agent.yaml
  Task: fix the auth token expiry bug
  Touch only: src/auth/token.py, src/auth/session.py
  Do not modify: src/api/routes.py, src/db/models.py
```

Without `--file`, context-router keyword-matches your task against agent descriptions:

```sh
context-router ask "authentication is broken"
```

```
Agent:   backend-engineer
Files:   src/auth/*.py, src/api/*.py, src/db/*.py
Prompt:
  Read: agents/backend-engineer.agent.yaml
  Task: authentication is broken
  Touch only: src/auth/*.py, src/api/*.py, src/db/*.py
  Do not modify: src/ui/*.tsx, src/infra/*.sh
```

**4. Validate a diff**

```sh
context-router validate --staged          # checks git diff --cached
context-router validate --diff my.patch   # checks a saved diff file
```

Loads `.context-router/validator.yaml` and runs all matching rules against the diff.
Exits 0 on PASS, 1 on FAIL.

```
STATUS: FAIL
REASON: 'reader.readtext' used outside `with _ocr_lock:` block
RETRY_WITH:
FIX_HINT: Wrap reader.readtext() inside `with _ocr_lock:`
```

Rule types: `lock_guard`, `pair_call`, `no_output`, `line_count`, `threshold_drift`.
See `.context-router/validator.yaml` (generated by `context-router init`) for full field reference.

## Usage — run command

### Manual mode (no API key needed)

```sh
context-router run "fix the login form" --file src/frontend/login.js
```

```
─────────────────────────────────────
  Agent:   frontend
  Prompt:  .context-router/current-task.md
─────────────────────────────────────
  1. Paste the prompt into your AI tool
  2. Save the diff output as:
     .context-router/output.diff
  3. Run: context-router run --continue
─────────────────────────────────────
```

### Auto mode (API key in .env)

```sh
cp .env.example .env
# add your key to .env
context-router run "fix the login form" --file src/frontend/login.js
```

```
Auto mode — using anthropic (claude-sonnet-4-6)
Attempt 1/3...
STATUS: PASS
Apply the diff to your codebase.
```

### Override model

```sh
context-router run "task" --file path --model claude-opus-4-6
```

## Meta-controller retry loop

`MetaController` wraps any agent/validator pair with a failure-aware retry loop.

```python
from context_router.meta_controller import MetaController
from pathlib import Path

mc = MetaController(
    task="fix the token expiry bug",
    root=Path("."),
    config_path=Path(".context-router/retry_config.yaml"),
)
result = mc.run(agent_runner, validator)
# result = {"passed": True/False, "attempts": N, "report_path": None | "logs/..."}
```

`agent_runner(task, prompt_patches, param_patches) → str`
`validator(output) → (bool passed, str fail_text)`

On FAIL, the controller:
1. Stashes working tree (`git stash`)
2. Builds a retry plan from `retry_config.yaml` (prompt constraints + param overrides)
3. Re-runs the agent with accumulated patches
4. On `retry_with: human` — escalates immediately, writes failure report
5. After MAX_RETRIES (3) — restores working tree, writes report to `logs/`

**retry_config.yaml example:**

```yaml
categories: [system, logic, output, cross_agent]

prompt_patches:
  logic:
    2: "add inline comments explaining each change"
    3: "minimal fix only — change one thing at a time"

category_map:
  my_rule_id: logic

human_hints:
  logic: "Logic error — review the change manually."
```

See `examples/reel-engine/retry_config.yaml` for a full real-world example.

## Agent YAML format

```yaml
name: string                    # unique identifier, used in output
owned_paths:                    # glob patterns — files this agent is responsible for
  - src/auth/*.py
  - src/api/*.py
description: one sentence       # used for keyword fallback matching when no --file given
```

## Adding a new agent

1. Create `agents/<name>.agent.yaml`
2. Run `context-router init`

That's it.

## How it works

```
context-router init
  └── scans agents/*.agent.yaml
  └── writes .context-router/project_map.json  {glob → agent_name}

context-router ask "task" --file src/auth/token.py
  └── loads project_map.json
  └── matches file path against globs
  └── prints structured prompt block
  └── no network, no LLM, no side effects
```

## Optional: graphify

[graphify](https://github.com/graphify-dev/graphify) builds an AST-based knowledge graph of your codebase.
Zero API cost. Run it after every code change:

```sh
graphify update .
```

Output: `graphify-out/GRAPH_REPORT.md` — node/edge/community structure for your codebase.

## How graphify integrates with context-router

Data flow:
```
code change
  → graphify update .
  → graphify-out/GRAPH_REPORT.md   (always regenerated)
  → @memory-keeper (post-commit)
      reads GRAPH_REPORT.md
      updates MEMORY_MAP.md        (always, after every commit)

agents during a task
  → read MEMORY_MAP.md             (session start, always)
  → read GRAPH_REPORT.md           (on demand only — unknown deps)
```

Rules:
- `graphify update .` is always a plain shell command — no API calls
- `GRAPH_REPORT.md` is read on demand only (not at session start)
- `MEMORY_MAP.md` is the agent-readable ownership table
- `graphify-out/` files are never loaded at session start
- graphify never writes to `.context-router/` directly

Trigger graphify after every code change:
```sh
graphify update .
```

Your AI tool reads the output:
```
Read graphify-out/GRAPH_REPORT.md only when tracing
unknown cross-file dependencies.
```

## Success criteria

- Install in under 2 minutes
- Understand it fully in under 5 minutes
- Add a new agent by editing one YAML file
- Reduces context size when working with any AI coding tool
