Контекст должен быть в репозитории, а не в чате

—

от автора

Меня зацепила статья «Жизнь после Claude Code: как я переехал на Codex и сохранил историю проектов».
С решением, которое предлагает автор, я не согласен, т.к. в архиве переписки хранится разговор о проекте, а не итоговые решения.
Я предлагаю другой подход. Мой личный опыт — полгода у меня контекст в репозитории (методика GRACE, доведённая напильником до утилиты doubled-graph).

Вахта сменяется, записи остаются

Вахта сменяется, записи остаются

1. Почему истории чатов мало

Восстановленная история остаётся текстом, который новый агент обязан прочитать и заново интерпретировать. Проверить эту интерпретацию нечем: в переписке нет ни контрактов, ни границ модулей, ни критериев приёмки, которые можно сверить с кодом. Есть только то, что было сказано, — с шумом, повторами, отменёнными решениями и вежливыми «ок».

Также восстановление контекста — это налог с каждой новой сессии. Если знание о проекте живёт в переписке, его придётся собирать заново столько раз, сколько раз сменится инструмент. И каждый раз — заново решать, какое из противоречащих друг другу сообщений было последним.

2. Тест на переносимость контекста

Есть простой эксперимент. Откройте репозиторий в агенте, который проект никогда не видел. Дайте ровно один промпт: «прочитай, что здесь есть, и расскажи, как устроен проект и что в нём сейчас происходит».

3. Что лежит в репозитории

Я работаю по методологии GRACE: её сделал osovv, лицензия MIT.
Её идея о том, что замысел, границы модулей и проверки лежат в репозитории рядом с кодом: пять context-файлов, граф модулей, записи проверок, бандлы изменений.
Я добавил к ней три вещи,  которых в upstream не было: сценарий миграции существующего кода, граф вызовов (через CodeGraphContext) и MCP-обёртку. Всё это вместе — doubled-graph.

4. Разметка кода

Документация описывает замысел.
Но агенту нужно ещё понимать, какой файл к какому модулю относится, иначе граф остаётся красивой схемой в стороне от реальности. Для этого код размечен якорями — парными комментариями, которые линтер проверяет на парность, уникальность и соответствие реальным экспортам.

// START_MODULE_CONTRACT// PURPOSE: validate JWT and resolve User// SCOPE: src/auth/validate.ts// DEPENDS: M-AUTH-TOKENS// LINKS: M-AUTH-VALIDATE// ROLE: RUNTIME// MAP_MODE: EXPORTS// END_MODULE_CONTRACT// START_CONTRACT: validateUser// PURPOSE: validate JWT, resolve user// INPUTS: req: Request (with Authorization: Bearer header)// OUTPUTS: User | null (null if invalid, never throws)// SIDE_EFFECTS: none// END_CONTRACT: validateUserexport function validateUser(req: Request): User | null {  // START_BLOCK_DECODE_JWT  const token = extractToken(req);  const payload = decodeJwt(token);  // END_BLOCK_DECODE_JWT  // START_BLOCK_CHECK_CLOCK_SKEW  if (Math.abs(Date.now() - payload.iat * 1000) > 60_000) return null;  // END_BLOCK_CHECK_CLOCK_SKEW  return payload.user;}

LINKS: M-AUTH-VALIDATE — это способ связать файл с модулем. Модель когда делает grep, то она найдёт всё остальное. Вместо сорока файлов — два абзаца по делу.

5. Два графа и детектор дрейфа

То, чего в исходной методике не было.

Графов два. Declared — то, что записано в .grace/graph/: модули, зависимости, границы. Computed — то, что строится из AST реального кода: кто кого вызывает, где что определено. Первый описывает замысел, второй — факт. Они существуют одновременно и сверяются инструментом detect_changes, который сверяет declared-граф с файлами.

Типы расхождений:

  • code_without_module — файл с кодом, ни к одному модулю не привязан;

  • module_without_code — модуль заявлен, кода под ним нет;

  • stale_crosslinks — зависимость объявлена, а целевого модуля нет;

  • missing_verification — модуль есть, проверки нет;

  • markup_missing — в файле нет MODULE_CONTRACT или MODULE_MAP, также смотрим, есть ли LINKS.

Штатно ставится post-commit-хук, который обновляет computed-граф. А detect_changes запускается явно или в CI, его можно повесить на pre-commit.
В итоге расхождение между замыслом и кодом обнаруживается сразу.
Отдельно скажу про impact: перед правкой агент спрашивает, кого затронет изменение.

