mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
41 KiB
41 KiB
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
{
"src/auth/login.ts": {
"lines": ["import jwt from 'jsonwebtoken'", "import { User } from '../models'", "..."],
"hash": "a3f2b1c...",
"size": 2431,
"lastModified": 1705312200
}
}
Пример данных в project:{name}:ast
{
"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)
# 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 опасных команд
{
"blacklist": [
"rm -rf",
"rm -r",
"git push --force",
"git reset --hard",
"git clean -fd",
"npm publish",
"sudo",
"chmod",
"chown"
]
}
Whitelist команд для run_command
{
"default": [
"npm", "pnpm", "yarn",
"git",
"node", "npx", "tsx",
"vitest", "jest",
"tsc", "eslint", "prettier"
],
"user": []
}
Пользователь может расширять whitelist через конфиг:
{
"whitelist": {
"user": ["bun", "deno", "cargo"]
}
}
Логика выполнения
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
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
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 — остановить' }
]
})
}
Обработка конфликтов редактирования
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
Опциональный режим для опытных пользователей:
// В конфиге или через команду
{
"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):
- Diff с inline highlights — подсветка изменённых символов
- Syntax highlighting — полная подсветка синтаксиса в diff
- Описание — краткое объяснение что изменится
- Длинные строки — wrap в терминале (хранятся полностью)
- Выбор:
[Y]— применить изменение[N]— отменить[E]— редактировать предложенный код
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 структуры
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
}
Сессия
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
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
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
{
"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
{
"commands": [
"rm -rf",
"rm -r",
"git push --force",
"git reset --hard",
"git clean -fd",
"npm publish",
"sudo",
"chmod",
"chown"
]
}
config/whitelist.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 за сессию |