145 lines
9.1 KiB
Markdown
145 lines
9.1 KiB
Markdown
---
|
||
date: '2026-05-05'
|
||
status: design-approved
|
||
parent: concepts/interns-design.md
|
||
source_buffer: .meeting-room/.brainstorm/repo-read.md
|
||
title: interns-repo-read-design
|
||
type: concept
|
||
ingested_at: '2026-05-05T20:04:56.490Z'
|
||
ingested_by: OpeItcLoc03@DESKTOP-NSEF0UK
|
||
source_project: .meeting-room
|
||
---
|
||
# Interns — `repo_read` intern (v0.1.0)
|
||
|
||
Расширение каталога `interns-mcp` MVP: добавляет интерн `repo_read`, специализированный на «прочитай эту директорию/репо и ответь на вопрос». Реализуется поверх существующей трёхслойной архитектуры (config / MCP server / skills) без изменений в архитектуре, только новый tool + routing-добавка в `using-interns`.
|
||
|
||
## Context
|
||
|
||
Из исходного research line: «Repomix лучше код». Интерн оборачивает `repomix` CLI + cheap LLM (`deepseek-v4-flash`) в один MCP tool, чтобы Claude делегировал понимание целой кодовой базы дешёвой модели вместо того чтобы прочитать репо своим Read'ом и сжечь Anthropic-квоту.
|
||
|
||
Это первый **tool-wrapping** интерн (subprocess + LLM-call в одной операции) — паттерн под будущие PDF (Marker) и JS-web (Firecrawl) интерны.
|
||
|
||
## Decisions
|
||
|
||
| # | Решение | Аргумент |
|
||
|---|---|---|
|
||
| 1 | Шейп — композитный `repo_read(paths, question) → answer` | Симметрия с `bulk_text_read(paths, question) → answer`. Pure pack не экономит квоту — Claude всё равно читает результат. |
|
||
| 2 | Tool name `repo_read` (не `repo_qa`, не `code_read`) | Симметрия с `bulk_text_read` (тоже семантически Q&A, но `_read`). Не `code_read` — repomix именно про **директорию целиком**, оставляем `code_read` под будущий grep+cheap-LLM интерн. |
|
||
| 3 | Overflow при превышении контекста — hard fail с подсказкой | YAGNI. Без авто-compress / map-reduce / smart-selection. Параметр `compress: bool = False` явный, user-controlled. |
|
||
| 4 | Runtime — `npx repomix@latest` | Zero-config: `setup-interns` только проверяет `node` в PATH. Всегда свежая версия. Первый запуск 5-10s (npm cache populate), потом instant. Pin'ить версию — позже, если будут регрессии. |
|
||
| 5 | Safety — два слоя: input matcher + transitive `--ignore` | Always-ask matcher проверяет input `paths` (как у остальных interns). Дополнительно: `safety.always_ask_globs()` транслируется в флаги `--ignore` для repomix subprocess'а. Repomix walks recursively, нельзя надеяться что `.gitignore` юзера полный. Защита on-server. |
|
||
| 6 | Без `include`/`ignore` параметров от Claude | YAGNI: исключение через узкие `paths` + `.gitignore` + always-ask. Если упрёмся — добавим в v0.2.0. |
|
||
|
||
## Сигнатура
|
||
|
||
```python
|
||
repo_read(
|
||
paths: list[str], # директории и/или файлы — targets для repomix
|
||
question: str, # вопрос
|
||
compress: bool = False, # repomix --compress (lossy: убирает комменты/whitespace)
|
||
) -> InternResponse
|
||
```
|
||
|
||
`InternResponse = {text: str, usage: {tokens_in, tokens_out, cost_usd, packed_files: int, packed_tokens: int}}`
|
||
|
||
При overflow: вместо `InternResponse` возвращается `BudgetExceededError{tokens, limit, top_files: [...], hint}` — Claude получает structured ответ и переформулирует (узкие paths или `compress=True`).
|
||
|
||
## Реализация (`interns_mcp/interns/repo_read.py`)
|
||
|
||
```python
|
||
class RepoRead(Intern):
|
||
id = "repo_read"
|
||
|
||
def run(self, paths, question, compress=False):
|
||
safety.check_paths(paths) # raise if always-ask
|
||
ignore_globs = safety.always_ask_globs() # transitive guard
|
||
|
||
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as packed:
|
||
args = ["npx", "repomix@latest", *paths,
|
||
"--output", packed.name, "--style", "xml"]
|
||
if compress:
|
||
args.append("--compress")
|
||
for glob in ignore_globs:
|
||
args += ["--ignore", glob]
|
||
subprocess.run(args, check=True, timeout=120)
|
||
packed_text = Path(packed.name).read_text()
|
||
|
||
tokens = count_tokens(packed_text, model=self.config.model)
|
||
if tokens > self.config.context_budget:
|
||
return BudgetExceededError(
|
||
tokens=tokens,
|
||
limit=self.config.context_budget,
|
||
top_files=top_files_by_size(packed_text, n=5),
|
||
hint="use compress=True or narrow paths",
|
||
)
|
||
|
||
return self.client.complete(
|
||
system=self.config.system_prompt,
|
||
user=f"# Packed repo\n\n{packed_text}\n\n# Question\n\n{question}",
|
||
max_tokens=self.config.max_tokens,
|
||
)
|
||
```
|
||
|
||
## Layer 1 — config (`.common/config/interns/config.yaml`)
|
||
|
||
```yaml
|
||
interns:
|
||
repo_read:
|
||
description: "Pack a repo/directory via repomix and answer a focused question via cheap LLM."
|
||
endpoint: ollama_cloud
|
||
model: deepseek-v4-flash
|
||
max_tokens: 4096
|
||
temperature: 0.2
|
||
context_budget: 120000 # input-token cap before BudgetExceededError
|
||
system_prompt: |
|
||
You are reading a packed code repository. Answer ONLY what the user asks,
|
||
citing file:line refs from the packed structure. Do not hallucinate file
|
||
contents. If the answer requires files outside the pack, say so explicitly.
|
||
```
|
||
|
||
## Layer 3 — skill update
|
||
|
||
`using-interns/SKILL.md`, секция Routing-подсказки — добавить:
|
||
|
||
> - Вопрос про целое репо/директорию (архитектура, «где используется X», «что делает модуль Y», обзор кодбазы) и нужно прочесть >5 файлов кода → `repo_read`.
|
||
> - `bulk_text_read` vs `repo_read`: первый — пути известны и явные; второй — нужна целая директория без явного выбора файлов.
|
||
> - Не делегировать `repo_read` для редактирования или отладки конкретного файла — читать файл сам.
|
||
|
||
## Setup-side change
|
||
|
||
`setup-interns/SKILL.md` — добавить runtime check:
|
||
|
||
1. `shutil.which("node")` — если нет, instruct: install Node.js 20+, retry.
|
||
2. (Опц.) Pre-warm: `npx --yes repomix@latest --version` чтобы первый реальный вызов был быстрым.
|
||
|
||
Bump `setup-interns` 0.1.0 → 0.2.0 (MINOR — capability added).
|
||
|
||
## Cross-platform
|
||
|
||
| Слой | Windows | Linux | macOS |
|
||
|---|---|---|---|
|
||
| `npx repomix@latest` | OK через node from PATH | OK | OK |
|
||
| `tempfile.NamedTemporaryFile` | OK | OK | OK |
|
||
| Always-ask globs (`PurePath.match`) | OK POSIX-style работают везде | OK | OK |
|
||
| `subprocess.run` timeout | OK | OK | OK |
|
||
|
||
## Action items
|
||
|
||
- [ ] **`.common/lib/interns-mcp`**: реализовать `interns_mcp/interns/repo_read.py`, зарегистрировать в `server.py`. Тесты: happy-path, safety-block (`paths=[".env"]`), budget-exceeded. Patch-bump `interns-mcp`.
|
||
- [ ] **`.common/config/interns/config.yaml`**: добавить запись `repo_read`.
|
||
- [ ] **`claude-skills/using-interns`**: добавить routing-подсказки в SKILL.md. Bump MINOR.
|
||
- [ ] **`claude-skills/setup-interns`**: добавить Node-check + (опц.) pre-warm. Bump MINOR.
|
||
|
||
## Open questions
|
||
|
||
- Реальный context window `deepseek-v4-flash` на Ollama Cloud — проверить эмпирически после первого боевого вызова, скорректировать `context_budget`.
|
||
- Когда появится 2-3-й tool-wrapping интерн (PDF/web) — выделить общий helper `interns_mcp/tool_wrapper.py` для пары (subprocess + safety-translation + budget-check). Сейчас inline в `repo_read.py`.
|
||
- Cost-cap >$0.10 (упомянут в parent spec как always-ask trigger) — не реализован. Если появится несколько tool-wrapping interns с разной стоимостью — добавим `safety.check_cost(estimated)` рядом с `check_paths`.
|
||
|
||
## References
|
||
|
||
- `concepts/interns-design.md` — parent design (архитектура interns-mcp, "How to add an intern" recipe, action items).
|
||
- [Repomix](https://github.com/yamadashy/repomix) — JS CLI, упаковывает директорию в один LLM-friendly файл.
|
||
- `using-interns/SKILL.md` — целевой файл для routing-добавки.
|
||
- `setup-interns/SKILL.md` — целевой файл для Node-check.
|