Files
puaros/packages/ipuaro/CONCEPT.md
2025-11-29 22:10:32 +05:00

41 KiB
Raw Blame History

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):

  1. Diff с inline highlights — подсветка изменённых символов
  2. Syntax highlighting — полная подсветка синтаксиса в diff
  3. Описание — краткое объяснение что изменится
  4. Длинные строки — wrap в терминале (хранятся полностью)
  5. Выбор:
    • [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 за сессию