AI и автоматизация

Система памяти для Gemini CLI с использованием Git hooks и локальной базы данных

Система памяти для Gemini CLI с использованием Git hooks и локальной базы данных

Предлагаю создать систему памяти для Gemini CLI с использованием Git hooks и локальной базы данных. Вот полное решение:

Структура системы памяти

1. Основной скрипт установки

#!/bin/bash
# install_memory_system.sh

# Создание директории для системы памяти
mkdir -p ~/.gemini-memory
mkdir -p ~/.gemini-memory/projects
mkdir -p ~/.gemini-memory/templates

# Установка зависимостей
echo "Установка зависимостей..."
sudo apt-get update
sudo apt-get install -y python3-pip sqlite3 jq
pip3 install --user gitpython watchdog pyyaml

# Копирование скриптов
cp gemini_memory.py ~/.gemini-memory/
cp memory_hook.sh ~/.gemini-memory/
chmod +x ~/.gemini-memory/*.sh
chmod +x ~/.gemini-memory/*.py

echo "Система памяти установлена!"

2. Основной Python модуль системы памяти

#!/usr/bin/env python3
# ~/.gemini-memory/gemini_memory.py

import os
import json
import sqlite3
import hashlib
import datetime
from pathlib import Path
import git
import yaml
from typing import Dict, List, Optional

class GeminiMemory:
    def __init__(self, project_path: str = None):
        self.base_dir = Path.home() / '.gemini-memory'
        self.base_dir.mkdir(exist_ok=True)

        if project_path:
            self.project_path = Path(project_path).resolve()
            self.project_id = self._get_project_id()
            self.db_path = self.base_dir / 'projects' / f'{self.project_id}.db'
            self._init_database()

    def _get_project_id(self) -> str:
        """Генерирует уникальный ID для проекта"""
        return hashlib.md5(str(self.project_path).encode()).hexdigest()[:12]

    def _init_database(self):
        """Инициализирует базу данных для проекта"""
        self.db_path.parent.mkdir(exist_ok=True)
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.cursor()

        # Таблица изменений кода
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS code_changes (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                file_path TEXT,
                change_type TEXT,
                diff TEXT,
                commit_hash TEXT,
                branch TEXT,
                summary TEXT
            )
        ''')

        # Таблица взаимодействий с AI
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS ai_interactions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                prompt TEXT,
                response TEXT,
                context TEXT,
                tags TEXT,
                token_count INTEGER
            )
        ''')

        # Таблица задач и решений
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS tasks (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                task_description TEXT,
                status TEXT,
                solution TEXT,
                files_affected TEXT,
                complexity INTEGER
            )
        ''')

        # Таблица архитектурных решений
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS architecture_decisions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                decision TEXT,
                reasoning TEXT,
                alternatives TEXT,
                impact TEXT
            )
        ''')

        conn.commit()
        conn.close()

    def record_change(self, file_path: str, change_type: str, diff: str, summary: str = None):
        """Записывает изменение в коде"""
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.cursor()

        try:
            repo = git.Repo(self.project_path)
            commit_hash = repo.head.commit.hexsha if repo.head.is_valid() else None
            branch = repo.active_branch.name if not repo.head.is_detached else 'detached'
        except:
            commit_hash = None
            branch = 'unknown'

        cursor.execute('''
            INSERT INTO code_changes (file_path, change_type, diff, commit_hash, branch, summary)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (file_path, change_type, diff, commit_hash, branch, summary))

        conn.commit()
        conn.close()

    def record_ai_interaction(self, prompt: str, response: str, context: str = None, tags: List[str] = None):
        """Записывает взаимодействие с AI"""
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.cursor()

        tags_str = json.dumps(tags) if tags else '[]'
        token_count = len(prompt.split()) + len(response.split())

        cursor.execute('''
            INSERT INTO ai_interactions (prompt, response, context, tags, token_count)
            VALUES (?, ?, ?, ?, ?)
        ''', (prompt, response, context, tags_str, token_count))

        conn.commit()
        conn.close()

    def get_project_context(self, limit: int = 10) -> Dict:
        """Получает контекст проекта для AI"""
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.cursor()

        # Последние изменения
        cursor.execute('''
            SELECT * FROM code_changes
            ORDER BY timestamp DESC
            LIMIT ?
        ''', (limit,))
        recent_changes = cursor.fetchall()

        # Последние взаимодействия
        cursor.execute('''
            SELECT * FROM ai_interactions
            ORDER BY timestamp DESC
            LIMIT ?
        ''', (limit,))
        recent_interactions = cursor.fetchall()

        # Активные задачи
        cursor.execute('''
            SELECT * FROM tasks
            WHERE status != 'completed'
            ORDER BY timestamp DESC
        ''')
        active_tasks = cursor.fetchall()

        # Архитектурные решения
        cursor.execute('''
            SELECT * FROM architecture_decisions
            ORDER BY timestamp DESC
            LIMIT 5
        ''')
        architecture = cursor.fetchall()

        conn.close()

        return {
            'project_path': str(self.project_path),
            'recent_changes': self._format_records(recent_changes, 'code_changes'),
            'recent_interactions': self._format_records(recent_interactions, 'ai_interactions'),
            'active_tasks': self._format_records(active_tasks, 'tasks'),
            'architecture': self._format_records(architecture, 'architecture_decisions')
        }

    def _format_records(self, records: List, table_name: str) -> List[Dict]:
        """Форматирует записи из БД"""
        formatted = []
        for record in records:
            if table_name == 'code_changes':
                formatted.append({
                    'timestamp': record[1],
                    'file': record[2],
                    'type': record[3],
                    'diff': record[4][:200] + '...' if len(record[4]) > 200 else record[4],
                    'commit': record[5],
                    'branch': record[6],
                    'summary': record[7]
                })
            elif table_name == 'ai_interactions':
                formatted.append({
                    'timestamp': record[1],
                    'prompt': record[2][:100] + '...' if len(record[2]) > 100 else record[2],
                    'response': record[3][:200] + '...' if len(record[3]) > 200 else record[3],
                    'context': record[4],
                    'tags': json.loads(record[5]) if record[5] else []
                })
            elif table_name == 'tasks':
                formatted.append({
                    'timestamp': record[1],
                    'description': record[2],
                    'status': record[3],
                    'solution': record[4],
                    'files': record[5]
                })
            elif table_name == 'architecture_decisions':
                formatted.append({
                    'timestamp': record[1],
                    'decision': record[2],
                    'reasoning': record[3]
                })
        return formatted

    def generate_summary(self) -> str:
        """Генерирует саммари проекта для AI"""
        context = self.get_project_context()

        summary = f"""
# Контекст проекта: {context['project_path']}

## Последние изменения в коде:
"""
        for change in context['recent_changes'][:5]:
            summary += f"- [{change['timestamp']}] {change['file']}: {change['type']}\n"
            if change['summary']:
                summary += f"  Описание: {change['summary']}\n"

        summary += "\n## Активные задачи:\n"
        for task in context['active_tasks'][:3]:
            summary += f"- [{task['status']}] {task['description']}\n"

        summary += "\n## Архитектурные решения:\n"
        for decision in context['architecture'][:3]:
            summary += f"- {decision['decision']}\n"
            summary += f"  Обоснование: {decision['reasoning']}\n"

        return summary

3. Git Hook для отслеживания изменений

#!/bin/bash
# ~/.gemini-memory/git_hook.sh

PROJECT_PATH=$(git rev-parse --show-toplevel)
MEMORY_SCRIPT=~/.gemini-memory/gemini_memory.py

# Получаем информацию о коммите
COMMIT_MSG=$(git log -1 --pretty=%B)
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD)

# Записываем изменения в память
python3 -c "
import sys
sys.path.insert(0, '$HOME/.gemini-memory')
from gemini_memory import GeminiMemory

memory = GeminiMemory('$PROJECT_PATH')
commit_msg = '''$COMMIT_MSG'''
files = '''$CHANGED_FILES'''.split('\n')

for file in files:
    if file:
        diff = '$(git diff HEAD~1 HEAD -- {} 2>/dev/null | head -100)'
        memory.record_change(file, 'commit', diff, commit_msg)
"

4. CLI интерфейс для Gemini

#!/usr/bin/env python3
# ~/.gemini-memory/gemini_cli_wrapper.py

import os
import sys
import json
import subprocess
from pathlib import Path
from gemini_memory import GeminiMemory
import argparse

class GeminiCLIWrapper:
    def __init__(self):
        self.current_project = self._detect_project()
        if self.current_project:
            self.memory = GeminiMemory(self.current_project)

    def _detect_project(self) -> Optional[str]:
        """Определяет текущий проект по git репозиторию"""
        try:
            result = subprocess.run(
                ['git', 'rev-parse', '--show-toplevel'],
                capture_output=True, text=True
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except:
            pass
        return None

    def enhance_prompt(self, original_prompt: str) -> str:
        """Добавляет контекст к промпту"""
        if not self.memory:
            return original_prompt

        context = self.memory.generate_summary()
        enhanced = f"""
{context}

---
Текущий запрос: {original_prompt}

Учитывай контекст проекта и предыдущие решения при ответе.
"""
        return enhanced

    def run_gemini(self, prompt: str, save_interaction: bool = True):
        """Запускает Gemini с улучшенным промптом"""
        enhanced_prompt = self.enhance_prompt(prompt)

        # Здесь вызов Gemini CLI (адаптируйте под вашу версию)
        result = subprocess.run(
            ['gemini', 'ask', enhanced_prompt],
            capture_output=True, text=True
        )

        response = result.stdout

        if save_interaction and self.memory:
            self.memory.record_ai_interaction(
                prompt=prompt,
                response=response,
                context=self.current_project
            )

        return response

    def init_project(self):
        """Инициализирует память для проекта"""
        if not self.current_project:
            print("Не найден git репозиторий")
            return

        # Установка git hooks
        hooks_dir = Path(self.current_project) / '.git' / 'hooks'
        post_commit = hooks_dir / 'post-commit'

        with open(post_commit, 'w') as f:
            f.write('#!/bin/bash\n')
            f.write('~/.gemini-memory/git_hook.sh\n')

        os.chmod(post_commit, 0o755)
        print(f"Память инициализирована для проекта: {self.current_project}")

def main():
    parser = argparse.ArgumentParser(description='Gemini CLI с системой памяти')
    parser.add_argument('command', choices=['init', 'ask', 'context', 'summary'])
    parser.add_argument('prompt', nargs='?', help='Промпт для Gemini')
    parser.add_argument('--no-save', action='store_true', help='Не сохранять взаимодействие')

    args = parser.parse_args()
    wrapper = GeminiCLIWrapper()

    if args.command == 'init':
        wrapper.init_project()
    elif args.command == 'ask':
        if not args.prompt:
            print("Необходим промпт для команды ask")
            sys.exit(1)
        response = wrapper.run_gemini(args.prompt, not args.no_save)
        print(response)
    elif args.command == 'context':
        if wrapper.memory:
            context = wrapper.memory.get_project_context()
            print(json.dumps(context, indent=2, ensure_ascii=False))
    elif args.command == 'summary':
        if wrapper.memory:
            print(wrapper.memory.generate_summary())

if __name__ == '__main__':
    main()

5. Bash алиас для удобства

# Добавить в ~/.bashrc или ~/.zshrc

# Алиас для Gemini с памятью
alias gmem='python3 ~/.gemini-memory/gemini_cli_wrapper.py'

# Функция для быстрого запроса
gask() {
    python3 ~/.gemini-memory/gemini_cli_wrapper.py ask "$*"
}

# Инициализация памяти в текущем проекте
ginit() {
    python3 ~/.gemini-memory/gemini_cli_wrapper.py init
}

# Показать контекст проекта
gcontext() {
    python3 ~/.gemini-memory/gemini_cli_wrapper.py context
}

6. Конфигурационный файл

# ~/.gemini-memory/config.yaml

memory:
  max_context_items: 20
  max_token_count: 4000
  auto_summarize: true

projects:
  default_tags:
    - architecture
    - bugfix
    - feature
    - refactoring

storage:
  cleanup_days: 90
  backup_enabled: true
  backup_path: ~/.gemini-memory/backups

gemini:
  model: gemini-pro
  temperature: 0.7
  max_tokens: 2048

Установка и использование

  • Установка системы:
# Клонируйте репозиторий или создайте файлы
chmod +x install_memory_system.sh
./install_memory_system.sh
  • Инициализация в проекте:
cd /path/to/your/project
ginit
  • Использование:


# Обычный запрос с контекстом

gask "Как оптимизировать функцию обработки данных?"


# Просмотр контекста

gcontext


# Просмотр саммари

gmem summary

Особенности системы:

  • Автоматическое отслеживание: Git hooks автоматически записывают все изменения

  • Изоляция проектов: Каждый проект имеет свою БД

  • Контекстуализация: Каждый запрос дополняется историей проекта

  • Легкость: SQLite не требует сервера БД

  • Расширяемость: Легко добавить новые типы данных

Эта система обеспечит полную память для Gemini CLI при работе с разными проектами.

Инструкция по установке системы памяти для Gemini CLI

Что это даст вам:

  • Gemini будет помнить всю историю работы над каждым проектом

  • Не нужно будет повторять контекст при каждом вопросе

  • Автоматическое отслеживание всех изменений в коде

  • Отдельная память для каждого проекта

Предварительные требования:

✅ У вас установлена Zorin OS (уже есть) ✅ Установлен GitHub (уже есть) ✅ Установлен Gemini CLI (должен быть установлен) ✅ Есть хотя бы один проект с Git репозиторием


📋 Пошаговая установка

Шаг 1: Создание структуры папок

  • Откройте терминал (Ctrl+Alt+T)

  • Создайте основную папку для системы памяти:

mkdir -p ~/.gemini-memory
mkdir -p ~/.gemini-memory/projects
mkdir -p ~/.gemini-memory/templates

Шаг 2: Установка необходимых программ

В терминале выполните по очереди:

sudo apt-get update
sudo apt-get install -y python3-pip sqlite3 jq
pip3 install --user gitpython watchdog pyyaml

Что это установит:

  • python3-pip - менеджер пакетов Python

  • sqlite3 - база данных для хранения памяти

  • jq - инструмент для работы с JSON

  • Библиотеки Python для работы с Git и файлами

Шаг 3: Создание файлов системы

Вам нужно создать 5 файлов. Для каждого:

  • Создайте файл установки install_memory_system.sh
  • Откройте текстовый редактор: `nano ~/.gemini-memory/install_memory_system.sh`
    • Скопируйте код из раздела 1 предыдущего ответа

    • Сохраните: Ctrl+O, Enter, Ctrl+X

  • - **Создайте основной модуль** `gemini_memory.py`
  • Команда: `nano ~/.gemini-memory/gemini_memory.py`
    • Скопируйте код из раздела 2 (Python модуль системы памяти)

    • Сохраните: Ctrl+O, Enter, Ctrl+X

  • - **Создайте Git hook** `git_hook.sh`
  • Команда: `nano ~/.gemini-memory/git_hook.sh`
    • Скопируйте код из раздела 3

    • Сохраните: Ctrl+O, Enter, Ctrl+X

  • - **Создайте CLI обертку** `gemini_cli_wrapper.py`
  • Команда: `nano ~/.gemini-memory/gemini_cli_wrapper.py`
    • Скопируйте код из раздела 4

    • Сохраните: Ctrl+O, Enter, Ctrl+X

  • - **Создайте конфигурацию** `config.yaml`
  • Команда: `nano ~/.gemini-memory/config.yaml`
    • Скопируйте код из раздела 6

    • Сохраните: Ctrl+O, Enter, Ctrl+X

  • Шаг 4: Делаем файлы исполняемыми

    В терминале выполните:

    chmod +x ~/.gemini-memory/*.sh
    chmod +x ~/.gemini-memory/*.py
    

    Шаг 5: Настройка быстрых команд

    • Откройте файл настроек bash:
    nano ~/.bashrc
    
    • В конец файла добавьте текст из раздела 5 (Bash алиасы)

    • Сохраните файл: Ctrl+O, Enter, Ctrl+X

    • Активируйте новые команды:

    source ~/.bashrc
    

    🚀 Начало работы

    Инициализация в проекте

    • Перейдите в папку вашего проекта:
    cd /путь/к/вашему/проекту
    
    • Инициализируйте систему памяти:
    ginit
    

    Вы увидите сообщение: “Память инициализирована для проекта”

    Использование

    Задать вопрос с учетом контекста проекта:

    gask "Ваш вопрос о коде"
    

    Посмотреть, что помнит система о проекте:

    gcontext
    

    Получить краткую сводку по проекту:

    gmem summary
    

    🔧 Проверка установки

    • Проверьте, что все файлы созданы:
    ls -la ~/.gemini-memory/
    

    Должны быть видны все .py и .sh файлы

    • Проверьте, что команды работают:
    gmem --help
    

    Должна появиться справка

    • Тестовый запрос (в папке с проектом):
    gask "тестовый запрос"
    

    ❗ Частые проблемы и решения

    Проблема: Команда gask не найдена

    • Решение: Выполните source ~/.bashrc или откройте новый терминал

    Проблема: Ошибка “No module named ‘git’”

    • Решение: Установите библиотеку: pip3 install --user gitpython

    Проблема: Ошибка при инициализации проекта

    • Решение: Убедитесь, что вы находитесь в папке с Git репозиторием (git status должен работать)

    Проблема: Gemini CLI не отвечает

    • Решение: Проверьте, что Gemini CLI установлен и работает сам по себе

    📝 Важные замечания

    • Для каждого проекта нужно выполнить ginit один раз

    • История сохраняется в папке ~/.gemini-memory/projects/

    • Резервные копии рекомендуется делать периодически:

    cp -r ~/.gemini-memory/projects ~/.gemini-memory/backup_$(date +%Y%m%d)
    
    • Настройки Gemini CLI могут потребовать корректировки в файле gemini_cli_wrapper.py (строка с вызовом gemini)

    🎯 Что дальше?

    После успешной установки:

    • Инициализируйте систему во всех ваших проектах

    • Используйте gask вместо прямого вызова Gemini

    • Система будет автоматически накапливать знания о каждом проекте

    • При возврате к старому проекту, вся история будет доступна

    Если возникнут проблемы, проверьте, что все файлы скопированы правильно и содержат весь код из предыдущего ответа.