dejavu-gates
v2.53.1
Published
Cross-session error gates for AI coding agents: detects recurring tool-call failures and promotes them into enforced gates. Remind first, block on same-session repeat offense. Works with OpenCode, Claude Code, Codex CLI, Gemini CLI, Cursor, Copilot CLI, a
Downloads
2,803
Maintainers
Readme
[!IMPORTANT]
opencode-dejavuпереименован вdejavu-gates— вы на новом доме. Старый URL github.com/WhiteBite/opencode-dejavu навсегда редиректит сюда.
- Конфиг OpenCode:
{ "plugin": ["opencode-dejavu"] }→{ "plugin": ["dejavu-gates"] }- git remote:
git remote set-url origin https://github.com/WhiteBite/dejavu-gates.git- npm: старый пакет
opencode-dejavuобъявлен устаревшим и заморожен на 2.27.0; все релизы начиная с 2.39.0 — этоdejavu-gates
Протез памяти с зубами поверх сессий для AI-кодинг-агентов. Агенты повторяют одни и те же ошибки, потому что забывают их между сессиями, и markdown-правила это не лечат. dejavu механически детектирует повторяющиеся сбои вызовов инструментов (bash, read, edit, write, glob, grep) и промоутит их в принудительные гейты: напоминание при следующей попытке, жесткий блок при повторном нарушении в той же сессии. Один движок, много хостов: OpenCode (плагин), Claude Code, Codex CLI, Gemini CLI, Cursor, Copilot CLI, Crush, Devin CLI, Kiro (hook-handler CLI), Cline (встроенный плагин). TypeScript + Bun, поставляется как исходники, без шага сборки.
Быстрый старт
npm install dejavu-gates
dejavu reportТесты: npm run test
Поддерживаемые харнессы
| Харнесс | Установка | Блок (pre) | Remind NOTE (post) | Примечания |
|---|---|---|---|---|
| OpenCode | npm plugin / source | ✅ | ✅ | полная интеграция: все каналы, repeat guard, контекст компакции |
| Claude Code | install-hooks.ts | ✅ | ✅ | аннотации через additionalContext; хук SessionStart вносит дайджест гейтов (топ enforced-гейтов) в начале сессии; хуки не несут exit codes → текстовая детекция |
| Codex CLI | install-hooks.ts | ✅ | ✅ | хуки стабильны и включены по умолчанию; Pre/PostToolUse срабатывают на каждый инструмент (matcher покрывает Bash + apply_patch); для project hooks может остаться trust prompt при первом запуске |
| Gemini CLI | install-hooks.ts | ✅ | ✅ | BeforeTool/AfterTool |
| Cursor | install-hooks.ts | ✅ | ✅ | shell events + CC-совместимые события |
| Copilot CLI | install-hooks.ts | ✅ | ✅ | семейства payload в camelCase + PascalCase |
| Crush | install-hooks.ts | ✅ | ❌ degraded | upstream имеет только PreToolUse — enforcement + общий store всё равно защищают; post-аннотаций нет |
| Devin CLI | install-hooks.ts | ✅ | ✅ | merge событий в корень .devin/hooks.v1.json; Claude-формат хуков также авто-импортируется из .claude/settings.json |
| Kiro | install-hooks.ts | ✅ | ✅ | .kiro/hooks/dejavu-gates.json; NOTE летит сырым stdout хука (канал контекста Kiro); только project scope |
| Cline | cline plugin install --npm dejavu-gates | ✅ | ✅ | встроенный плагин-хост (src/cline-plugin.ts); только SDK/CLI/Kanban — расширения VS Code и JetBrains не имеют системы плагинов; exit codes нет → текстовая детекция |
| Zed, Aider | — | ❌ | ❌ | нет hook API для перехвата вызовов инструментов — не портируемо |
| Windsurf, Amp | — | (planned) | ❌ | поверхности только с блоком / fire-and-forget; отложено, пока не появится инъекция контекста |
Единый store — гейты путешествуют между харнессами. Каждый хост читает и пишет один и тот же store (<repo>/.opencode/dejavu/ + ~/.config/opencode/dejavu/, DEJAVU_HOME переопределяет). Сигнатуры вызовов харнесс-нейтральны (имена инструментов и поля аргументов нормализуются до подписи), поэтому гейт, выученный в Claude Code, срабатывает в OpenCode, Cursor или где угодно еще, и наоборот.
Как это работает
tool call fails → signature normalized (paths/numbers/hashes stripped)
→ pattern-key counted, sessions tracked
→ 3 failures across 2 distinct sessions → gate promoted
next attempt → [dejavu] REMINDER (call aborted, agent sees the correction)
retry fails again → same-session repeat offense → hard BLOCK on further attempts
diagnostic cmd → gate stays remind-only: the call RUNS and the reminder rides
on the failing output as a [dejavu] NOTE (once per session)Проектные решения (post-mortem существующих подходов):
- Сначала напоминание, блок при повторе. Чистый блок запускает гонку вооружений — агент обходит гейты (
npmзаблокирован → используетpnpm). Напоминание с коррекцией учит; блок зарезервирован для проигнорированных напоминаний. - Сообщения гейтов — учителя. Каждое сообщение несет
CORRECTION:(что делать вместо) иEVIDENCE:(N сбоев в M сессиях), а не просто запрет. - Только механические pattern-keys. Никакой LLM-классификации ошибок в горячем пути — ненадежный компонент не занимается надежностью.
- Две области действия. Репозиторные ловушки живут в
<repo>/.opencode/dejavu/(можно коммитить); паттерны, замеченные в 2+ проектах, — привычки агента, и переезжают в~/.config/opencode/dejavu/. Ни один store не видит все проекты, поэтому глобальный индекс паттернов (index.json) считает уникальные директории проектов на каждый key и двигает эскалацию. - Гейты гниют — значит, истекают. 60 дней без рецидива — и гейт сбрасывается. Гейт, заблокировавший 10+ раз, но не убравший ошибку, получает
review: trueдля ручного осмотра. - Метрика — recurrence-after-gate. Ведется по каждому гейту как
recurredAfterGate: если гейты не снижают рецидивы, весь подход неверен, и это будет видно в данных. - Enforcement имеет обратную связь. Метрика действует: гейт, который продолжает падать после промоушена (3+ рецидива в 2+ сессиях, нарушивших снова после напоминания), или который упорно обходят явно (блокирующие: 3+ override
dejavu:proceedв 2+ различных сессиях; напоминающие: 5 различных обходящих сессий, без сырого счётчика — обход гейта, который никогда не прерывает, — более слабый сигнал трения; одна упрямая/инжектированная сессия не может разоружить гейт), — это трение, а не обучение: такой гейт понижает себя доwatchingи больше не промоутится механически (feedbackDemoted). Человек может вернуть принуждение, выставивstatusобратно и очистивfeedbackDemotedвgates.json; тогда гейт получает свежий льготный период. - Нет идентичности — нет зубов. Сигнатура, чья суть целиком съедена параметризацией (
cmd <path> <str>,node <str>), матчит целое семейство команд и никогда не сможет принуждать — только наблюдать. Слишком общие формы деградируют в evidence вместо наказания несвязанных вызовов. - Fail-open, всегда. Hook CLI никогда не клинит хост: кривой payload, сломанный store или внутренний баг dejavu дают
{}+ exit 0. stdout несет только JSON решения; всё остальное идет в stderr.
Установка
Предварительное условие для любого пути: Bun в PATH (hook-обработчики исполняют чистый TypeScript; сборки нет).
Одна команда, любой харнесс (рекомендуется)
npx -y dejavu-gates install # auto-detects installed harnesses, installs project-scope
npx -y dejavu-gates install --user # user-scope (~/.claude, ~/.codex, ...)
npx -y dejavu-gates install --harness claude,cursor --yes
npx -y dejavu-gates uninstall # removes only dejavu-managed entries, keeps your other hooks
npx -y dejavu-gates hooks --check # drift report: ok / stale (moved clone) / missing / brokenУстановщик идемпотентно мержится в конфиг каждого харнесса (чужие хуки и поля выживают), делает копию файла в <config>.dejavu-bak перед изменением, отказывается трогать непарсимые конфиги и пишет hook-команды, вызывающие CLI установленного пакета напрямую (никакого npx в горячем пути — хуки fires на каждый вызов инструмента). Либо поставьте пакет один раз (npm i -g dejavu-gates или npm i -D dejavu-gates) и используйте команду dejavu / dejavu-gates вместо npx -y.
Нативные каналы плагинов (без npx, автообновляемые)
| Харнесс | Команда |
|---|---|
| Claude Code | claude plugin marketplace add WhiteBite/dejavu-gates затем claude plugin install dejavu-gates@dejavu-marketplace |
| Codex CLI | codex plugin add WhiteBite/dejavu-gates (хуки включены по умолчанию; одобрите trust prompt при первом запуске, если появится) |
| Copilot CLI | copilot plugin install WhiteBite/dejavu-gates |
| Cursor | IDE: /add-plugin → browse marketplace → dejavu-gates (или скопируйте репо в ~/.cursor/plugins/local/dejavu-gates) |
| Gemini CLI | gemini extensions install https://github.com/WhiteBite/dejavu-gates |
| Crush | системы плагинов нет — используйте установщик выше или правьте crush.json руками |
| Devin CLI | маркетплейса плагинов нет — установщик пишет .devin/hooks.v1.json; Devin также авто-импортирует хуки Claude-формата из .claude/settings.json |
| Kiro | маркетплейса плагинов нет — установщик пишет .kiro/hooks/dejavu-gates.json (project scope; пользовательского пути хуков Kiro не документирует) |
| Cline | cline plugin install --npm dejavu-gates (или положите src/cline-plugin.ts в .cline/plugins/); только хосты SDK/CLI/Kanban |
Сопутствующий протокол реакции (skills/dejavu/) поставляется внутри бандла плагина и автоматически обнаруживается форматами плагинов Claude Code, Cursor и Gemini CLI.
GitHub Packages (зеркало с авторизацией)
Каждый релиз также публикуется в GitHub Packages как @whitebite/dejavu-gates. GitHub Packages требует токен даже для публичных пакетов, поэтому npmjs.com выше остается рекомендованным каналом:
# .npmrc
@whitebite:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=<PAT with read:packages>npm i -D @whitebite/dejavu-gatesOpenCode
npm (рекомендуется) — OpenCode ставит его сам при старте. OpenCode V1 использует ключ "plugin"; V2 переименовал его в "plugins":
// ~/.config/opencode/opencode.json (global) or opencode.json (project)
// OpenCode V1 (@opencode-ai/plugin)
{ "plugin": ["dejavu-gates"] }// OpenCode V2 (@opencode/cli) — or run `opencode plugin add dejavu-gates`
{ "plugins": ["dejavu-gates"] }Из исходников:
git clone https://github.com/WhiteBite/dejavu-gates ~/.config/opencode/vendor/dejavu
cd ~/.config/opencode/vendor/dejavu && bun install// ~/.config/opencode/plugins/dejavu.ts
export { Dejavu } from "../vendor/dejavu/index.ts"Сопутствующий skill (протокол поведения агента): скопируйте skills/dejavu/ в ~/.config/opencode/skills/dejavu/.
Команда статуса: скопируйте commands/dejavu.md в ~/.config/opencode/command/dejavu.md (Claude Code, Cursor и Gemini CLI подхватывают ту же команду из бандла плагина автоматически: commands/dejavu.md для первых двух, commands/dejavu.toml для Gemini).
Перезапустите OpenCode. Гейты появятся сами по мере повтора сбоев — настраивать нечего.
Вручную / из клона (все харнессы)
git clone https://github.com/WhiteBite/dejavu-gates && cd dejavu-gates && bun install
bun scripts/install-hooks.ts --harness claude # project scope (cwd)
bun scripts/install-hooks.ts --harness claude --user # user scope (~/.claude/...)
bun scripts/install-hooks.ts --harness codex --dry-run # preview without writingГенератор мержит hook-записи в конфиг харнесса (.claude/settings.json, .codex/hooks.json, .gemini/settings.json, .cursor/hooks.json, .github/hooks/dejavu.json, .crush/crush.json, .devin/hooks.v1.json, .kiro/hooks/dejavu-gates.json), указывая на bun "<clone>/src/cli.ts" <pre|post> --harness <name>. Он идемпотентен, сохраняет остальные хуки/поля и отказывается трогать непарсимый конфиг.
Особенности харнессов:
- Codex: хуки — стабильная фича, включена по умолчанию; Pre/PostToolUse fires на каждый function tool с каноническими именами (
Bashдля shell,apply_patchдля правок с алиасами матчераWrite/Edit,spawn_agent, MCP-инструменты под плоскими именами). Matcher dejavu покрываетBashиapply_patch; для project hooks может остаться trust prompt при первом запуске. - Crush: только PreToolUse (AfterTool в upstream нет) — dejavu работает в degraded: блокирование работает, напоминания не могут аннотировать; общий store всё равно обучает Crush гейтами, выученными в других местах.
- Devin CLI: хуки живут в
.devin/hooks.v1.json, где корень файла И ЕСТЬ карта событий — установщик мержит записи dejavu в него и снимает их при uninstall, чужие события выживают. Devin также авто-импортирует хуки Claude-формата из.claude/settings.json(read_config_from.claude, включено по умолчанию), поэтому установка для Claude уже гейтит и Devin-сессии. Пользовательского файла хуков не документировано — только project scope. - Kiro: хуки — отдельные файлы в
.kiro/hooks/; dejavu владеет тамdejavu-gates.json. Kiro блокирует на любом ненулевом exit хука и инжектирует stdout успешного хука в контекст агента, поэтому напоминающий NOTE летит сырым stdout, а allow ничего не пишет. Пользовательского пути хуков Kiro не документирует — только project scope. - Claude Code:
PostToolUseFailureподключен к тому же post-обработчику; payloads не несут exit codes, поэтому детекция сбоев идет по тексту вывода (текстовый канал движка). ХукSessionStartвносит дайджест топ enforced-гейтов проекта (сначала blocking, затем reminding) какadditionalContext— агент узнает гейтнутые вызовы до того, как потеряет первый на напоминании.
Ручной вызов (любой харнесс с command hooks):
echo '{"hook_event_name":"PreToolUse","session_id":"s","tool_name":"Bash","tool_input":{"command":"deploy.sh"},"cwd":"/repo"}' \
| bun src/cli.ts pre --harness claude --store /repo
# exit 0 + {} = allow; exit 2 + stderr = blocked with a [dejavu] messageНадежность и безопасность
- Политика блокировки — блокирующими гейтами могут становиться только
bash-команды, которые НЕ являются диагностикой. Диагностика и итерационные команды (tsc/eslint/mypy/pytest/phpunit/rspec/rubocop/gradle-test/mvn test/dotnet test/flutter/curl/grep,dart run,go run|build|test|vet,cargo run|build|test|clippy,swift build|test...) промоутятся вreminding— они аннотируют падающий вывод пометкой[dejavu] NOTE(раз в сессию) и никогда не блокируют и не прерывают запуск, так что итерация над тестами и сборками не наказывается. Файловые пробы (read/edit/write/glob/grep) остаютсяwatching: измеряются, видны в отчетах, никогда не прерывают.canBlock()/canRemind()вsrc/patterns.ts— единственный источник правды. Сигнатуры без остаточной идентичности (см. выше) не принуждают ни на одном тире. - Идентичность one-liner'ов — для
python -c/node -e/bun -e/php -r/ruby -e/perl -e/julia -e/lua -e/Rscript -eи PowerShell-Commandкод в payload И ЕСТЬ вызов, поэтому он отпечатывается (<code:hash>) вместо схлопывания в<str>: разные скрипты никогда не делят гейт, а один и тот же падающий скрипт по-прежнему сходится.-rдает fingerprint только у php (у node/ruby/perl-r— флаг предзагрузки). Формы PowerShell покрыты — кавыченные пути exe (& "C:\...\python.exe" -c ...), here-string payloads, вызовы с env-префиксом (PYTHONPATH=x python -c ...). Легасые голые формы-c <str>никогда не принуждают ни на одном тире (residual-identity guard). - Разворачивание оберток —
cmd /c|/k "..."нормализуется во ВНУТРЕННЮЮ команду: key гейта, идентичность и диагностический тир видят реальный вызов вместо формыcmd <path> <str>, матчающей каждый запуск cmd. - Чистое сохранение — терминальные управляющие символы (VT-цвета PowerShell, NUL) срезаются до того, как что-либо ляжет на диск; сигнатуры, сниппеты и коррекции никогда не несут ANSI escape'ов. Payload харнессов — внешний недоверенный ввод, и он проходит тот же барьер
sanitizeForStore(), прежде чем что-либо персистится. - Вычищение секретов — каждая сигнатура и сниппет проходят
scrubSecrets()(паттерны OpenAI/Anthropic/AWS/GitHub/Slack/Stripe/JWT/bearer/строки подключения к БД/PEM +root@host) до записи на диск. Исторические данные чиститmigrate()при init либо черезbun scripts/migrate.ts <dirs...>(чистит и логи). - Штатные ненулевые exits — exit 1 у диагностики НЕ является сбоем (это их нормальный итог «ничего не найдено / найдены проблемы»). Exit ≥ 2 считается всегда.
- Aborted ≠ failed — отмененные/прерванные исполнения инструментов ("Tool execution aborted") — инфраструктурный шум, и никогда не считаются сбоями.
- Guard против долгих процессов — ЗАПУСК dev-сервера/наблюдателя НА ПЕРВЕМ ПЛАНЕ (
npm run dev,next dev,vite,flask run,uvicorn,python -m http.server,mvn spring-boot:run,gradle bootRun, …) заблокировал бы bash-вызов до его таймаута и оставил сироту-процесс. dejavu прерывает его в before-hook с напоминанием «запускай detached» (tmux /nohup … &/Start-Process/ стартовый скрипт, спавнящий detached). Detached-формы (trailing&,nohup,tmux,Start-Process) и one-shots/сборки (vite build,npm run build) проходят молча;# dejavu:proceedразрешает осознанный запуск на переднем плане. Список стартеров намеренно консервативен (неоднозначныеnode <file>,go run,dotnet runне флабятся). - Guard против подавленного spawn — компенсирующая мера для anomalyco/opencode#29831 (удалить, когда фикс выйдет в upstream): вызов, который спавнит DETACHED демон, одновременно пипя/редиректя stdout (
… start | Out-Null,… start > $null), висит навсегда — opencode завершает bash-вызов только по EOF stdio, а живой демон держит пайп открытым. Статический ограниченный список (DETACHED_SPAWNERS) прерывается в before-hook с коррекцией «запусти spawn голой командой (1-3 строки вывода) и опрашивай статус отдельным вызовом». Голые spawn'ы, не-spawn глаголы и редиректы только stderr (2>,2>&1) проходят;# dejavu:proceedобходит (логируется как warning). - Guard против наследуемого spawn — второй путь утечки той же EOF-семантики, проверен эмпирически:
Start-Process -RedirectStandard*/-WaitВКЛЮЧАЕТ наследование хэндлов, поэтому спавнутый процесс получает stdio-пайпы вызова и держит их до своего выхода — демон, переживающий вызов (-WindowStyle Hidden|Minimizedили известный серверный стартер в качестве спавнимой команды), вешает вызов навсегда, и редирект всех трех потоков НЕ помогает (голыйStart-Processничего не течет). Before-hook прерывает такую форму и учит голым spawn'ам (демон пишет свои логи сам) либо двухступенчатому spawn (внешний голыйStart-Processpwsh one-liner'а, который редиректит внутри);# dejavu:proceedобходит (логируется). - Guard против orphan-job —
Start-Jobбез ожидания внутри вызова выполняется внутри PowerShell этого вызова и молча убивается, когда вызов заканчивается: «фоновая» работа, которая никогда не выживает, без единой ошибки. Before-hook прерывает это и учит паттерну detached bare-Start-Process (либоWait-Job/Receive-Job -Waitдля результатов внутри вызова);# dejavu:proceedобходит (логируется). - Содержимое файла — не вывод команды — в OpenCode текстовые сигнатуры сбоев сканируются только для
bash; сбоиread/edit/writeприходят исключительно из event-канала (файл со словом "TypeError" внутри — не сбой). Внешние харнессы доставляют сбои файловых инструментов через свои post hooks, где payload — текст ошибки самого инструмента, а не содержимое файла. - Конкурентность — мутации gates.json идут под эксклюзивным lockfile; аппенд логов и ротация берут собственный лок; записи делаются через tmp+rename с ретраями на EPERM/EACCES/EBUSY (Windows AV/индексатор). Длинные пути NT получают префикс
\\?\. Параллельные вызовы инструментов в ОДНОМ процессе сериализуются in-process очередью, прежде чем вообще коснуться файлового лока. Между процессами (несколько окон OpenCode или параллельные запуски hook-CLI из одного харнесса), если лок не удалось взять за 3 секунды, критическая секция деградирует в unlocked (конвейер инструментов никогда не должен виснуть) и пишет событие логаdegraded. Временно нечитаемый store (лок AV,EISDIR) бросает исключение вместо парсинга как пустой, поэтому неудачное чтение никогда не даст следующему сохранению затереть настоящие гейты. - Безопасно для нескольких процессов — цепочка эскалации remind→block персистится на самом гейте (
remindedSessions/failedSessions), а не в памяти процесса: несколько окон, несколько харнессов и короткоживущие процессы hook-CLI над одним store видят одну и ту же цепочку. Enforcement всегда читает свежее состояние гейта под локом store. - Склейка почти-дубликатов — новые сбои мержатся в существующие паттерны по нормализованному Levenshtein ≤ 0.3 с абсолютным порогом в 3 правки (заменило token Jaccard, который схлопывал все
<str>плейсхолдеры; порог останавливает мержи видаgit pushvsgit pull). - Ограниченная память — per-session карты ограничены (50 записей на гейт) и освобождаются по окончании сессии; обработанные part ID вытесняются FIFO; TTL-экспирация и ротация логов перезапускаются каждые 6 часов в долгоживущих процессах (CLI прогоняет свои идемпотентные init-проходы на каждый запуск и флашит отложенные события лога перед выходом).
- Миграция — гейты вне политики блокировки автоматически перетируются (диагностика попадает в
reminding, остальное вwatching); уже доказанно повторяющаяся диагностика начинает напоминать сразу; проектные копии уже глобальных гейтов сливаются в глобальный гейт (evidence консолидируется, никогда не удаляется). - Self-healing — каждый init сверяет store: непарсимый
gates.jsonкарантинится (байты сохраняются какgates.json.corrupt-<ts>), записи гейтов строго парсятся и механически чинятся (перевёрнутые даты меняются местами, дубликаты ключей сливаются, секреты/управляющие символы повторно санитизируются, протухший blocking понижается), непарсимые строки лога вырезаются вlog.jsonl.corrupt, и кросс-проектный индекс сверяется. Каждый ремонт логируется событиемrepaired/quarantined. - Гейты заживают, а не только накапливаются — dejavu видит и успехи: SUCCESS, совпавший с действующим гейтом, растит
succeededAfterGate, и после 3 подряд гейт уходит вwatching(логируетсяhealed) — починенная команда перестаёт триггерить напоминания. Сбой сбрасывает серию. Негативный близнец: гейт, с которым агент продолжает бороться (рецидивы или явные override), понижает себя сам (логируетсяdemoted) — принуждение слушает поведение в обе стороны. Третий путь: гейт, напомнивший 5+ раз без единого повторного нарушения, СВОЙ УРОК ВЫУЧИЛ — он мягко уходит на пенсию (логируетсяretired-taught), повторный промоут при новых сбоях остается возможным. Heal-aware: блокирующий гейт с живой heal-серией не прерывает первый запуск — он даёт вероятно-починенной команде пройти и блокирует только повторный сбой. - Авто-коррекции, без ручной работы — промоутнутый гейт всегда приходит с механической дефолтной коррекцией, которую можно переопределить; она выбирается по семейству команд (протухшие артефакты
--check, падающие тесты, ошибки типов, сеть, установки, сборки go/cargo/maven/dotnet/rspec/phpunit/make) либо из пойманной строки ошибки, поэтому гейт никогда не сидит "NOT TEACHING" в ожидании человека.migrate()добирает существующие гейты. - Repeat channel (только OpenCode) — DashScope/Qwen жестко отвергает запрос, в истории которого одинаковый вызов инструмента (name + args байт-в-байт) стоит в последовательных раундах (HTTP 400), и один отказ отравляет сессию навсегда. Transform hook сканирует каждый исходящий payload stateless: вхождения после первого в серии одинаковых-последовательных получают маркер
_dejavu_repeat(только в payload, никогда не персистится), серия, дошедшая до хвоста, получает пометку[dejavu] REPETITIONна своем последнем tool result, и третий одинаковый вызов жестко блокируется в before-hook. Обход:_dejavu_proceed: trueв аргументах (логируется как override). Никаких гейтов, никакого персиста, никакого промоушена — чистая гигиена payload. - Loop break (только OpenCode) — модели, которые продолжают перевыпускать вызов после сообщения REPEAT STOP, получают автоматическое сообщение с ролью user, дописанное в исходящий payload (только payload, помечено
synthetic). Ошибы заблокированных инструментов — стимул внутри цикла, который слабые модели читают как «повтори»; реплика с ролью user — единственный стимул, надежно выжимающий текстовый ответ и завершающий цикл. Инъекция срабатывает, только пока та же серия держит хвост — реальное сообщение пользователя или текстовый ответ ее заканчивают, — и логируетloop-breakодин раз на серию.
Наблюдаемость (подручные инструменты отладки)
- Каждый
log.jsonlполучает событиеinitсPLUGIN_VERSION; событияdetectedнесутchannel(exit/text/event) и сырой exit code;reminded/blockedнесутvia(exact/fuzzy/segment). Протухшие сессии плагина поэтому видны в данных. bun scripts/doctor.ts [--repair] [projectDirs...]— отчет одной командой по всем инвариантам, которые подразумевает модель данных: форма гейта, дубликаты ключей, временной порядок, порча вложенных токенов, blocking без evidence, нарушения политики, согласованность index↔gates, протухшие проектные копии, пропущенная эскалация, целостность логов, секреты, дрейф версий.--repairсначала лечит (идемпотентно), потом отчетит.dejavu report [dirs...](npm bin) гоняет тот же отчет для любого харнесса, OpenCode не нужен.bun scripts/analyze.ts [projectDirs...]— сводка store: статусы, инструменты, топ паттернов.dejavu lesson list/dejavu lesson set <key> "<one-line fix>"— просмотр гейтов, у которыхcorrectionвсе еще машинный дефолт, и запись человеческой коррекции на существующий гейт (создавать гейты нельзя; промоут остается механическим).lesson list --allвключает watching-гейты;lesson set --author owner|agentзаписывает автора коррекции;lesson retire-when <key> <spec>объявляет внешнее условие недействительности (dep:<name>@>=<min>,path-present/absent:<p>,tag:<name>), которое вычисляетdoctor.- Команда
/dejavu(OpenCode, установлена глобально) сначала гоняет doctor, потом отчет. - Диагностика hook CLI: выставьте
DEJAVU_DEBUG=1, чтобы видеть строки лога движка в stderr (stdout всегда остается чистым JSON).
Покрытие детекции
| Канал | Ловит |
|---|---|
| exit code + текст вывода (OpenCode tool.execute.after) | сбои bash (ненулевой exit, TS-ошибки, падения тестов, stack traces) |
| только текст вывода (post hooks внешних харнессов — exit codes там не существует) | вывод failure-формы: error TS…, FAIL, panic:, [ERROR], PHPUnit/FAILURES!, Fatal error:, TAP not ok, … |
| сканирование событий message.part.updated (OpenCode) | сбои уровня инструментов (чтение отсутствующего файла, отклоненные правки), которые никогда не доходят до after-hook; текст ошибки параметризуется в Sentry-стиле |
| chain-segment matching | гейты срабатывают, даже когда гейтимая команда прячется в цепочках x && gated-cmd, обертках cmd /c "..." или подстановках $(...) / backtick |
| experimental.chat.messages.transform (OpenCode) | repeat channel: последовательные одинаковые вызовы инструментов санитизируются в исходящем payload (профилактика DashScope 400) + NOTE на 2-м раунде, жесткий блок на 3-м |
| companion skill | протокол поведения агента (как реагировать, когда аннотировать) |
| команда /dejavu | отчет о статусе: активные гейты, метрика рецидивов, флаги review |
Языковые экосистемы, покрытые детекцией сбоев: JS/TS, Python, Go, Rust, Java/Kotlin/Scala (Maven/Gradle/sbt), .NET, Ruby, PHP (PHPUnit), Dart/Flutter, C/C++, Elixir; шеллы: bash, PowerShell, cmd. Не покрыто (по замыслу): семантически равные, но синтаксически разные сбои за пределами fuzzy-матчинга (Levenshtein ≤ 0.3, ≥ 3 правки).
Файлы данных
| Файл | Содержимое |
|---|---|
| ~/.config/opencode/dejavu/gates.json | глобальные гейты (привычки агента) |
| ~/.config/opencode/dejavu/index.json | кросс-проектный индекс паттернов: в каких директориях проектов замечен каждый failure key (evidence эскалации) |
| <repo>/.opencode/dejavu/gates.json | проектные гейты (ловушки репо) |
| */dejavu/log.jsonl | каждое событие: detected, promoted, reminded, retry-allowed, blocked, override, expired, recurred-after-gate, demoted, healed, retired-healed, retired-taught, repaired, quarantined, degraded, init |
| */dejavu/*.corrupt* | карантинная порча (непарсимый gates.json, вырезанные строки лога) — байты сохранены для форензики; после осмотра можно удалять |
Пути store исторические (.opencode/), но сам store харнесс-нейтрален — ЕГО ДЕЛЯТ ВСЕ харнессы. Оба файла редактируются человеком. Удаление объекта гейта отключает его. Правка correction улучшает то, что говориться агенту. Очистка feedbackDemoted (и возврат status в blocking/reminding) возвращает принуждение гейту, который поведение агента отправил на пенсию, — он получает свежий льготный период через feedbackBaseline.
Проектные store сами держат себя вне git status: init пишет самоиgnорирующий .opencode/dejavu/.gitignore — gates.json рассчитан на коммит (общие ловушки репо; внутри repo-относительные пути проектов и id сессий, без абсолютных машинных путей), рантайм-файлы (log, index, locks, tmp) игнорируются, — а doctor --repair выметает осиротевшие *.tmp и протухшие *.lock файлы (--prune-corrupt=<days> включает удаление карантинных артефактов старше возраста; по умолчанию 30 дней). Правило .gitignore уровня корня репо на .opencode/ перебивает вложенный re-include — если репо игнорирует .opencode/ в корне, поправьте ignore-правила, чтобы реально коммитить gates.json.
Переопределения окружения
DEJAVU_HOME переноса оба store-каталога; DEJAVU_DEBUG=1 печатает диагностику hook-CLI в stderr. Перечисленные ниже настройки enforcement можно переопределить на процесс без правки исходников — каждая читается один раз при старте и падает обратно в дефолт, если значение не целое в границах:
| Переменная | Дефолт | Границы | Эффект |
|---|---|---|---|
| DEJAVU_TTL_DAYS | 60 | 1–3650 | дней без рецидива до истечения гейта |
| DEJAVU_NOISE_TTL_DAYS | 7 | 1–365 | TTL для одноразовых паттернов (watching, count ≤ 1) |
| DEJAVU_PROMOTE_COUNT | 3 | 1–100 | сбоев, после которых bash-паттерн становится гейтом |
| DEJAVU_PROMOTE_COUNT_PROBE | 5 | 1–100 | сбоев, после которых probe-инструмент становится гейтом |
| DEJAVU_PROMOTE_SESSIONS | 2 | 1–100 | уникальных сессий, необходимых для промоута |
| DEJAVU_HEAL_SUCCESSES | 3 | 1–100 | последовательных успехов, отправляющих гейт на пенсию |
| DEJAVU_DEMOTE_RECURRENCES | 3 | 1–100 | рецидивов после гейта, понижающих его |
| DEJAVU_DEMOTE_OVERRIDES | 3 | 1–100 | явных обходов, понижающих блокирующий гейт |
| DEJAVU_TAUGHT_REMINDERS | 5 | 1–100 | напоминаний без рецидива, после которых гейт считается выученным |
Разработка
bun install
bun run typecheck # tsc --noEmit (index.ts, src/**, scripts/**, test/**)
bun test/smoke.ts # OpenCode plugin behavioral suite (drives index.ts hooks)
bun test/enforce.ts # engine characterization (harness-agnostic core)
bun test/adapters.ts # adapter mapping + decision dialects
bun test/cli.ts # CLI end-to-end (spawn, promotion, block/annotate, fail-open)
bun test/language-gaps.ts # language-ecosystem coverage of patterns.ts
bun test/guards.ts # proactive guards characterization (fire shapes, precedence, bypass)
bun test/messages.ts # teaching-text framing (tier-truthful, data-label, correction bound)
bun test/property.ts # seeded generator: normalization/fuzzy invariants
bun test/fuzz.ts # mutation fuzz: no crash, no invariant break
bun test/env.ts # DEJAVU_* env overrides: resolved tunables + promotion effect
bun run lint:ast # ast-grep structural gates (needs ast-grep on PATH)Архитектура: src/patterns.ts + src/store.ts + src/validate.ts (чистый движок + персист), src/enforce.ts + siblings (харнесс-агностичный enforcement: enforceBefore/enforceAfter/recordEventFailure/cleanupSession поверх EnforceContext), src/adapters/ (маппинг payload ↔ контракт на каждый харнесс), src/cli.ts (точка входа hook-handler для внешних харнессов), index.ts (хост плагина OpenCode). Настройки — именованные константы в начале своих модулей (src/store.ts, src/context.ts, src/before.ts, src/repeat.ts, index.ts).
Структурные гейты живут в .ast-grep/rules/ (гоняются bun run lint:ast и CI): no-load-force-flag запрещает load(true)/loadIndex(true); no-raw-gates-splice запрещает raw splice на массивах гейтов.
Сравнение с аналогами
Обзор ландшафта (сен 2026, проверено ~60 OSS-проектов + нативные фичи). Пространство распадается на два лагеря, которые никогда не пересекаются: guardrail/hook движки перехватывают и блокируют вызовы инструментов, но на каждый вызов оценивают только статические политики, написанные человеком, — никакой памяти о сбоях, ничему не учатся; memory/learning плагины персистят и извлекают между сессиями, но только инжектируют контекст в промпт — ни один никогда не блокирует вызов инструмента. dejavu сейчас единственный shipped-проект, замыкающий цикл механически: наблюдение сбоев → подсчет рецидивов (3× в 2 сессиях) → промоут принудительного гейта → remind/block → heal/demote поведению.
Три оси, разделяющие каждый продукт в пространстве:
| Продукт | Учит правила из наблюдаемых сбоев | Принуждает / блокирует вызовы | Механический горячий путь (без LLM) | |---|---|---|---| | dejavu | ✅ механический промоут | ✅ эскалация remind→block | ✅ | | Cupcake · agentjail · cc-safety-net · probity · guardrails packs | ❌ политика руками человека | ✅ deny | ◐ | | claude-mem · claude-smart · supermemory · opencode-mem · harness-memory | ◐ извлечение LLM, только инжект | ❌ | ❌ | | sinapsis (archived) · harness-forge · projectmem · open-bias | ◐ считается или классифицируется | ❌ advisory only | ◐ | | NeMo Guardrails · Guardrails AI · LLM gateways | ❌ | ❌ на уровне прокси | ❌ | | Claude Code native permissions | ❌ статичный allow/deny | ◐ | ✅ |
Чего нет ни у кого больше
- Обучение + принуждение в одном цикле. Policy-двики блокируют, но никогда не учатся; memory-инструменты учатся, но никогда не блокируют. Ближайшие механизмы умерли на подступах: sinapsis (подсчет вхождений, мульти-сессийный промоут, понижение даунвоутом, TTL — advisory-only, архивирован в авг 2026), harness-forge (журнал сбоев с весами уроков, но классификация LLM), projectmem (предупреждает перед повтором провалившегося подхода, без зубов).
- Правила с отрицательной обратной связью. Гейт, с которым агент продолжает бороться (рецидивы или явные override), понижает себя сам; команда, начавшая получаться, залечивается до пенсии. Ни одна система политик у аналогов не адаптируется к игнорированию.
- Харнесс-нейтральная идентичность. Сигнатуры нормализуются до хеширования, поэтому один store, общий для OpenCode/Claude/Codex/Gemini/Cursor/Copilot/Crush/Devin/Kiro/Cline, означает: гейт, выученный где угодно, срабатывает везде. Конфиги аналогов по конструкции привязаны к харнессу.
- Операционная закалка. Self-healing/карантинный store, doctor-отчет, вычищение секретов перед персистом, проактивные guards against зависания и сирот — в обследованном множестве никто такой поверхности не поставляет.
Где dejavu хуже (честные пробелы)
- Семантическая идентичность. Два синтаксически разных вызова, решающих одну и ту же сломанную вещь (
pnpm tsc --noEmitvsnpm run typecheck), попадают на разные keys; fuzzy-мерж по дизайну — Levenshtein ≤ 0.3 с порогом ≥ 3 правок. LLM-driven memory-инструменты схлопывают такие варианты ценой извлечения — dejavu платит точностью за детерминизм. - Выразительность правил. Гейты ключуются только по сигнатурам вызовов инструментов. Policy-двики руками человека (Rego в Cupcake/agentjail, rulebooks в cc-safety-net,
probity.config.ts) умеют выражать произвольные условия — пути файлов, изоляция тенантов, PII, «никогда не трогать prod», — которые dejavu не может представить. dejavu сознательно не пытается быть security-песочницей; те инструменты дополняют, а не конкурируют. - Разговорная память. claude-mem/claude-smart/supermemory вспоминают решения, предпочтения и историю проекта; dejavu помнит ровно одну вещь: падающие вызовы. Композируемо — они занимают ортогональные хранилища.
- Тяга. claude-mem (~95k★), claude-smart (~800★), abide (~400★) против молодого репо здесь.
Смежные по названию, не конкуренты
- ast-grep / sloppy / линтеры стиля Semgrep — статически проверяют содержимое кода; dejavu потребляет ast-grep для гейтов собственного репо, но принуждает в рантайме на вызовах инструментов.
- NeMo Guardrails / Guardrails AI / LLM gateways (LiteLLM, Portkey, Plano) — патрулируют промпты и ответы на уровне прокси; конвейер инструментов агента они никогда не видят и не перехватывают.
- Reflexion (research) — вербальная саморефлексия в эпизодическом буфере на прогон; никогда не поставлялся как плагин кодинг-агента, ничего не принуждал.
- post_compact_reminder — статичный хук «перечитай AGENTS.md» после компакции; compact-хуки dejavu вместо этого несут настоящее состояние гейтов.
- Hook SDK (cchooks, cc-hooks-ts, claude_hooks, beyondcode SDK) — фреймворки для написания собственных хуков; dejavu — готовая политика, и его CLI говорит на тех диалектах, на которые они нацелены.
- Нативные фичи платформ — permissions/checkpoints Claude Code и плагины OpenCode закрывают статические половины; ни у кого нет кросс-сессионного обучения на сбоях или принуждения по рецидивам (запрос persistent-memory anthropics/claude-code#34556 закрыт без реализации). Если платформа выпустит это нативно, она поглотит нишу — следите, не предполагайте.
Статистика
Честный ответ: dejavu не поставляет бенчмарк-цифер, и этот раздел их не выдумает. Что существует — ваш собственный store: каждый хост пишет log.jsonl, так что evidence локально, по каждому агенту, и аудируемо.
За все время в глобальном store одного разработчика (~/.config/opencode/dejavu/log.jsonl) по состоянию на 29 сен 2026 — 6574 события за 40 активных дней (21 авг – 29 сен), по 11 директориям проектов и всем харнессам, делящим этот store:
| Событие | Количество |
|---|---|
| сбои detected | 3931 (2431 уникальных pattern keys) |
| промоуты гейтов promoted | 237 событий, 211 уникальных keys |
| recurred-after-gate | 163 события, 76 уникальных keys |
| blocked | 56 |
| override (dejavu:proceed) | 383 |
| healed / retired-taught / demoted | 11 / 46 / 30 |
Если читать внимательно: 62 из 211 промоутнутых keys рецидивировали после появления своего гейта. Это не rate снижения — recurrence-after-gate есть сигнал здоровья конкретного гейта (recurredAfterGate), а понижения (30) плюс пенсии от залечивания/обучения (57) — это цикл, закрывающийся в обе стороны. Привычки агента одного человека за шесть недель — выборка размером один; относитесь к этому как к примеру того, как выглядят данные, а не как к доказательству.
Померьте сами. После N сессий реальной работы:
dejavu report # doctor over project + global stores
bun scripts/analyze.ts # statuses, tools, top patternsили выполните /dejavu внутри OpenCode. Важные метрики лежат в gates.json и log.jsonl: recurredAfterGate на каждый гейт (пережил ли ошибку напоминание?) и события healed / retired-taught / demoted (гейт ушел на пенсию, потому что вы починили команду, или потому что боролся с вами?). Если ваши цифры говорят, что подход неверен, данные это покажут — в этом и смысл метрики.
Дорожная карта
- Адаптеры Windsurf / Amp — заблокированы отсутствием пригодных поверхностей инъекции контекста (сегодня только блок); слоты адаптеров существуют
- команда отчетности recurrence-after-gate;
tool.execute.error(issue opencode #27900) закрыт в upstream как not planned — детекция через event-канал остается поддерживаемым путем - автопредложение ast-grep правил для статически детектируемых паттернов (гейты уровня CI репо)
- мерж кандидатов с эмбеддингами для семантических почти-дубликатов (advisory only — решение о принуждении остается механическим; закрывает пробел семантической идентичности выше)
dejavu share— экспорт/merge переносимых бандлов гейтов между машинами и командами (opt-in; гейты и так харнесс-нейтральный JSON, команда делает их перенос явным)- docs-as-code — генерировать справочник гейтов/коррекций из схемы store вместо ручного ухода за прозой, дрейфующей от
validate.ts - переопределения env-переменными — открыть настройки enforcement (пороги промоута, TTL, каппы) через env vars
DEJAVU_*рядом с именованными константами, без конфига - таблица семантического маппинга — поддерживаемый словарь синонимов (
pnpm tsc↔npm run typecheck), питающий существующий механический fuzzy-мерж; advisory only, как и эмбеддинги
Дисклеймер
dejavu — community-проект. Он не создан командами OpenCode, Anthropic, OpenAI, Google, Anysphere (Cursor), GitHub или Charm и с ними не связан.
Лицензия
MIT
Для кого это
Разработчики, которые ежедневно гоняют AI-кодинг-агентов и наблюдают одни и те же сбои в каждой новой сессии — протухший флаг, отсутствующий путь, команда, которая падает только на этой машине. Три профиля:
- Соло-разработчики, тяжело сидящие на агентах — гейты накапливаются из вашего собственного evidence в
log.jsonl, поэтому принуждение соответствует вашей реальной истории сбоев, а не generic rulebook. - Пользователи нескольких харнессов — один store, общий для всех поддерживаемых хостов: гейт, выученный в Claude Code, срабатывает в OpenCode, Cursor или любом другом харнессе, читающем тот же store.
- Команды, которым нужно принуждение без рукописной политики — гейты промоутятся механически из рецидивов (3 сбоя в 2 сессиях) и понижаются сами, когда агент перестает с ними бороться; люди правят только коррекции.
Сценарии применения
- Команда, валится сессия за сессией. После 3 сбоев в 2 различных сессиях промоутится гейт; следующая попытка прерывается напоминанием с
CORRECTION:, а повтор в той же сессии после неотработанного напоминания блокируется. - Падающая диагностика, которая должна продолжать работать.
tsc,pytest,cargo testи прочая диагностика промоутятся вreminding— вызов выполняется,[dejavu] NOTEложится на падающий вывод раз в сессию, ничего не прерывается. - Повторяющиеся сбои файловых проб. Чтения отсутствующих файлов и отклоненные правки попадают в
watching-гейты — измеряются и видны в отчетах, никогда не прерывают. - Серверы на переднем плане, оставляющие сирот. Запуски
npm run dev,uvicorn,next devпрерываются в before-hook с коррекцией run-detached (tmux /nohup … &). - Одна привычка, каждый харнесс. Сигнатуры нормализуются до хеширования, поэтому один и тот же падающий вызов гейтится в любом хосте, делящем store.
- Курирование того, что говорится агенту.
dejavu lesson listпоказывает гейты, все еще на машинных дефолтных коррекциях;lesson setзаменяет их человеческим фиксом.
Почему это
- Обучение и принуждение в одном цикле. Опрос ~60 OSS-проектов в сен 2026 не нашел ни одного shipped-инструмента, который и учит правила из наблюдаемых сбоев, и блокирует вызовы инструментов — policy-двики никогда не учатся, memory-плагины никогда не блокируют.
- Механический горячий путь. Подсчет pattern-keys и fuzzy-мерж Levenshtein ≤ 0.3 решают каждый промоут — никакого LLM на пути сбоя.
- Гейты отвечают поведением. 3 рецидива после гейта или 3 явных override понижают гейт; 3 последовательных успеха залечивают его; 60 дней без рецидива снимают его.
- Один store, 10 харнессов. OpenCode, Claude Code, Codex CLI, Gemini CLI, Cursor, Copilot CLI, Crush, Devin CLI, Kiro и Cline читают и пишут одни и те же гейты.
Примеры
Установка в конкретные харнессы и проверка проводки хуков:
npx -y dejavu-gates install --harness claude,cursor --yes
dejavu hooks --checkПроверка здоровья гейтов — инварианты doctor по каждому найденному store, затем вердикты рецидивов по каждому гейту:
dejavu report
dejavu report --recurrenceЗамена машинной дефолтной коррекции гейта на человеческую (keys берутся из lesson list):
dejavu lesson list
dejavu lesson set <key> "run pnpm install before building"