From 625e109c0a3a0ef62d9b25393df7c94f11852923 Mon Sep 17 00:00:00 2001 From: imfozilbek Date: Sat, 29 Nov 2025 22:10:32 +0500 Subject: [PATCH] feat: add ipuaro package with concept and roadmap --- packages/ipuaro/.gitignore | 13 + packages/ipuaro/.npmignore | 38 + packages/ipuaro/CONCEPT.md | 1143 +++++++++++++++++++++++++++++++ packages/ipuaro/LICENSE | 21 + packages/ipuaro/ROADMAP.md | 1331 ++++++++++++++++++++++++++++++++++++ 5 files changed, 2546 insertions(+) create mode 100644 packages/ipuaro/.gitignore create mode 100644 packages/ipuaro/.npmignore create mode 100644 packages/ipuaro/CONCEPT.md create mode 100644 packages/ipuaro/LICENSE create mode 100644 packages/ipuaro/ROADMAP.md diff --git a/packages/ipuaro/.gitignore b/packages/ipuaro/.gitignore new file mode 100644 index 0000000..aa95ec3 --- /dev/null +++ b/packages/ipuaro/.gitignore @@ -0,0 +1,13 @@ +# Build output +dist/ +*.tsbuildinfo + +# Dependencies +node_modules/ + +# Test coverage +coverage/ + +# Logs +*.log +npm-debug.log* diff --git a/packages/ipuaro/.npmignore b/packages/ipuaro/.npmignore new file mode 100644 index 0000000..825186b --- /dev/null +++ b/packages/ipuaro/.npmignore @@ -0,0 +1,38 @@ +# Source files (only publish dist/) +src/ +*.ts +!*.d.ts + +# Build artifacts +tsconfig.json +tsconfig.*.json +tsconfig.tsbuildinfo +*.tsbuildinfo + +# Tests +**/*.spec.ts +**/*.test.ts +__tests__/ +coverage/ + +# Development +node_modules/ +.env +.env.* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Git +.git/ +.gitignore + +# Other +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store diff --git a/packages/ipuaro/CONCEPT.md b/packages/ipuaro/CONCEPT.md new file mode 100644 index 0000000..21ebc27 --- /dev/null +++ b/packages/ipuaro/CONCEPT.md @@ -0,0 +1,1143 @@ +# 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: ``) | +| **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 { + 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 { + // 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 { + 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) => Promise + 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 +} + +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 за сессию | diff --git a/packages/ipuaro/LICENSE b/packages/ipuaro/LICENSE new file mode 100644 index 0000000..96a01a9 --- /dev/null +++ b/packages/ipuaro/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Fozilbek Samiyev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/ipuaro/ROADMAP.md b/packages/ipuaro/ROADMAP.md new file mode 100644 index 0000000..9bda1a4 --- /dev/null +++ b/packages/ipuaro/ROADMAP.md @@ -0,0 +1,1331 @@ +# ipuaro Roadmap + +Local AI agent for codebase operations with "infinite" context feeling through lazy loading. + +## Project Structure (Clean Architecture) + +``` +packages/ipuaro/ +├── bin/ +│ └── ipuaro.js # CLI entry point +├── src/ +│ ├── domain/ # Business logic (no dependencies) +│ │ ├── entities/ +│ │ │ ├── Session.ts +│ │ │ └── Project.ts +│ │ ├── value-objects/ +│ │ │ ├── FileData.ts +│ │ │ ├── FileAST.ts +│ │ │ ├── FileMeta.ts +│ │ │ ├── ChatMessage.ts +│ │ │ ├── ToolCall.ts +│ │ │ ├── ToolResult.ts +│ │ │ └── UndoEntry.ts +│ │ ├── services/ # Interfaces (ports) +│ │ │ ├── IStorage.ts +│ │ │ ├── ILLMClient.ts +│ │ │ ├── ITool.ts +│ │ │ └── IIndexer.ts +│ │ └── constants/ +│ │ └── index.ts +│ ├── application/ # Use cases & orchestration +│ │ ├── use-cases/ +│ │ │ ├── StartSession.ts +│ │ │ ├── HandleMessage.ts +│ │ │ ├── IndexProject.ts +│ │ │ ├── ExecuteTool.ts +│ │ │ └── UndoChange.ts +│ │ ├── dtos/ +│ │ │ ├── SessionDto.ts +│ │ │ ├── MessageDto.ts +│ │ │ └── ToolCallDto.ts +│ │ ├── mappers/ +│ │ │ └── SessionMapper.ts +│ │ └── interfaces/ +│ │ └── IToolRegistry.ts +│ ├── infrastructure/ # External implementations +│ │ ├── storage/ +│ │ │ ├── RedisClient.ts +│ │ │ ├── RedisStorage.ts +│ │ │ └── schema.ts +│ │ ├── llm/ +│ │ │ ├── OllamaClient.ts +│ │ │ ├── prompts.ts +│ │ │ └── ResponseParser.ts +│ │ ├── indexer/ +│ │ │ ├── FileScanner.ts +│ │ │ ├── ASTParser.ts +│ │ │ ├── MetaAnalyzer.ts +│ │ │ ├── IndexBuilder.ts +│ │ │ └── Watchdog.ts +│ │ ├── tools/ +│ │ │ ├── registry.ts +│ │ │ ├── read/ +│ │ │ │ ├── GetLinesTool.ts +│ │ │ │ ├── GetFunctionTool.ts +│ │ │ │ ├── GetClassTool.ts +│ │ │ │ └── GetStructureTool.ts +│ │ │ ├── edit/ +│ │ │ │ ├── EditLinesTool.ts +│ │ │ │ ├── CreateFileTool.ts +│ │ │ │ └── DeleteFileTool.ts +│ │ │ ├── search/ +│ │ │ │ ├── FindReferencesTool.ts +│ │ │ │ └── FindDefinitionTool.ts +│ │ │ ├── analysis/ +│ │ │ │ ├── GetDependenciesTool.ts +│ │ │ │ ├── GetDependentsTool.ts +│ │ │ │ ├── GetComplexityTool.ts +│ │ │ │ └── GetTodosTool.ts +│ │ │ ├── git/ +│ │ │ │ ├── GitStatusTool.ts +│ │ │ │ ├── GitDiffTool.ts +│ │ │ │ └── GitCommitTool.ts +│ │ │ └── run/ +│ │ │ ├── RunCommandTool.ts +│ │ │ └── RunTestsTool.ts +│ │ ├── security/ +│ │ │ ├── Blacklist.ts +│ │ │ ├── Whitelist.ts +│ │ │ └── PathValidator.ts +│ │ └── constants/ +│ │ ├── blacklist.ts +│ │ └── whitelist.ts +│ ├── shared/ # Cross-cutting concerns +│ │ ├── types/ +│ │ │ └── index.ts +│ │ ├── constants/ +│ │ │ ├── config.ts +│ │ │ └── messages.ts +│ │ ├── utils/ +│ │ │ ├── hash.ts +│ │ │ └── tokens.ts +│ │ └── errors/ +│ │ └── IpuaroError.ts +│ ├── tui/ # Terminal UI (Ink/React) +│ │ ├── App.tsx +│ │ ├── components/ +│ │ │ ├── StatusBar.tsx +│ │ │ ├── Chat.tsx +│ │ │ ├── Input.tsx +│ │ │ ├── DiffView.tsx +│ │ │ ├── ConfirmDialog.tsx +│ │ │ ├── ErrorDialog.tsx +│ │ │ └── Progress.tsx +│ │ └── hooks/ +│ │ ├── useSession.ts +│ │ ├── useHotkeys.ts +│ │ └── useAutocomplete.ts +│ └── cli/ # CLI commands +│ ├── index.ts +│ └── commands/ +│ ├── start.ts +│ ├── init.ts +│ └── index-cmd.ts +├── tests/ +│ ├── unit/ +│ │ ├── domain/ +│ │ ├── application/ +│ │ └── infrastructure/ +│ ├── e2e/ +│ │ ├── cli.test.ts +│ │ └── full-flow.test.ts +│ └── fixtures/ +│ └── sample-project/ +├── examples/ +│ └── demo-project/ +├── config/ +│ ├── default.json +│ ├── blacklist.json +│ └── whitelist.json +├── CHANGELOG.md +├── TODO.md +├── README.md +├── package.json +├── tsconfig.json +└── vitest.config.ts +``` + +--- + +## Version 0.1.0 - Foundation ⚙️ + +**Priority:** CRITICAL + +### 0.1.1 - Project Setup + +**Dependencies:** +```json +{ + "dependencies": { + "ink": "^4.0.0", + "ink-text-input": "^5.0.0", + "react": "^18.0.0", + "ioredis": "^5.0.0", + "tree-sitter": "^0.20.0", + "tree-sitter-typescript": "^0.20.0", + "tree-sitter-javascript": "^0.20.0", + "ollama": "^0.5.0", + "simple-git": "^3.0.0", + "chokidar": "^3.0.0", + "commander": "^11.0.0", + "zod": "^3.0.0", + "ignore": "^5.0.0" + }, + "devDependencies": { + "vitest": "^1.0.0", + "@vitest/coverage-v8": "^1.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0" + } +} +``` + +**Deliverables:** +- [ ] package.json with all dependencies +- [ ] tsconfig.json (strict, jsx react, nodenext) +- [ ] tsup.config.ts (bundle ESM + CJS) +- [ ] vitest.config.ts (coverage 80%) +- [ ] bin/ipuaro.js entry point + +### 0.1.2 - Domain Value Objects + +```typescript +// src/domain/value-objects/FileData.ts +interface FileData { + lines: string[] + hash: string // MD5 + size: number + lastModified: number +} + +// src/domain/value-objects/FileAST.ts +interface FileAST { + imports: ImportInfo[] + exports: ExportInfo[] + functions: FunctionInfo[] + classes: ClassInfo[] + parseError: boolean +} + +interface ImportInfo { + name: string + from: string + line: number + type: "internal" | "external" | "builtin" + isDefault: boolean +} + +interface FunctionInfo { + name: string + lineStart: number + lineEnd: number + params: string[] + isAsync: boolean + isExported: boolean +} + +interface ClassInfo { + name: string + lineStart: number + lineEnd: number + methods: MethodInfo[] + extends?: string + isExported: boolean +} + +// src/domain/value-objects/ChatMessage.ts +interface ChatMessage { + role: "user" | "assistant" | "tool" + content: string + timestamp: number + toolCalls?: ToolCall[] + toolResults?: ToolResult[] + stats?: { tokens: number; timeMs: number; toolCalls: number } +} + +// src/domain/value-objects/UndoEntry.ts +interface UndoEntry { + id: string + timestamp: number + filePath: string + previousContent: string[] + newContent: string[] + description: string +} +``` + +### 0.1.3 - Domain Services (Interfaces) + +```typescript +// src/domain/services/IStorage.ts +interface IStorage { + getFile(path: string): Promise + setFile(path: string, data: FileData): Promise + deleteFile(path: string): Promise + getAllFiles(): Promise> + getAST(path: string): Promise + setAST(path: string, ast: FileAST): Promise + getSymbolIndex(): Promise + setSymbolIndex(index: SymbolIndex): Promise +} + +// src/domain/services/ILLMClient.ts +interface ILLMClient { + chat(messages: ChatMessage[], tools?: ToolDef[]): Promise + countTokens(text: string): Promise + isAvailable(): Promise +} + +// src/domain/services/ITool.ts +interface ITool { + name: string + description: string + parameters: ToolParameter[] + requiresConfirmation: boolean + execute(params: Record, ctx: ToolContext): Promise +} +``` + +### 0.1.4 - Shared Config + +```typescript +// src/shared/constants/config.ts +interface Config { + redis: { host: string; port: number; db: number } + llm: { model: string; contextWindow: number; temperature: number } + project: { ignorePatterns: string[]; binaryExtensions: string[] } + watchdog: { debounceMs: number } + undo: { stackSize: number } + edit: { autoApply: boolean } +} + +// loadConfig(): reads config/default.json + .ipuaro.json +// validates with zod schema +``` + +**Tests:** +- [ ] Unit tests for value objects +- [ ] Unit tests for config loader + +--- + +## Version 0.2.0 - Redis Storage 🗄️ + +**Priority:** CRITICAL + +### 0.2.1 - Redis Client + +```typescript +// src/infrastructure/storage/RedisClient.ts +class RedisClient { + connect(): Promise // AOF config on connect + disconnect(): Promise + isConnected(): boolean + getClient(): Redis +} + +// Redis config for AOF persistence +// appendonly yes +// appendfsync everysec +``` + +### 0.2.2 - Redis Schema + +``` +project:{name}:files # Hash +project:{name}:ast # Hash +project:{name}:meta # Hash +project:{name}:indexes # Hash +project:{name}:config # Hash + +session:{id}:data # Hash +session:{id}:undo # List (max 10) +sessions:list # List +``` + +**Project name format:** `{parent-folder}-{project-folder}` + +### 0.2.3 - Redis Storage Implementation + +```typescript +// src/infrastructure/storage/RedisStorage.ts +class RedisStorage implements IStorage { + constructor(private client: RedisClient, private projectName: string) + + async getFile(path: string): Promise + async setFile(path: string, data: FileData): Promise + async deleteFile(path: string): Promise + async getAllFiles(): Promise> + // ... all IStorage methods +} +``` + +**Tests:** +- [ ] Unit tests for RedisStorage (mock Redis) +- [ ] Integration tests with real Redis + +--- + +## Version 0.3.0 - Indexer 📂 + +**Priority:** CRITICAL + +### 0.3.1 - File Scanner + +```typescript +// src/infrastructure/indexer/FileScanner.ts +class FileScanner { + scan(root: string): AsyncGenerator +} + +interface ScanResult { + path: string + type: "file" | "dir" | "symlink" + stats: Stats +} + +// Filters: .gitignore (via ignore lib), node_modules, dist +// Supports: .ts, .tsx, .js, .jsx, .json, .yaml +// Only UTF-8 files (skip binary) +// Progress callback: onProgress(current, total, file) +``` + +### 0.3.2 - AST Parser + +```typescript +// src/infrastructure/indexer/ASTParser.ts +class ASTParser { + parse(content: string, lang: "ts" | "tsx" | "js" | "jsx"): FileAST +} + +// Uses tree-sitter +// Extracts: imports, exports, functions, classes +// On error: parseError: true, continue with partial data +``` + +### 0.3.3 - Meta Analyzer + +```typescript +// src/infrastructure/indexer/MetaAnalyzer.ts +class MetaAnalyzer { + analyze(path: string, ast: FileAST, allASTs: Map): FileMeta +} + +interface FileMeta { + complexity: { loc: number; nesting: number; score: number } + dependencies: string[] // files this imports + dependents: string[] // files importing this + isHub: boolean // >5 dependents + isEntryPoint: boolean // index.ts or 0 dependents +} +``` + +### 0.3.4 - Index Builder + +```typescript +// src/infrastructure/indexer/IndexBuilder.ts +class IndexBuilder { + buildSymbolIndex(asts: Map): SymbolIndex + buildDepsGraph(asts: Map): DepsGraph +} + +// SymbolIndex: { [name]: { path, line, type } } +// DepsGraph: { [path]: { imports: [], importedBy: [] } } +``` + +### 0.3.5 - Watchdog + +```typescript +// src/infrastructure/indexer/Watchdog.ts +class Watchdog { + start(root: string, storage: IStorage): void + stop(): void + onFileChange(callback: (path: string) => void): void +} + +// chokidar with 500ms debounce +// On change: recalc hash → update lines/AST/meta if changed +// Silent updates (no UI notification) +``` + +**Tests:** +- [ ] Unit tests for ASTParser (fixtures) +- [ ] Unit tests for MetaAnalyzer +- [ ] Integration tests for full indexing + +--- + +## Version 0.4.0 - LLM Integration 🤖 + +**Priority:** CRITICAL + +### 0.4.1 - Ollama Client + +```typescript +// src/infrastructure/llm/OllamaClient.ts +class OllamaClient implements ILLMClient { + constructor(config: { model: string; contextWindow: number; temperature: number }) + + async chat(messages: ChatMessage[], tools?: ToolDef[]): Promise + async countTokens(text: string): Promise + async isAvailable(): Promise + async pullModel(model: string): Promise +} + +interface LLMResponse { + content: string + toolCalls?: ToolCall[] + tokens: number + timeMs: number +} +``` + +### 0.4.2 - System Prompt + +```typescript +// src/infrastructure/llm/prompts.ts +const SYSTEM_PROMPT: string // EN, role + rules + tools + +function buildInitialContext( + structure: ProjectStructure, + asts: Map +): string + +// Returns: project structure + AST metadata (NO code) +// Code loaded lazily via tools +``` + +### 0.4.3 - Tool Definitions + +```typescript +// src/infrastructure/llm/toolDefs.ts +const TOOL_DEFINITIONS: ToolDef[] // 18 tools + +interface ToolDef { + name: string + description: string + parameters: { + type: "object" + properties: Record + required: string[] + } +} + +// XML format in prompt: ... +``` + +### 0.4.4 - Response Parser + +```typescript +// src/infrastructure/llm/ResponseParser.ts +function parseToolCalls(response: string): ToolCall[] + +// Parses XML tool calls from model response +// Handles multiple tool calls in one response +``` + +**Tests:** +- [ ] Unit tests for ResponseParser +- [ ] Mock tests for OllamaClient + +--- + +## Version 0.5.0 - Read Tools 📖 + +**Priority:** HIGH + +4 tools for reading code without modification. + +### 0.5.1 - Tool Registry + +```typescript +// src/infrastructure/tools/registry.ts +class ToolRegistry implements IToolRegistry { + register(tool: ITool): void + get(name: string): ITool | undefined + getAll(): ITool[] + execute(name: string, params: Record, ctx: ToolContext): Promise +} +``` + +### 0.5.2 - get_lines + +```typescript +// src/infrastructure/tools/read/GetLinesTool.ts +class GetLinesTool implements ITool { + name = "get_lines" + requiresConfirmation = false + + // get_lines(path, start?, end?) + // Returns lines from Redis (or file if not indexed) + // Default: entire file +} +``` + +### 0.5.3 - get_function + +```typescript +// src/infrastructure/tools/read/GetFunctionTool.ts +class GetFunctionTool implements ITool { + name = "get_function" + requiresConfirmation = false + + // get_function(path, name) + // Uses AST lineStart/lineEnd to return function code +} +``` + +### 0.5.4 - get_class + +```typescript +// src/infrastructure/tools/read/GetClassTool.ts +class GetClassTool implements ITool { + name = "get_class" + requiresConfirmation = false + + // get_class(path, name) + // Uses AST lineStart/lineEnd to return class code +} +``` + +### 0.5.5 - get_structure + +```typescript +// src/infrastructure/tools/read/GetStructureTool.ts +class GetStructureTool implements ITool { + name = "get_structure" + requiresConfirmation = false + + // get_structure(path?) + // Returns folder/file tree + // Default: entire project +} +``` + +**Tests:** +- [ ] Unit tests for each read tool +- [ ] Integration tests with real storage + +--- + +## Version 0.6.0 - Edit Tools ✏️ + +**Priority:** HIGH + +3 tools for file modifications. All require confirmation (unless autoApply). + +### 0.6.1 - edit_lines + +```typescript +// src/infrastructure/tools/edit/EditLinesTool.ts +class EditLinesTool implements ITool { + name = "edit_lines" + requiresConfirmation = true + + // edit_lines(path, start, end, content) + // Replaces lines start-end with content + // Checks hash conflict before apply +} +``` + +### 0.6.2 - create_file + +```typescript +// src/infrastructure/tools/edit/CreateFileTool.ts +class CreateFileTool implements ITool { + name = "create_file" + requiresConfirmation = true + + // create_file(path, content) + // Creates new file + // Validates path inside project +} +``` + +### 0.6.3 - delete_file + +```typescript +// src/infrastructure/tools/edit/DeleteFileTool.ts +class DeleteFileTool implements ITool { + name = "delete_file" + requiresConfirmation = true + + // delete_file(path) + // Deletes file from filesystem and Redis +} +``` + +**Tests:** +- [ ] Unit tests for each edit tool +- [ ] Integration tests with filesystem + +--- + +## Version 0.7.0 - Search Tools 🔍 + +**Priority:** HIGH + +### 0.7.1 - find_references + +```typescript +// src/infrastructure/tools/search/FindReferencesTool.ts +class FindReferencesTool implements ITool { + name = "find_references" + requiresConfirmation = false + + // find_references(symbol) + // Searches SymbolIndex for all usages + // Returns: [{ path, line, context }] +} +``` + +### 0.7.2 - find_definition + +```typescript +// src/infrastructure/tools/search/FindDefinitionTool.ts +class FindDefinitionTool implements ITool { + name = "find_definition" + requiresConfirmation = false + + // find_definition(symbol) + // Finds where symbol is defined + // Returns: { path, line, type } +} +``` + +**Tests:** +- [ ] Unit tests for search tools + +--- + +## Version 0.8.0 - Analysis Tools 📊 + +**Priority:** MEDIUM + +### 0.8.1 - get_dependencies + +```typescript +// src/infrastructure/tools/analysis/GetDependenciesTool.ts +// get_dependencies(path) +// Returns files this file imports (from FileMeta) +``` + +### 0.8.2 - get_dependents + +```typescript +// src/infrastructure/tools/analysis/GetDependentsTool.ts +// get_dependents(path) +// Returns files that import this file +``` + +### 0.8.3 - get_complexity + +```typescript +// src/infrastructure/tools/analysis/GetComplexityTool.ts +// get_complexity(path?) +// Returns complexity metrics +// Default: all files sorted by score +``` + +### 0.8.4 - get_todos + +```typescript +// src/infrastructure/tools/analysis/GetTodosTool.ts +// get_todos(path?) +// Finds TODO/FIXME comments in code +// Returns: [{ path, line, text }] +``` + +**Tests:** +- [ ] Unit tests for analysis tools + +--- + +## Version 0.9.0 - Git & Run Tools 🚀 + +**Priority:** MEDIUM + +### 0.9.1 - git_status + +```typescript +// src/infrastructure/tools/git/GitStatusTool.ts +// git_status() +// Returns: { branch, staged, modified, untracked } +``` + +### 0.9.2 - git_diff + +```typescript +// src/infrastructure/tools/git/GitDiffTool.ts +// git_diff(path?) +// Returns uncommitted changes +// Default: all changes +``` + +### 0.9.3 - git_commit + +```typescript +// src/infrastructure/tools/git/GitCommitTool.ts +// git_commit(message, files?) +// Creates commit +// requiresConfirmation: true +``` + +### 0.9.4 - run_command + +```typescript +// src/infrastructure/tools/run/RunCommandTool.ts +// run_command(command) +// Executes shell command with security checks: +// 1. Check blacklist → reject +// 2. Check whitelist → allow +// 3. Unknown → ask user confirmation +``` + +### 0.9.5 - run_tests + +```typescript +// src/infrastructure/tools/run/RunTestsTool.ts +// run_tests(path?, filter?) +// Detects test runner (vitest/jest/npm test) +// Runs tests and returns results +``` + +**Tests:** +- [ ] Unit tests for git tools +- [ ] Unit tests for run tools + +--- + +## Version 0.10.0 - Session Management 💾 + +**Priority:** HIGH + +### 0.10.1 - Session Entity + +```typescript +// src/domain/entities/Session.ts +class Session { + id: string + projectName: string + createdAt: number + lastActivityAt: number + history: ChatMessage[] + context: ContextState + undoStack: UndoEntry[] + stats: SessionStats + inputHistory: string[] +} + +interface SessionStats { + totalTokens: number + totalTime: number + toolCalls: number + editsApplied: number + editsRejected: number +} +``` + +### 0.10.2 - Session Use Cases + +```typescript +// src/application/use-cases/StartSession.ts +class StartSession { + execute(projectName: string): Promise + // Creates new or loads latest session +} + +// src/application/use-cases/HandleMessage.ts +class HandleMessage { + execute(session: Session, message: string): Promise + // Main message flow +} +``` + +### 0.10.3 - Undo Use Case + +```typescript +// src/application/use-cases/UndoChange.ts +class UndoChange { + execute(session: Session): Promise + // Reverts last file change from undo stack +} +``` + +### 0.10.4 - Context Manager + +```typescript +// src/application/use-cases/pipeline/ContextManager.ts +class ContextManager { + addToContext(file: string, tokens: number): void + getUsage(): number // 0-1 + needsCompression(): boolean // >80% + compress(llm: ILLMClient): Promise +} + +// Compression: LLM summarizes old messages + removes tool results +``` + +**Tests:** +- [ ] Unit tests for session use cases +- [ ] Integration tests for full session flow + +--- + +## Version 0.11.0 - TUI Basic 🖥️ + +**Priority:** CRITICAL + +### 0.11.1 - App Shell + +```typescript +// src/tui/App.tsx +function App({ projectPath }: { projectPath: string }) { + const [session, setSession] = useState(null) + const [status, setStatus] = useState<"ready" | "thinking" | "error">("ready") + const [messages, setMessages] = useState([]) + + return ( + + + + + + ) +} +``` + +### 0.11.2 - StatusBar + +```typescript +// src/tui/components/StatusBar.tsx +// [ipuaro] [ctx: 12%] [project: myapp] [main] [47m] ✓ + +interface Props { + contextUsage: number + projectName: string + branch: string + sessionTime: number + status: "ready" | "thinking" | "error" +} +``` + +### 0.11.3 - Chat + +```typescript +// src/tui/components/Chat.tsx +// Displays message history +// Tool calls as: [tool_name params...] +// Stats after response: ⏱ 3.2s │ 1,247 tokens │ 1 tool call + +interface Props { + messages: ChatMessage[] + isThinking: boolean +} +``` + +### 0.11.4 - Input + +```typescript +// src/tui/components/Input.tsx +// Prompt: > _ +// ↑/↓ for history +// Tab for path autocomplete + +interface Props { + onSubmit: (text: string) => void + history: string[] + disabled: boolean +} +``` + +**Tests:** +- [ ] Component tests for TUI + +--- + +## Version 0.12.0 - TUI Advanced 🎨 + +**Priority:** HIGH + +### 0.12.1 - DiffView + +```typescript +// src/tui/components/DiffView.tsx +// Inline highlights: green added, red removed +// Header: ┌─── path (lines X-Y) ───┐ + +interface Props { + filePath: string + oldLines: string[] + newLines: string[] + startLine: number +} +``` + +### 0.12.2 - ConfirmDialog + +```typescript +// src/tui/components/ConfirmDialog.tsx +// [Y] Apply [N] Cancel [E] Edit + +interface Props { + message: string + diff?: DiffProps + onSelect: (choice: "apply" | "cancel" | "edit") => void +} +``` + +### 0.12.3 - ErrorDialog + +```typescript +// src/tui/components/ErrorDialog.tsx +// ❌ type: message +// [R] Retry [S] Skip [A] Abort + +interface Props { + error: { type: string; message: string; recoverable: boolean } + onChoice: (choice: "retry" | "skip" | "abort") => void +} +``` + +### 0.12.4 - Progress + +```typescript +// src/tui/components/Progress.tsx +// [=====> ] 45% (120/267 files) +// Used during indexing + +interface Props { + current: number + total: number + label: string +} +``` + +**Tests:** +- [ ] Component tests for dialogs + +--- + +## Version 0.13.0 - Security 🔒 + +**Priority:** HIGH + +### 0.13.1 - Blacklist + +```typescript +// src/infrastructure/security/Blacklist.ts +const BLACKLIST = [ + "rm -rf", "rm -r", + "git push --force", "git reset --hard", "git clean -fd", + "npm publish", "sudo", "chmod", "chown" +] + +function isBlacklisted(command: string): boolean +// Substring match - always reject +``` + +### 0.13.2 - Whitelist + +```typescript +// src/infrastructure/security/Whitelist.ts +const DEFAULT_WHITELIST = [ + "npm", "pnpm", "yarn", "git", + "node", "npx", "tsx", + "vitest", "jest", "tsc", "eslint", "prettier" +] + +function isWhitelisted(command: string): boolean +// First word match +// User can extend via config.whitelist.user +``` + +### 0.13.3 - Path Validator + +```typescript +// src/infrastructure/security/PathValidator.ts +function validatePath(path: string, projectRoot: string): boolean +// Rejects: .., absolute paths outside project +``` + +**Tests:** +- [ ] Unit tests for security validators + +--- + +## Version 0.14.0 - Orchestrator 🎭 + +**Priority:** CRITICAL + +### 0.14.1 - HandleMessage Use Case + +```typescript +// src/application/use-cases/HandleMessage.ts +class HandleMessage { + constructor( + private storage: IStorage, + private llm: ILLMClient, + private tools: IToolRegistry + ) + + async execute(session: Session, message: string): Promise { + // 1. Add user message to history + // 2. Build context: system prompt + structure + AST + history + // 3. Send to LLM + // 4. Parse tool calls + // 5. For each tool: + // - if requiresConfirmation → emit onEdit + // - else → execute + // 6. If tool results → repeat from step 3 + // 7. Show final response with stats + } + + // Events + onMessage: (msg: ChatMessage) => void + onToolCall: (call: ToolCall) => void + onEdit: (edit: EditRequest) => Promise + onError: (error: IpuaroError) => Promise + onStatusChange: (status: Status) => void +} +``` + +### 0.14.2 - Edit Flow + +```typescript +// Edit handling inside HandleMessage: +// 1. Check hash conflict (file changed during generation?) +// 2. If conflict → onEdit with choices: apply/skip/regenerate +// 3. If not autoApply → onEdit with diff +// 4. On "apply": +// - Save to undo stack +// - Apply changes to file +// - Update storage (lines, AST, meta) +``` + +**Tests:** +- [ ] Unit tests for HandleMessage +- [ ] E2E tests for full message flow + +--- + +## Version 0.15.0 - Commands 📝 + +**Priority:** MEDIUM + +7 slash commands for TUI. + +```typescript +// src/tui/hooks/useCommands.ts + +/help // Shows all commands and hotkeys +/clear // Clears chat history (keeps session) +/undo // Reverts last file change from undo stack +/sessions // list | load | delete +/status // Shows: Redis, context, model, session stats +/reindex // Forces full project reindexation +/eval // LLM self-check for hallucinations +/auto-apply // on | off - toggle auto-apply mode +``` + +**Tests:** +- [ ] Unit tests for command handlers + +--- + +## Version 0.16.0 - Hotkeys & Polish ⌨️ + +**Priority:** MEDIUM + +### 0.16.1 - Hotkeys + +```typescript +// src/tui/hooks/useHotkeys.ts + +Ctrl+C // Interrupt generation (1st), exit (2nd) +Ctrl+D // Exit with session save +Ctrl+Z // Undo (= /undo) +↑/↓ // Input history +Tab // Path autocomplete +``` + +### 0.16.2 - Auto-compression + +```typescript +// Triggered at >80% context: +// 1. LLM summarizes old messages +// 2. Remove tool results older than 5 messages +// 3. Update status bar (ctx% changes) +// No modal notification - silent +``` + +**Tests:** +- [ ] Integration tests for hotkeys +- [ ] Unit tests for compression + +--- + +## Version 0.17.0 - CLI Entry Point 🚪 + +**Priority:** HIGH + +### 0.17.1 - CLI Commands + +```typescript +// src/cli/index.ts + +ipuaro [path] // Start TUI in directory (default: cwd) +ipuaro init // Create .ipuaro.json config +ipuaro index // Index only (no TUI) +``` + +### 0.17.2 - CLI Options + +```bash +--auto-apply # Enable auto-apply mode +--model # Override model (default: qwen2.5-coder:7b-instruct) +--help # Show help +--version # Show version +``` + +### 0.17.3 - Onboarding + +```typescript +// src/cli/commands/start.ts + +// On first run: +// 1. Check Redis → error with install instructions if missing +// 2. Check Ollama → error if unavailable +// 3. Check model → offer to pull if missing +// 4. Check project size → warn if >10K files, offer subdirectory +``` + +**Tests:** +- [ ] E2E tests for CLI + +--- + +## Version 0.18.0 - Error Handling ⚠️ + +**Priority:** HIGH + +### 0.18.1 - Error Types + +```typescript +// src/shared/errors/IpuaroError.ts +type ErrorType = "redis" | "parse" | "llm" | "file" | "command" | "conflict" + +class IpuaroError extends Error { + type: ErrorType + recoverable: boolean + suggestion?: string +} +``` + +### 0.18.2 - Error Handling Matrix + +| Error | Recoverable | Options | +|-------|-------------|---------| +| Redis unavailable | No | Retry / Abort | +| AST parse failed | Yes | Skip file / Abort | +| LLM timeout | Yes | Retry / Skip / Abort | +| File not found | Yes | Skip / Abort | +| Command not in whitelist | Yes | Confirm / Skip / Abort | +| Edit conflict | Yes | Apply / Skip / Regenerate | + +**Tests:** +- [ ] Unit tests for error handling + +--- + +## Version 1.0.0 - Production Ready 🚀 + +**Target:** Stable release + +**Checklist:** +- [ ] All 18 tools implemented and tested +- [ ] TUI fully functional +- [ ] Session persistence working +- [ ] Error handling complete +- [ ] Performance optimized +- [ ] Documentation complete +- [ ] 80%+ test coverage +- [ ] 0 ESLint errors +- [ ] Examples working +- [ ] CHANGELOG.md up to date + +--- + +## Post 1.0 - Future 💡 + +### 1.1.0 - Performance +- Parallel AST parsing +- Incremental indexing +- Response caching + +### 1.2.0 - Features +- Multiple file edits in one operation +- Batch operations +- Custom prompt templates + +### 1.3.0 - Extensibility +- Plugin system for tools +- Custom LLM providers (OpenAI, Anthropic) +- IDE integration (LSP?) + +--- + +## Summary Tables + +### Tool Summary (18 total) + +| Category | Tool | Confirm | Description | +|----------|------|---------|-------------| +| **Read** | get_lines | No | Get file lines | +| | get_function | No | Get function by name | +| | get_class | No | Get class by name | +| | get_structure | No | Get project tree | +| **Edit** | edit_lines | Yes | Replace lines | +| | create_file | Yes | Create new file | +| | delete_file | Yes | Delete file | +| **Search** | find_references | No | Find symbol usages | +| | find_definition | No | Find symbol definition | +| **Analysis** | get_dependencies | No | File imports | +| | get_dependents | No | Files importing this | +| | get_complexity | No | Complexity metrics | +| | get_todos | No | Find TODO/FIXME | +| **Git** | git_status | No | Repository status | +| | git_diff | No | Uncommitted changes | +| | git_commit | Yes | Create commit | +| **Run** | run_command | Conditional | Execute shell command | +| | run_tests | No | Run test suite | + +### Redis Schema + +``` +# Project (5 keys per project) +project:{name}:files # Hash +project:{name}:ast # Hash +project:{name}:meta # Hash +project:{name}:indexes # Hash +project:{name}:config # Hash + +# Sessions (3 keys per session) +session:{id}:data # Hash +session:{id}:undo # List max 10 +sessions:list # List +``` + +### Context Budget (128K window) + +| Component | Tokens | % | +|-----------|--------|---| +| System prompt | ~2,000 | 1.5% | +| Structure + AST | ~10,000 | 8% | +| **Available** | ~116,000 | 90% | + +--- + +**Last Updated:** 2025-11-29 +**Target Version:** 1.0.0 \ No newline at end of file