Files
puaros/packages/ipuaro/docs/CONCEPT.md
2025-11-29 22:12:28 +05:00

1144 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ipuaro: Локальный AI-агент для работы с кодовой базой
## Цель
Один пользователь, одна сессия, работа с кодовой базой любого размера. Ощущение «бесконечного» контекста через ленивую загрузку и умное управление контекстом.
---
## Технологический стек
| Компонент | Технология |
|-----------|------------|
| **Язык** | TypeScript |
| **Runtime** | Node.js |
| **TUI** | Ink (React для терминала) |
| **Storage** | Redis с AOF persistence |
| **AST парсинг** | tree-sitter (js, ts, jsx, tsx, json, yaml) |
| **LLM** | Ollama API (qwen2.5-coder:7b-instruct, 128K контекст) |
| **Git** | simple-git |
| **File watching** | chokidar |
| **CLI** | Commander.js |
| **Тестирование** | Vitest |
| **Сборка** | tsup |
| **Gitignore парсинг** | ignore (npm) |
| **Подсчёт токенов** | Ollama API |
| **Хеширование файлов** | MD5 |
### Поддерживаемые языки
Фокус на TypeScript/JavaScript экосистеме:
- TypeScript (.ts, .tsx)
- JavaScript (.js, .jsx)
- JSON, YAML конфиги
### Структура проекта
```
src/
├── cli/ # Точка входа, CLI парсинг
│ └── index.ts
├── tui/ # Терминальный интерфейс
│ ├── App.tsx # Главный компонент (Ink)
│ ├── components/ # UI компоненты
│ │ ├── Chat.tsx
│ │ ├── StatusBar.tsx
│ │ ├── DiffView.tsx # Отображение изменений (inline highlights)
│ │ ├── ConfirmDialog.tsx # Подтверждение действий
│ │ └── Input.tsx
│ └── hooks/ # React hooks
├── core/ # Ядро системы
│ ├── orchestrator.ts # Оркестратор
│ ├── context.ts # Управление контекстом (ленивая загрузка)
│ ├── undo.ts # Стек отмены (5-10 изменений)
│ └── session.ts # Управление сессией
├── llm/ # Работа с моделью
│ ├── client.ts # Ollama клиент
│ ├── tools.ts # Определения тулов (18 штук)
│ ├── prompts.ts # Системные промпты
│ └── parser.ts # Парсинг ответов модели
├── indexer/ # Индексация проекта
│ ├── scanner.ts # Сканирование файлов
│ ├── parser.ts # AST парсинг (tree-sitter)
│ ├── analyzer.ts # Анализ связей
│ └── watcher.ts # Watchdog (chokidar)
├── redis/ # Работа с Redis
│ ├── client.ts # Подключение + AOF config
│ ├── schema.ts # Типы ключей (группировка по типу)
│ └── queries.ts # Запросы
├── tools/ # Реализация тулов
│ ├── read.ts # get_lines, get_function
│ ├── search.ts # find_references, find_definition
│ ├── edit.ts # edit_lines, create_file
│ ├── git.ts # git_status, git_diff, git_commit
│ ├── run.ts # run_command, run_tests
│ └── index.ts # Регистрация всех тулов
├── security/ # Безопасность
│ ├── blacklist.ts # Blacklist опасных команд
│ ├── whitelist.ts # Whitelist разрешённых команд
│ └── validator.ts # Валидация параметров
├── types/ # TypeScript типы
│ ├── ast.ts
│ ├── redis.ts
│ ├── tools.ts
│ └── session.ts
└── utils/ # Утилиты
├── hash.ts
└── tokens.ts # Подсчёт токенов
tests/
├── unit/
├── integration/
└── fixtures/
config/
├── default.json # Настройки по умолчанию
├── blacklist.json # Blacklist команд
└── whitelist.json # Whitelist команд (расширяемый)
```
---
## Архитектура
```
┌─────────────────────────────────────────────────────────────┐
│ Пользователь │
└─────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ TUI (Ink/React) │
│ Chat режим │
│ - подтверждение изменений (или auto-apply режим) │
│ - diff с inline highlights │
│ - статистика: токены, время, вызванные tools │
└─────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Оркестратор │
│ - начальный контекст: структура + AST (без кода) │
│ - код подгружается через tools по запросу модели │
│ - user choice при ошибках (retry/skip/abort) │
│ - авто-сжатие контекста при >80% │
│ - обработка конфликтов редактирования │
└───────────┬─────────────────┬───────────────────────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────────────────────────┐
│ qwen2.5-coder │ │ Redis + AOF │
│ 7B, 128K ctx │ │ │
│ │ │ - код файлов (lines) │
│ - основная │ │ - AST структуры │
│ - рефакторинг │ │ - метаданные и индексы │
│ - ревью │ │ - сессии (бессрочно) │
│ - tools │ │ │
└───────────────────┘ └───────────────────────────────────────┘
```
### Ключевые принципы
| Принцип | Реализация |
|---------|------------|
| **Одна модель** | qwen2.5-coder:7b-instruct для всех задач |
| **Ленивая загрузка кода** | Код НЕ в начальном контексте. Модель запрашивает через tools |
| **User choice** | При ошибке — выбор пользователя: retry/skip/abort |
| **Код в Redis** | Дублирование для быстрого доступа без I/O |
| **Подтверждение изменений** | По умолчанию спрашивать, опционально auto-apply |
| **Undo stack** | История 5-10 изменений для отката |
| **Без лимитов** | Нет ограничений на размер проекта и файлов |
| **Язык промптов** | Системный промпт на EN, ответы адаптируются к языку пользователя |
| **Монорепозитории** | Индексируется весь репозиторий |
| **Tool format** | XML в промпте (как Claude Code: `<tool_call>`) |
| **Encoding** | Только UTF-8, остальные пропускаются |
| **Symlinks** | Хранятся как метаданные (имя, target) |
---
## Redis: структура данных
### Принцип: группировка по типу
Вместо множества мелких ключей — несколько больших JSON-структур:
```
# ═══════════════════════════════════════════════════════════════
# ПРОЕКТ (5 основных ключей)
# Имя проекта: {parent-folder}-{project-folder}
# Пример: projects-myapp
# ═══════════════════════════════════════════════════════════════
project:{name}:files # Hash: path → {lines[], hash, size, lastModified}
project:{name}:ast # Hash: path → {imports[], exports[], functions[], classes[], parseError?}
project:{name}:meta # Hash: path → {complexity, deps, dependents, isHub, isEntry}
project:{name}:indexes # Hash: symbols, deps_graph, call_graph
project:{name}:config # Hash: settings, whitelist, last_indexed
# ═══════════════════════════════════════════════════════════════
# СЕССИИ (бессрочное хранение)
# ═══════════════════════════════════════════════════════════════
session:{id}:data # Hash: history[], context, created_at, last_activity, stats
session:{id}:undo # List: стек изменений для отмены (5-10 элементов)
sessions:list # List: все session_id для проекта
```
### Итого ключей
| Категория | Ключей | Описание |
|-----------|--------|----------|
| Проект | 5 | files, ast, meta, indexes, config |
| Сессия | 3 на сессию | data + undo + общий list |
| **Всего** | ~10-20 | Компактная структура |
### Пример данных в project:{name}:files
```json
{
"src/auth/login.ts": {
"lines": ["import jwt from 'jsonwebtoken'", "import { User } from '../models'", "..."],
"hash": "a3f2b1c...",
"size": 2431,
"lastModified": 1705312200
}
}
```
### Пример данных в project:{name}:ast
```json
{
"src/auth/login.ts": {
"imports": [
{"name": "jwt", "from": "jsonwebtoken", "line": 1, "type": "external"},
{"name": "User", "from": "../models", "line": 2, "type": "internal"}
],
"exports": [
{"name": "AuthManager", "type": "class", "line": 10},
{"name": "refreshToken", "type": "function", "line": 55}
],
"functions": [
{"name": "refreshToken", "lineStart": 55, "lineEnd": 70, "params": ["token"], "isAsync": true}
],
"classes": [
{
"name": "AuthManager",
"lineStart": 10,
"lineEnd": 52,
"methods": [
{"name": "login", "lineStart": 15, "lineEnd": 30, "params": ["username", "password"]},
{"name": "validateToken", "lineStart": 32, "lineEnd": 50, "params": ["token"]}
]
}
],
"parseError": false
}
}
```
### Redis конфигурация (AOF)
```conf
# redis.conf
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
```
---
## Индексация проекта
### Первичная индексация
```
При первом запуске:
0. ПРОВЕРКА РАЗМЕРА
└→ Если >10K файлов → показать предупреждение
└→ Спросить: продолжить / выбрать поддиректорию / отмена
1. СКАНИРОВАНИЕ (с прогресс-баром)
└→ [=====> ] 45% (120/267 files)
└→ Обход файловой системы проекта
└→ Фильтрация: .gitignore, node_modules, dist, etc
└→ TypeScript/JavaScript файлы + JSON/YAML
└→ Только UTF-8 файлы (остальные пропускаются)
2. ПАРСИНГ (для каждого файла, без ограничений размера)
└→ Чтение файла → lines[] → Redis (project:{name}:files)
└→ Парсинг через tree-sitter → Redis (project:{name}:ast)
└→ При ошибке парсинга → parseError: true в AST
└→ Базовые метрики → Redis (project:{name}:meta)
3. ПОСТРОЕНИЕ ИНДЕКСОВ
└→ Граф импортов между файлами
└→ Индекс символов (функции, классы)
└→ Redis (project:{name}:indexes)
```
### Watchdog (chokidar)
```
Фоновый процесс отслеживает изменения:
1. chokidar.watch() на директорию проекта
2. Debounce: 500ms (группировка быстрых изменений)
3. При изменении файла:
└→ Пересчитать MD5 hash
└→ Если изменился:
└→ Тихо обновить lines в Redis
└→ Перепарсить AST через tree-sitter
└→ Обновить метрики
4. Модель получит свежие данные при следующем запросе через tools
Бинарные файлы (.png, .pdf, etc):
└→ Индексируются только метаданные (имя, размер, тип)
└→ Содержимое не хранится
```
---
## Tools модели (18 штук)
### Core Tools — Чтение
| Tool | Описание | Параметры |
|------|----------|-----------|
| `get_lines` | Получить строки кода | `path`, `start?`, `end?` |
| `get_function` | Получить функцию по имени | `path`, `name` |
| `get_class` | Получить класс по имени | `path`, `name` |
### Core Tools — Редактирование (требуют подтверждения или auto-apply)
| Tool | Описание | Параметры |
|------|----------|-----------|
| `edit_lines` | Заменить строки | `path`, `start`, `end`, `content` |
| `create_file` | Создать новый файл | `path`, `content` |
| `delete_file` | Удалить файл | `path` |
### Core Tools — Поиск и навигация
| Tool | Описание | Параметры |
|------|----------|-----------|
| `find_references` | Где используется символ | `symbol` |
| `find_definition` | Где определён символ | `symbol` |
| `get_structure` | Получить структуру проекта | `path?` |
### Core Tools — Анализ
| Tool | Описание | Параметры |
|------|----------|-----------|
| `get_dependencies` | От чего зависит файл | `path` |
| `get_dependents` | Что зависит от файла | `path` |
| `get_complexity` | Метрики сложности | `path?` |
| `get_todos` | TODO/FIXME в проекте | `path?` |
### Core Tools — Git и выполнение
| Tool | Описание | Параметры |
|------|----------|-----------|
| `git_status` | Статус репозитория | — |
| `git_diff` | Незакоммиченные изменения | `path?` |
| `git_commit` | Коммит изменений | `message`, `files?` |
| `run_command` | Shell команда (whitelist) | `command` |
| `run_tests` | Запустить тесты | `path?`, `filter?` |
### Итого: 18 tools
Модель получает структуру проекта и AST в начальном контексте, а код запрашивает через tools по мере необходимости.
---
## Безопасность
### Blacklist опасных команд
```json
{
"blacklist": [
"rm -rf",
"rm -r",
"git push --force",
"git reset --hard",
"git clean -fd",
"npm publish",
"sudo",
"chmod",
"chown"
]
}
```
### Whitelist команд для run_command
```json
{
"default": [
"npm", "pnpm", "yarn",
"git",
"node", "npx", "tsx",
"vitest", "jest",
"tsc", "eslint", "prettier"
],
"user": []
}
```
Пользователь может расширять whitelist через конфиг:
```json
{
"whitelist": {
"user": ["bun", "deno", "cargo"]
}
}
```
### Логика выполнения
```typescript
async function runCommand(command: string): Promise<ToolResult> {
const [cmd, ...args] = command.split(' ')
// 1. Проверка blacklist — безусловный отказ
if (isBlacklisted(command)) {
return { success: false, error: `Команда запрещена: ${command}` }
}
// 2. Проверка whitelist (default + user)
if (!isWhitelisted(cmd)) {
// 3. Спросить пользователя для неизвестных команд
const confirmed = await askUserConfirmation(
`Команда '${cmd}' не в whitelist. Выполнить?`
)
if (!confirmed) {
return { success: false, error: `Команда отклонена пользователем` }
}
}
// 4. Выполнение
return execute(command)
}
```
### Валидация параметров tools
```typescript
function validatePath(path: string): boolean {
// Запрет path traversal
if (path.includes('..')) return false
// Только внутри проекта
if (!path.startsWith(projectRoot)) return false
return true
}
```
---
## Управление контекстом
### Начальный контекст (при старте сессии)
```
ФИКСИРОВАННАЯ ЧАСТЬ:
├── Системный промпт с ролью и правилами
├── Список tools с описаниями
└── ВСЁ из Redis КРОМЕ кода:
├── Структура проекта (папки, файлы)
├── AST метаданные (imports, exports, функции, классы — БЕЗ тел)
├── Граф зависимостей между файлами
└── Метрики (complexity, hubs, entry points)
КОД НЕ ВКЛЮЧАЕТСЯ — модель запрашивает через tools по необходимости
```
### Бюджет токенов (128K окно)
| Компонент | Токены | % |
|-----------|--------|---|
| Системный промпт | ~2,000 | 1.5% |
| Структура + AST meta | ~10,000 | 8% |
| **Доступно для работы** | ~116,000 | 90% |
### Авто-сжатие контекста
```
При заполнении >80%:
1. Комбинированное сжатие:
└→ LLM суммаризирует старые сообщения
└→ Удаляются tool results (код из get_lines, etc.)
└→ Остаются ключевые решения и структура диалога
2. Удалить код файлов не упомянутых 5+ сообщений
└→ Можно запросить снова через tools
3. Уведомление: только в status bar (ctx% меняется)
└→ Нет модального уведомления
```
---
## Error Handling
### Принцип: User Choice
```typescript
interface ErrorResult {
type: 'redis' | 'parse' | 'llm' | 'file' | 'command' | 'conflict'
message: string
recoverable: boolean
}
interface UserChoice {
retry: boolean
skip: boolean
abort: boolean
}
async function handleError(error: ErrorResult): Promise<UserChoice> {
// 1. Логировать (локальная статистика)
stats.recordError(error)
// 2. Показать ошибку
ui.showError(`${error.type}: ${error.message}`)
// 3. Дать выбор пользователю (ВСЕГДА)
return ui.askChoice({
message: 'Что делать?',
options: [
{ key: 'r', label: 'Retry — попробовать снова' },
{ key: 's', label: 'Skip — пропустить и продолжить' },
{ key: 'a', label: 'Abort — остановить' }
]
})
}
```
### Обработка конфликтов редактирования
```typescript
async function handleEditConflict(
path: string,
expectedHash: string,
currentHash: string
): Promise<ConflictChoice> {
ui.showWarning(`⚠️ Файл ${path} был изменён во время генерации`)
return ui.askChoice({
message: 'Файл изменился. Что делать?',
options: [
{ key: 'a', label: 'Apply — применить поверх новой версии' },
{ key: 's', label: 'Skip — пропустить это изменение' },
{ key: 'r', label: 'Regenerate — попросить модель заново' }
]
})
}
```
### Типичные ошибки и варианты
| Ошибка | Варианты |
|--------|----------|
| Redis недоступен | Retry / Abort |
| AST парсинг упал | Skip файл (пометить parseError) / Abort |
| LLM timeout | Retry / Skip запрос / Abort |
| Файл не найден | Skip / Abort |
| Команда не в whitelist | Подтвердить / Skip / Abort |
| Конфликт редактирования | Apply / Skip / Regenerate |
---
## TUI Интерфейс
### Структура экрана
```
┌─────────────────────────────────────────────────────────────┐
│ [ipuaro] [ctx: 12%] [project: projects-myapp] [47m] ✓ │
├─────────────────────────────────────────────────────────────┤
│ │
│ User: объясни как работает авторизация │
│ │
│ Agent: [get_function src/auth/login.ts login] │
│ Авторизация построена на JWT... │
│ ⏱ 3.2s │ 1,247 tokens │ 1 tool call │
│ │
│ User: отрефактори функцию validateToken │
│ │
│ Agent: Предлагаю следующие изменения: │
│ │
│ ┌─── src/auth/login.ts (строки 32-50) ───┐ │
│ │ const isValid = jwt.verify(token) │ │
│ │ const isValid = this.verifyToken() │ ← изменено │
│ │ if (!isValid) throw new AuthError() │ ← добавлено │
│ └────────────────────────────────────────┘ │
│ │
│ Изменения: выделил валидацию, добавил проверку expiry │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ [Y] Применить [N] Отменить [E] Edit │ │
│ └────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ > _ │
└─────────────────────────────────────────────────────────────┘
```
### Команды (7 штук)
| Команда | Действие |
|---------|----------|
| `/help` | Показать справку |
| `/clear` | Очистить историю чата |
| `/undo` | Отменить изменение (стек 5-10) |
| `/sessions` | Управление сессиями (list, load, delete) |
| `/status` | Состояние системы (Redis, контекст, модель, статистика) |
| `/reindex` | Принудительная переиндексация проекта |
| `/eval` | Самопроверка модели на галлюцинации (LLM проверяет свой ответ) |
**Всё остальное — через естественный язык в чате.**
Примеры:
- "покажи все TODO" → модель вызывает `get_todos`
- "найди где используется AuthManager" → модель вызывает `find_references`
- "запусти тесты для auth" → модель вызывает `run_tests`
### Режим работы
**Только Chat.** Один режим для всех задач:
- Код-ревью
- Рефакторинг
- Написание тестов
- Документирование
- Навигация по коду
- Git операции
### Режим Auto-Apply
Опциональный режим для опытных пользователей:
```typescript
// В конфиге или через команду
{
"edit": {
"autoApply": false // по умолчанию выключен
}
}
// Включить на сессию
> /auto-apply on
// При включённом режиме изменения применяются автоматически
// Но всегда можно отменить через /undo
```
### Индикаторы статус-бара
| Индикатор | Описание |
|-----------|----------|
| `ctx: 12%` | Заполненность контекста |
| `project: projects-myapp` | Текущий проект (parent-folder) |
| `main` | Текущая git branch |
| `47m` | Время сессии |
| `✓` / `⟳` / `✗` | ready / thinking / error |
### Статистика ответа
После каждого ответа модели показывается:
- ⏱ Время генерации
- 📊 Количество токенов
- 🔧 Количество вызванных tools
### Горячие клавиши
| Клавиша | Действие |
|---------|----------|
| `Ctrl+C` | Прервать генерацию (1-й раз), выход (2-й раз) |
| `Ctrl+D` | Выход с автосохранением сессии |
| `Ctrl+Z` | Undo (= /undo) |
| `↑/↓` | История ввода |
| `Tab` | Автодополнение путей |
### Подтверждение изменений
При каждом edit_lines, create_file, delete_file (если не auto-apply):
1. **Diff с inline highlights** — подсветка изменённых символов
2. **Syntax highlighting** — полная подсветка синтаксиса в diff
3. **Описание** — краткое объяснение что изменится
4. **Длинные строки** — wrap в терминале (хранятся полностью)
5. **Выбор**:
- `[Y]` — применить изменение
- `[N]` — отменить
- `[E]` — редактировать предложенный код
---
## TypeScript типы
### Метаданные файла
```typescript
interface FileData {
lines: string[]
hash: string
size: number
lastModified: number
}
interface FileAST {
imports: ImportInfo[]
exports: ExportInfo[]
functions: FunctionInfo[]
classes: ClassInfo[]
parseError: boolean // true если tree-sitter не смог распарсить
}
interface FileMeta {
complexity: ComplexityScore // Простой скор: LOC + вложенность + imports
dependencies: string[]
dependents: string[]
isHub: boolean // Комбинированно: >5 dependents ИЛИ часто импортируется
isEntryPoint: boolean // Комбинированно: index.ts/main.ts ИЛИ 0 dependents
}
interface ComplexityScore {
loc: number // Lines of code
nesting: number // Макс. вложенность
imports: number // Количество imports
score: number // Общий скор
}
```
### AST структуры
```typescript
interface ImportInfo {
name: string
from: string
line: number
type: 'internal' | 'external' | 'builtin'
isDefault: boolean
}
interface ExportInfo {
name: string
type: 'function' | 'class' | 'interface' | 'type' | 'const'
line: number
}
interface FunctionInfo {
name: string
lineStart: number
lineEnd: number
params: string[]
returnType?: string
isAsync: boolean
isExported: boolean
}
interface ClassInfo {
name: string
lineStart: number
lineEnd: number
methods: MethodInfo[]
properties: PropertyInfo[]
extends?: string
implements: string[]
isExported: boolean
}
interface MethodInfo {
name: string
lineStart: number
lineEnd: number
params: string[]
visibility: 'public' | 'private' | 'protected'
isStatic: boolean
isAsync: boolean
}
interface PropertyInfo {
name: string
line: number
type?: string
visibility: 'public' | 'private' | 'protected'
isStatic: boolean
isReadonly: boolean
}
```
### Сессия
```typescript
interface Session {
id: string
projectName: string // формат: parent-folder
createdAt: number
lastActivityAt: number
history: ChatMessage[]
context: ContextState
undoStack: UndoEntry[]
stats: SessionStats
inputHistory: string[] // История ввода для ↑/↓ (хранится в Redis)
}
interface SessionStats {
totalTokens: number
totalTime: number
toolCalls: number
editsApplied: number
editsRejected: number
}
interface ChatMessage {
role: 'user' | 'assistant' | 'tool'
content: string
timestamp: number
toolCalls?: ToolCall[]
toolResults?: ToolResult[]
stats?: MessageStats
}
interface MessageStats {
tokens: number
timeMs: number
toolCalls: number
}
interface ContextState {
loadedFiles: string[] // Файлы загруженные в контекст
tokensUsed: number
tokensLimit: number // 128000
}
interface UndoEntry {
id: string
timestamp: number
filePath: string
previousContent: string[]
newContent: string[]
description: string
}
```
### Tools
```typescript
interface ToolDefinition {
name: string
description: string
parameters: ToolParameter[]
handler: (params: Record<string, unknown>) => Promise<ToolResult>
requiresConfirmation: boolean // true для edit/create/delete (если не auto-apply)
}
interface ToolParameter {
name: string
type: 'string' | 'number' | 'boolean'
description: string
required: boolean
default?: unknown
}
interface ToolCall {
id: string
name: string
parameters: Record<string, unknown>
}
interface ToolResult {
callId: string
success: boolean
data?: unknown
error?: string
}
```
### Error Handling
```typescript
interface ErrorResult {
type: 'redis' | 'parse' | 'llm' | 'file' | 'command' | 'conflict'
message: string
recoverable: boolean
suggestion?: string
}
type UserErrorChoice = 'retry' | 'skip' | 'abort'
type ConflictChoice = 'apply' | 'skip' | 'regenerate'
```
---
## Флоу работы
### Общий флоу
```
0. ONBOARDING (минимальный, при первом запуске)
└→ Проверка Redis: подключение → если нет, ошибка с инструкцией
└→ Проверка Ollama: доступность → если нет, ошибка и выход
└→ Проверка модели: qwen2.5-coder:7b-instruct → если нет, предложить скачать
1. ИНИЦИАЛИЗАЦИЯ
└→ Подключение к Redis
└→ Проверка проекта в Redis
└→ Если нет → первичная индексация (tree-sitter)
└→ Запуск watchdog (chokidar, debounce 500ms)
└→ Загрузка последней сессии или создание новой
2. ЗАПРОС ПОЛЬЗОВАТЕЛЯ
└→ Добавить в историю
└→ Сформировать контекст:
- Системный промпт
- Структура проекта + AST (без кода!)
- История (последние N сообщений)
└→ Отправить в модель
└→ Показать индикатор [⟳ thinking...]
3. ОБРАБОТКА ОТВЕТА (ждём полный ответ)
└→ Показать все tool calls по мере получения
└→ Если tool call (чтение):
└→ Валидация параметров
└→ Выполнение
└→ Результат обратно в модель
└→ Если tool call (редактирование):
└→ Проверить конфликт (hash изменился?)
└→ Если конфликт → спросить пользователя
└→ Показать diff с inline highlights + описание
└→ Если auto-apply → применить автоматически
└→ Иначе → СПРОСИТЬ подтверждение (Y/N/E)
└→ Если Y:
└→ Сохранить в undo stack
└→ Применить изменения
└→ Обновить Redis (lines, AST)
└→ Показать статистику: время, токены, tools
└→ Показать ответ пользователю
4. ОШИБКА → User Choice (всегда)
└→ Показать ошибку
└→ Спросить: Retry / Skip / Abort
└→ Выполнить выбор пользователя
5. ПОВТОРИТЬ с шага 2
```
### Флоу код-ревью (через чат)
```
User: "сделай ревью src/auth/login.ts"
Agent:
1. get_lines(src/auth/login.ts) → получить код
2. get_dependencies(src/auth/login.ts) → понять контекст
3. get_todos(src/auth/login.ts) → найти TODO
4. Анализ:
- Баги и edge cases
- Стиль кода
- Security issues
- Performance issues
5. Вывод: список замечаний с номерами строк
6. Статистика: ⏱ 5.2s │ 2,847 tokens │ 3 tool calls
```
### Флоу рефакторинга (через чат)
```
User: "отрефактори функцию validateToken"
Agent:
1. find_definition(validateToken) → найти где
2. get_function(path, validateToken) → получить код
3. find_references(validateToken) → где используется
4. Планирование изменений
5. Проверить не изменился ли файл
6. Показать diff с inline highlights + описание
7. Спросить подтверждение [Y/N/E] (или auto-apply)
8. Если Y: edit_lines(...) → применить
9. Добавить в undo stack
10. Статистика ответа
```
### Флоу написания тестов (через чат)
```
User: "напиши тесты для AuthManager.login"
Agent:
1. get_class(src/auth/login.ts, AuthManager) → получить класс
2. get_dependencies(src/auth/login.ts) → что мокать
3. Генерация тестов:
- Happy path
- Edge cases
- Error cases
4. Показать diff для нового файла
5. Спросить подтверждение [Y/N/E]
6. Если Y: create_file(tests/auth/login.test.ts, content)
7. run_tests(tests/auth/login.test.ts) → проверить
```
---
## Что НЕ делаем
| Отвергнуто | Причина |
|------------|---------|
| RAG с векторной БД | Оверкилл, tree-sitter + Redis достаточно |
| Вторая модель для ревью | Overhead, одна модель справляется |
| TypeScript Compiler API | tree-sitter легче и мультиязычнее |
| Regex поиск по коду | Модель сама запрашивает нужный код через tools |
| Understanding через LLM | Модель понимает код при чтении через tools |
| Множество режимов работы | Один Chat режим покрывает всё |
| Сложная структура Redis | Группировка по типу проще |
| Python/Go/Rust поддержка | Фокус на TypeScript/JS |
| Автоматическое восстановление | User choice надёжнее |
| Стриминг по токенам | Ждём полный ответ + статистика |
| Внешние API интеграции | Только TUI, монолит |
| Плагины/расширения | Всё встроено |
| Файловые логи | Только локальная статистика |
| Миграции данных | При несовместимости — /reindex |
---
## Конфигурация
### config/default.json
```json
{
"redis": {
"host": "localhost",
"port": 6379,
"db": 0
},
"llm": {
"provider": "ollama",
"model": "qwen2.5-coder:7b-instruct",
"contextWindow": 128000,
"temperature": 0.1,
"systemPromptLanguage": "en"
},
"project": {
"scope": "repository",
"ignorePatterns": [
"node_modules",
"dist",
"build",
".git",
"*.min.js"
],
"binaryExtensions": [".png", ".jpg", ".pdf", ".zip", ".woff"]
},
"session": {
"persistIndefinitely": true,
"maxHistoryMessages": 100,
"saveInputHistory": true
},
"context": {
"systemPromptTokens": 2000,
"maxContextUsage": 0.8,
"autoCompressAt": 0.8,
"compressionMethod": "llm-summary"
},
"watchdog": {
"debounceMs": 500
},
"commands": {
"timeout": null
},
"undo": {
"stackSize": 10
},
"edit": {
"alwaysConfirm": true,
"autoApply": false,
"showDiff": true,
"diffFormat": "inline-highlights",
"syntaxHighlight": true,
"showDescription": true,
"wrapLongLines": true
},
"display": {
"showStats": true,
"showToolCalls": true,
"theme": "dark",
"bellOnComplete": true,
"progressBar": true
},
"autocomplete": {
"source": "redis-index"
},
"input": {
"multiline": "auto"
},
"llm": {
"toolFormat": "xml",
"autoRetry": false,
"cache": false
},
"limits": {
"maxFilesWarning": 10000,
"encoding": "utf-8"
}
}
```
### config/blacklist.json
```json
{
"commands": [
"rm -rf",
"rm -r",
"git push --force",
"git reset --hard",
"git clean -fd",
"npm publish",
"sudo",
"chmod",
"chown"
]
}
```
### config/whitelist.json
```json
{
"default": [
"npm",
"pnpm",
"yarn",
"git",
"node",
"npx",
"tsx",
"vitest",
"jest",
"tsc",
"eslint",
"prettier"
],
"user": [],
"askForUnknown": true
}
```
---
## Метрики успеха
| Метрика | Цель |
|---------|------|
| Время первой индексации | Зависит от размера проекта |
| Время ответа модели | Стриминг, показывать статистику |
| Использование памяти Redis | Зависит от проекта |
| Точность AST парсинга (tree-sitter) | > 99% для TypeScript |
| Время синхронизации watchdog | < 1 секунда |
| Локальная статистика | Токены, время, tools за сессию |