Graphify: knowledge graph для кода
Превращаем код, схемы и PDF в запросаемый knowledge graph. Локальный AST-парсинг без векторного хранилища для Claude Code, Cursor и других ИИ-агентов.
Зачем knowledge graph вместо обычного поиска по коду
Ограничения классического grep и LLM-чатов
Grep — это как искать ключи в комнате, где всё время меняется расклад. Классический поиск по подстроке не понимает контекста: он найдёт user_id везде, где это слово встречается, но не скажет, где оно используется как параметр функции, а где — как поле структуры.
bash# Grep находит 47 совпадений, но неясно, какие из них — определения, а какие — вызовы
grep -rn "user_id" .
LLM-чаты хуже: они работают с тем, что им скормили в контекст. Обычно это 8–32 тысячи токенов. Для проекта в 100 тысяч строк кода это примерно 5% всего. ИИ видит отдельные файлы, но не связь между ними. Он не знает, что UserService в auth/ и UserController в api/ используют одну и ту же сущность, потому что не видит этой связи.
С практической точки зрения, это как пытаться собрать пазл, глядя только на отдельные кусочки.
Что такое knowledge graph и как он работает локально
Knowledge graph — это карта связей. Вместо того чтобы хранить код как плоский набор файлов, graph строит узлы (функции, классы, переменные, модули) и ребра (вызовы, наследование, зависимости, импорты).
Локальная реализация работает через AST-парсинг. Для Python это ast из стандартной библиотеки, для JavaScript — @swc/core, для Go — go/ast. Парсер разбирает каждый файл, извлекает сущности и их отношения, сохраняет в SQLite или embedded-БД вроде RocksDB.
python# Пример: извлечение зависимостей из Python-файла
import ast
tree = ast.parse(open("service.py").read())
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
print(f"Импорт из {node.module}: {[n.name for n in node.names]}")
Полученный graph запросается не через векторный поиск, а через Cypher-подобные запросы или просто SQL с JOIN'ами:
sql-- Кто вызывает функцию process_payment?
SELECT caller.name
FROM calls c
JOIN functions caller ON c.caller_id = caller.id
JOIN functions target ON c.target_id = target.id
WHERE target.name = 'process_payment'
Такой подход даёт предсказуемость: запрос всегда возвращает одинаковый результат, не зависящий от того, как модель «почувствует» контекст сегодня.
Связанная информация: Автоматизация и ИИ-агенты на сервере, Настройка CLAUDE.md для Claude Code по наблюдениям Андрея Карпатского.
Как устроен Graphify: AST-парсинг без векторного хранилища
Разбор архитектуры: deterministic подход
Графифай не использует вероятности. Ни в коем случае.
Когда я вижу «AI-анализ кода», мне сразу вспоминается та самая история с векторным хранилищем, которое «умное» перестраивало три раза в неделю и каждый раз выдало другой ответ на один и тот же запрос. Никакой предсказуемости — только «ну вроде работает». Graphify выбрал другой путь: deterministic подход. Каждый запуск даёт один и тот же результат. Это как настраивать сервер через конфиги, а не через пробование разных флагов в надежде, что что-то сработает.
Архитектура построена на трёх столпах:
- AST-парсинг на этапе импорта. Код разбирается на синтаксическое дерево с помощью специализированных парсеров для каждого языка. Никаких эмбеддингов, никаких LLM-средств для понимания структуры. Только чёткие правила.
- Построение графа по фиксированным правилам. Из узлов AST извлекаются сущности (функции, классы, таблицы, секции конфигов) и связи между ними. Правила жёсткие: если функция вызывает другую — связь есть. Если нет — связи нет. Без «может быть» и «скорее всего».
- Локальная индексация без векторного хранилища. Всё хранится в обычной базе, обычных таблицах. Запросы идут напрямую, без промежуточных слоёв абстракции.
Вот как это выглядит на практике. Допустим, у вас есть проект на Python:
python# app/services/user_service.py
def get_user(user_id: int) -> User:
return db.query(User).filter(User.id == user_id).first()
def create_user(name: str, email: str) -> User:
user = User(name=name, email=email)
db.add(user)
db.commit()
return user
Graphify построит граф, где get_user и create_user будут узлами типа function, а связь между ними появится только если одна вызывает другую. В данном случае связь будет с db.query и db.add — это методы объекта db, и они тоже станут узлами.
Конфигурация импорта выглядит предсказуемо:
yaml# graphify.yaml
sources:
- path: ./app
type: python
extensions: [.py]
- path: ./migrations
type: sql
extensions: [.sql]
- path: ./docs
type: pdf
extensions: [.pdf]
graph:
output: ./graph.db
format: sqlite
Каждый source обрабатывается независимо. Если вы добавите новый файл — граф обновится только для него. Остальное останется нетронутым. Это важно: вы не перестраиваете всё с нуля, вы инкрементально обновляете знания.
Поддерживаемые форматы: код, SQL, конфиги, PDF
Graphify не требует, чтобы всё было в одном языке. На практике проекты состоят из разного — и это нормально.
Код. Поддерживаются Python, JavaScript/TypeScript, Go, Rust, Java, C#. Каждый язык имеет свой парсер, но логика извлечения сущностей схожа. Для Python — это функции, классы, декораторы, вызовы. Для TypeScript — интерфейсы, типы, импорты, JSX-компоненты.
Пример для TypeScript:
typescript// src/api/users.ts
import { prisma } from '../db/client';
export interface User {
id: number;
name: string;
email: string;
}
export async function getUsers(): Promise<User[]> {
return prisma.user.findMany();
}
export async function createUser(data: { name: string; email: string }): Promise<User> {
return prisma.user.create({ data });
}
Из этого файла Graphify извлечёт:
- Интерфейс
Userс полямиid,name,email - Функции
getUsersиcreateUserс их сигнатурами - Импорт
prismaи вызовыprisma.user.findMany()иprisma.user.create()
SQL. DDL и DML разбираются отдельно. CREATE TABLE, ALTER TABLE, INSERT, UPDATE, DELETE — всё попадает в граф. Это особенно полезно, когда схема БД разрознена по миграциям.
sql-- migrations/001_create_users.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- migrations/002_add_index.sql
CREATE INDEX idx_users_email ON users(email);
Граф будет содержать таблицу users с колонками, их типами и ограничениями, а также индекс idx_users_email, связанный с таблицей.
Конфиги. YAML, JSON, TOML, INI, HCL (Terraform). Graphify разбирает структуру конфигураций и связывает их с соответствующими сущностями в коде.
yaml# config/database.yaml
database:
host: localhost
port: 5432
name: myapp
pool_size: 10
Этот конфиг становится узлом config в графе, и если ваш Python-код читает database.host, появится связь между конфигом и функцией, которая его использует.
PDF. Да, PDF. Используется OCR-движок Tesseract под капотом для извлечения текста, а затем применяются те же правила разбора. Это позволяет подключать архитектурные схемы, документацию и даже старые RFC, чтобы связать их с кодом.
Все форматы обрабатываются через единый интерфейс. Вы не пишете разные скрипты для разных типов файлов — просто указываете путь и тип источника в конфиге, и Graphify сам определит, какой парсер применить.
Связанное чтение: Автоматизация и ИИ-агенты на сервере, Настройка CLAUDE.md для Claude Code по наблюдениям Андрея Карпатского.
Подключение /graphify в Claude Code, Cursor, Codex и Gemini CLI
Установка и настройка навыка
С практической точки зрения, Graphify работает не как отдельное приложение, а как расширение контекста для LLM. Чтобы ИИ-агент (Claude Code, Cursor или Gemini CLI) мог использовать Graphify, ему нужно дать инструмент — так называемый «навык» (skill).
Если вы используете Claude Code, то оптимальный путь — добавить описание навыка в файл CLAUDE.md. Это обеспечит предсказуемость: агент будет знать, что при анализе сложной архитектуры ему стоит обратиться к графу знаний, не пытаясь угадать структуру проекта через ls -R.
Отредактируйте файл CLAUDE.md в корне проекта, добавив в него следующую секцию:
markdown# Knowledge Graph Tooling
- Tool: `/graphify`
- Purpose: Query the local AST-based knowledge graph of the code.
## Практическое применение: от рефакторинга до аудита безопасности
### Трассировка зависимостей и поиск уязвимостей
Граф знаний раскрывает то, что обычные сканеры пропускают: не просто «библиотека X уязвима», а «вот как X попадает в ваш бинарь, вот где её код реально вызывается».
Возьмём типичный случай. В проекте на Python есть зависимость `requests==2.19.0` — сканер выдаёт красный флажок. Но на деле эта версия используется только в тестовом скрипте `tests/legacy/test_old_api.py`, который никогда не запускается в продакшене. Graphify строит путь: `requirements.txt → requests → test_old_api.py → function old_auth() → call requests.get()` и понимает, что уязвимость не критична.
```python
# Пример: Graphify выявляет, что уязвимая функция вызывается только в тестах
# Анализ пути зависимости:
# main.py → utils/auth.py → requests.get() ← продакшен
# tests/legacy/test_old_api.py → requests.get() ← тесты, не в проде
Для поиска уязвимостей по типу «использование eval() с пользовательским вводом» удобно задать запрос в GraphQL:
graphql{
vulnerabilities(pattern: "eval\\(|exec\\(|subprocess\\.call\\(") {
file
line
function
callers(depth: 3) {
file
function
sourceCode
}
}
}
Команда для локального запуска анализа:
bashgraphify scan --lang python --vuln-patterns "eval,exec,subprocess.call" --output report.json .
Алексей: «Три года назад аудиторы провели pentest нашего сервиса и нашли, что pickle.loads() вызывается на входе из API. Мы бы проверили это за пять минут, если бы тогда был Graphify. Теперь каждый PR проходит через graphify audit --fail-on-critical в CI.»
Интеграция с существующими CI-CD и локальными серверами
Graphify не требует облачных сервисов — парсинг происходит локально, а результаты кладутся в SQLite или JSON. Это значит, что его можно встроить в любой pipeline, даже если ваш CI работает в изолированной сети.
Пример конфигурации для GitHub Actions:
yaml# .github/workflows/security-audit.yml
name: Security Audit
on: [push, pull_request]
jobs:
graphify-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Graphify
run: |
curl -sSL https://graphify.ai/install.sh | sh
graphify version
- name: Build knowledge graph
run: |
graphify build --lang python --output .graphify/graph.db .
- name: Run vulnerability scan
run: |
graphify audit --graph .graphify/graph.db --rules owasp-top-10 --format sarif --output results.sarif
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Для локального сервера на базе Proxmox или домашней ноды с Kubernetes удобно запускать Graphify как sidecar-контейнер или systemd-сервис. Алексей запускает его на ноде Винни-Пух (его первой домашней машине) через systemd:
ini# /etc/systemd/system/graphify.service
[Unit]
Description=Graphify Knowledge Graph Builder
After=network.target
[Service]
Type=simple
User=graphify
WorkingDirectory=/opt/projects
ExecStart=/usr/local/bin/graphify watch --lang python --graph /var/lib/graphify/graph.db --interval 300 .
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Активируем и проверяем статус:
bashsudo systemctl daemon-reload
sudo systemctl enable --now graphify
sudo systemctl status graphify
С практической точки зрения, если вы уже используете CLAUDE.md для Claude Code, Graphify можно подключить как внешнее хранилище знаний. Claude Code будет запрашивать граф при анализе кода, а не полагаться только на встроенный контекст.
Алексей: «Мы пробовали несколько подходов: сначала просто запускали Graphify вручную перед ревью, потом вынесли в CI, а сейчас он работает постоянно на одной из нод кластера. Каждые пять минут граф обновляется, и любой ИИ-агент в локальной сети может к нему подключиться по HTTP API. Это как Ponytail: ленивый подход к AI-агентам — только вместо лени мы получаем предсказуемость.»
Часто задаваемые вопросы
Как Graphify отличается от обычного grep по коду
Grep ищет подстроки, а Graphify понимает структуру. Если вы ищете user_id в grep, получите 47 совпадений без контекста — где это определение, где вызов, где поле структуры. Graphify строит knowledge graph: каждая функция, класс и переменная становятся узлами, а связи между ними — ребрами. Это значит, что вы можете спросить не «где встречается user_id», а «какие функции используют UserService из auth/», и получите точный ответ с контекстом зависимостей.
Нужен ли мне векторный поиск для работы с Graphify
Нет, векторный поиск не используется. Graphify работает исключительно на основе AST-парсинга и построения знаний через графовые структуры. Это особенность локальной реализации: вы извлекаете сущности и их отношения прямо из синтаксического дерева кода, сохраняете в SQLite или RocksDB, а затем запрашиваете через Cypher-подобные запросы. Векторные модели здесь избыточны — вы не ищете близкие по смыслу фрагменты, вы запрашиваете конкретные связи между конкретными сущностями.
Можно ли использовать Graphify с существующими ИИ-агентами
Да, Graphify специально разработан для интеграции с Claude Code, Cursor и другими ИИ-агентами. Поскольку graph хранится в локальной базе данных и доступен через стандартные запросы, ИИ-агент может получать контекст не через загрузку файлов в контекст, а через целенаправленные запросы к графу знаний. Это решает основную проблему LLM-чатов: вместо 5% кода в контексте вы получаете доступ к 100% структуре проекта по запросу.
Что делать, если мой язык программирования не поддерживается
Graphify использует языковые парсеры для извлечения AST, и поддержка зависит от доступности парсера для вашего языка. Для Python используется стандартный ast, для JavaScript — @swc/core, для Go — go/ast. Если ваш язык не поддерживается из коробки, вы можете добавить парсер, реализовав интерфейс извлечения сущностей и связей. Архитектура построена так, что добавление нового языка не требует переработки всей системы.
Как Graphify масштабируется на большие проекты
Графовая база данных масштабируется линейно с ростом кодовой базы, но ключевое преимущество — вы не загружаете весь код в контекст. Для проекта в 100 тысяч строк вы строите graph один раз, а затем запрашиваете только нужные связи. Это как хранить карту дома, а не сам дом целиком — вы всегда знаете, где находится нужная комната, не обходя все остальные.