6. Режимы migration и post_migration

Самая частая причина, по которой методики «сначала спецификация, потом код» умирают: их пытаются применить к проекту, который существует десять лет. Там спецификации нет, а код есть, и он работает. Если объявить артефакты источником истины с первого дня, получится не внедрение, а переписывание проекта под красивую схему.

Поэтому режимов два, и они переключаются явно.

migration — истина в коде. Агент читает существующий код, восстанавливает замысел, размечает файлы, заполняет проверки, пишет документацию, проходит через гейты одобрения пользователем.

post_migration — истина в артефактах (документации). Расхождение теперь подозрительно: либо в коде завёлся баг, либо кто-то протащил требование без одобрения. Три возможные реакции: обновить артефакт и провести изменение через бандл C-*, откатить код под контракт, либо записать в DRIFT.md и заблокировать модуль до решения.

7. Почему это работает

Подробно про саму методику можно почитать после установки или тут.
Обоснование можно почитать тут.

Кратко, все десять обязательны, вместо частичного применения лучше вообще не тратить силы на методику:
1. Явный замысел первичен. Любой код — следствие утверждённого артефакта: требования в requirements.xml, запись модуля M-* в графе, бандл изменения C-*. Кодогенерации без утверждённого замысла нет. В миграции замысел восстанавливают из кода, но и там требование одобряет человек до первых правок.
2. Синтез по утверждённому чертежу. Каждый артефакт подтверждается человеком, прежде чем стать входом для следующего шага. Гейты: архитектура модулей, черновик проверок, бандл изменения, изменение публичного контракта, разрешение дрейфа. Пропущен хотя бы один — методология нарушена.
3. Семантический каркас кода. Файл несёт MODULE_CONTRACT с PURPOSE, SCOPE, DEPENDS и LINKS, функции — CONTRACT: fn, логика размечена блоками до 500 токенов, MODULE_MAP перечисляет экспорты. Линтер проверяет парность, уникальность и паритет карты с реальными экспортами. Разметка здесь — контракт с агентом, а не стилистика.
4. Контекст через граф знаний. Declared-граф описывает замысел, computed-граф строится по AST. Оба живут одновременно и сверяются detect_changes.
5. Двойное назначение разметки. Якоря служат шаблоном для генерации и индексом для навигации: при генерации агент пишет внутри якорей; при поддержке агент получает только нужные блоки; при impact-анализе узлы графа сопоставляются с якорями по диапазонам строк. Выбросить якоря — потерять обе возможности.
6. Пропорциональная гранулярность. Глубина разметки и логов не одинакова для всех модулей, а зависит от класса критичности. Класс модуля фиксируется.
7. Код — живой документ. Правка кода или контракта тянет за собой артефакт: агент оформляет бандл C-*, правит граф и проверку, analyze пересчитывает computed-граф, а post-commit хук запускает его сам.
8. Наблюдаемое belief-state. В коде есть точки: граница модуля или критический блок, где ассистент принял неочевидное допущение и оставил его в логе. В этой точке ИИ пишет, что «думает» в этой точке: лог-событие с якорем, полем belief и session_id. Не скрытое рассуждение, а запись, привязанная к месту в коде. Потом, много времени спустя, когда придёт этот лог из прода, можно будет поднять историю и состояние ИИ.
9. Сквозная прослеживаемость. Требование → M-* → V-<M-id> → LINKS в контракте → якорь в коде → событие в логе.
10. Управляемая автономия. Человек утверждает и проверяет, а агент исполняет в рамках каркаса. Автономия заканчивается там, где кончается утверждённое: дальше агент останавливается и спрашивает.

Итоги

Чисел не будет. Контролируемого эксперимента я не ставил. По ощущениям работает хорошо. Если в марте при переходе с Claude на DeepSeek он мне показался деревяненьким, то сейчас (в ноябре) он достаточно бодро решает задачи.

Пробуйте:

pipx install doubled-graph

doubled-graph setup

Далее введите в ваш ИИ первый промпт. Прям скопируйте целиком из файла.

  • Сценарий 1. Новый проект — prompts/new-project/00-entry-prompt.md.

  • Сценарий 2. Миграция существующего проекта — prompts/migrate-existing-project/00-entry-prompt.md.

ссылка на оригинал статьи https://habr.com/ru/articles/1103614/