Skip to content

English | Русский

Архитектура TAUSIK ​

Где это стоит. Поле называет эту дисциплину harness engineering: харнесс — это цикл агента, интерфейс инструментов, управление контекстом и механизмы контроля. TAUSIK — не харнесс: цикл и инструменты принадлежат Claude Code, Cursor, Codex и другим. Это слой проверки и контроля поверх них, и его отличие в том, что без доказательства ничто не считается сделанным.

Три слоя: CLI → Сервис → Хранилище ​

Три слоя с чёткими границами. Сервисный слой содержит бизнес-логику, хранилище — только CRUD и SQL. CLI и MCP — два равноправных входа.

  Инженер (свободный текст)
       ↓
  ИИ-агент (Claude Code / Cursor)
       ↓
  ┌─────────────────────────┐
  │ Навыки (SKILL.md)       │  ← инструкции для агента
  └─────────────────────────┘
       ↓                ↓
  ┌─────────┐    ┌─────────┐
  │ MCP     │    │ CLI     │  ← два входа
  │ (tools) │    │ (bash)  │
  └────┬────┘    └────┬────┘
       └──────┬───────┘
              ↓
  ┌─────────────────────────┐
  │ Сервисный слой          │  ← бизнес-логика, QG-0, QG-2
  │ project_service.py      │
  │ + service_task.py       │
  │ + service_knowledge.py  │
  └─────────────────────────┘
              ↓
  ┌─────────────────────────┐
  │ Слой хранилища          │  ← SQLite CRUD, FTS5, метрики
  │ project_backend.py      │
  │ + backend_queries.py    │
  │ + backend_graph.py      │
  │ + backend_schema.py     │
  │ + backend_migrations.py │
  └─────────────────────────┘
              ↓
  ┌─────────────────────────┐
  │ SQLite (WAL mode)       │  ← .tausik/tausik.db
  │ схема v67               │
  └─────────────────────────┘

Ключевые модули ​

Скрипты (бизнес-логика) ​

В TAUSIK 1.11 пока сохраняется плоская поставка: 563 файла в scripts/, из них 502 — в корне. Это историческая совместимость, а не цель 2.0. Измеренные границы, baseline миграций и порядок перехода описаны в пакетной архитектуре TAUSIK 2.0.

Модули в scripts/, каждый ≤500 строк (гейт filesize; поднято с 400 как промежуточная мера решением #190 — более тесный лимит деформировал архитектуру, а не улучшал её). Число строк — не единственный контроль размера: гейт class_surface отдельно ограничивает составную публичную поверхность класса после наследования, которую пофайловый лимит видеть структурно не может. Хайлайты:

ФайлНазначение
project.pyТочка входа CLI, диспетчеризация
project_parser.pyДерево команд argparse
project_cli.py / _extra.py / _ops.pyCLI-обработчики (статус, задачи, сессии, память, шлюзы, навыки, FTS, метрики, поиск, события, исследования, аудит, run)
project_cli_doctor.py / _role.py / _stack.py / _verify.pyCLI-обработчики (doctor, roles, stacks, verify)
project_service.py + миксины service_*.pyБизнес-логика: задачи, знания, навыки, шлюзы, каскады, роли, верификация
service_verification.pyScoped pytest gate + verify cache (10 min TTL)
service_roles.pyГибридное хранение ролей (DB-метаданные + harness/roles/*.md)
service_stack_ops.pyStack scaffold, lint, diff, reset
project_backend.py + backend_*.pySQLite + FTS5 backend (WAL mode, schema v67)
backend_session_metrics.pyGap-based active-time computation
backend_tier_metrics.pycall_budget vs call_actual tier-метрики
backend_migrations.py / _legacy.py / _v*.pyМиграции схемы до v67; 34 version-модуля остаются для совместимости обновлений 1.11
project_config.py + default_gates.pyЗагрузчик конфигурации, настройка шлюзов, автовключение
gate_runner.py + gate_stack_dispatch.py + gate_test_resolver.pyScoped pytest mapping + dispatch
skill_manager.py + skill_repos.pyУстановка/удаление навыков из репозиториев
knowledge_db.py + knowledge_write.py + knowledge_read.pyОбщее локальное хранилище ~/.tausik-knowledge (--global у decide / memory add; входит в memory search)
publication_boundary.py + knowledge_export.pyЕдинственное место, где содержимое общего хранилища покидает машину (knowledge export --redacted)
cq_client.pyCross-project queue клиент
doc_extract.pymarkitdown интеграция
docs_lint.pyWarning-only stale-version линтер
plan_parser.pyПарсер markdown-планов для /run
model_routing.pyHelper выбора модели
ide_utils.pyОпределение IDE, пути, реестр
tausik_utils.py + tausik_version.py + project_types.pyХелперы, версия, типы
gen_doc_constants.pyТочка входа доковых проверок: --check, --write, перегенерация constants.json
doc_drift_common.pyОбщие таблицы регулярных выражений, цели сканов, текстовые помощники
doc_drift_scanners.pyШесть сканов дрейфа: версии, счётчики MCP, закрытые перечни, числа тестов и кода
doc_drift_tables.pyЧисловые ячейки колонок таблиц markdown и реестр их предметов
doc_drift_fixes.pyАвтопочинщик, который запускает --write
code_counts.pyСчитает состояние репозитория: хуки, стеки, роли, агенты ревью, скиллы
mcp_tool_counts.pyСчитает поверхность MCP, которую объявляет каждый сервер
audit_orphan_files.py / audit_stale_docs.py / audit_unused_python.py / audit_pytest_dedupe.pyStatic audit reports (review-only, v1.5)
project_cli_hygiene.pytausik hygiene archive / unarchive (мягкий архив старых done-задач и единственный путь обратно)
hooks/check_docs.pyPre-commit / CI wrapper для drift-проверки doc-constants (v1.5)

Начальная настройка (генерация) ​

ФайлСтрокНазначение
bootstrap.py~320Оркестрация: vendor sync, copy, generate
bootstrap_vendor.py~280Скачивание внешних навыков из GitHub (tarball)
bootstrap_copy.py~180Копирование навыков, скриптов, MCP в .claude/
bootstrap_config.py~70Конфигурация, стек-детекция
bootstrap_generate.py~300Генерация settings.json, CLAUDE.md, каталога навыков
analyzer.py~260Детекция расширяющих скиллов и обход дерева

MCP-сервер ​

ФайлНазначение
harness/claude/mcp/project/server.pyJSON-RPC stdio-сервер
harness/claude/mcp/project/tools.pycore tool definitions
harness/claude/mcp/project/tools_extra.pyрасширенные tool definitions (skills, gates, doctor, verify, roles, stacks)
harness/claude/mcp/project/handlers.pyТолько диспетчеризация: счётчик вызовов, handle_tool, слияние доменных таблиц
harness/claude/mcp/project/handlers_<домен>.pyОбработчики по доменам: task, session, status, knowledge, hierarchy, stack, role, verification, cq, skill, spec, adapt. Каждый модуль экспортирует <DOMAIN>_HANDLERS, handlers.py сливает их в _DISPATCH
harness/claude/mcp/project/handlers_render.pyОбщий рендер списков (render_list) — пустой результат обязан читаться как «ничего нет», а не как пустая строка

Полный MCP-surface: 147 project-инструментов (опциональный codebase-rag добавляет ещё 7; не в основном счёте).

ЦЕНА ЭТОЙ ПОВЕРХНОСТИ ПЛАТИТСЯ НА КАЖДОМ ХОДУ, И ОНА РАЗНАЯ ПО ХОСТАМ. Протокол MCP пересылает имя И схему каждого инструмента на каждом ходу, если клиент не откладывает схемы. Замер смены #229: сериализованные определения project-сервера весят 56 108 байт (порядка 14 000 токенов), одни имена — 3 201 байт (около 800 токенов), разница в 17.5 раза. Claude Code схемы откладывает и платит имена — это НАБЛЮДАЛОСЬ; хост без отложенной загрузки платит всё — это следует из протокола и здесь не наблюдалось.

Числа выше даны для чтения, а не как источник истины: источник — храповик mcp_surface в tausik/gates.json, который tests/test_mcp_surface_ratchet.py пересчитывает на каждом прогоне и краснит при росте. Так сделано потому, что предыдущая редакция этого числа сгнила ровно здесь: задача записала 117 инструментов и 44 501 байт в сессии #178, а к #229 стало 145 и 56 108 — рост на 24% и 26%, которого никто не заметил, пока число жило в тексте.

Контекстные заголовки чанков (codebase-rag) ​

Чанк, вырезанный из файла, перестаёт нести то, о чём файл был, и запрос, сформулированный в терминах документа, до такого пассажа не дотягивается. Поэтому каждый индексируемый чанк несёт короткий заголовок, который строит harness/claude/mcp/codebase-rag/rag_context.py: слова пути, символ, который чанк определяет, — или тот, внутри которого он находится, если это чанк-продолжение, — и строку-сводку самого файла.

Это contextual retrieval с изъятой из него моделью. Опубликованная техника просит LLM написать по предложению контекста на чанк; здесь заголовок берётся из метаданных, которые у индексатора уже есть, поэтому один и тот же вход даёт одни и те же байты, а индексация остаётся воспроизводимой и офлайновой.

Заголовок живёт в собственной индексируемой колонке (rag_chunks.context_prefix), а не в содержимом чанка: слово, присутствующее только в заголовке, находится — и в выдаче поиска не появляется. Индекс, собранный до v1.8, дорастает до новой раскладки при первом открытии: колонка добавляется, а FTS-таблица перестраивается из чанков, которые и есть источник истины.

Как это измерено, и чем измерить снова. Заголовки не «выглядят полезными» — их выигрыш посчитан воспроизводимым прибором scripts/rag_retrieval_bench.py (python scripts/rag_retrieval_bench.py [--limit N] [--json]). Он выводит запросы МЕХАНИЧЕСКИ из корпуса и выбирает их детерминированно, поэтому набор нельзя подогнать под лестный результат, а два прогона по одному дереву задают одни и те же вопросы. Меряет recall@K по КОНКРЕТНОМУ ЧАНКУ и держит два набора сразу: context — случай, ради которого заголовок заведён, и control — запросы из слов, уже лежащих в теле чанка, где заголовок помогать не должен и особенно не должен вредить. Результат при n=115: recall@3 в наборе context 0.4870 → 0.8348, в control 0.5739 → 0.6435; регрессии нет ни на одном K.

Поддержка разных сред разработки ​

Навыки, роли, стеки — общие для всех сред. MCP-серверы тоже: harness/claude/mcp/ — единственное каноническое дерево, и copy_mcp отдаёт его каждой среде, у которой нет своего (сегодня — всем). Отдельная копия под IDE была бы зеркалом, обречённым разъехаться: такое лежало в harness/cursor/ и удалено в v1.7.0.

harness/
├── skills/           # 13 core auto-deployed; внешние ставятся по одному
├── roles/            # 7 ролей (architect, developer, devops, qa, researcher, tech-writer, ui-ux)
├── stacks/           # Руководства по стекам
├── overrides/        # Переопределения для конкретных сред (claude/, cursor/, qwen/)
├── claude/mcp/       # MCP-серверы (project, codebase-rag) — канон для ВСЕХ сред
└── opencode/plugins/ # Плагин дисциплины QG-0 для OpenCode (tool.execute.before)

Среда (IDE) × Модель — две ортогональные оси ​

TAUSIK разделяет где он работает и какая модель отвечает:

ОсьЧто задаётЦель bootstrap --ideОпределение активной модели
claudeClaude Code (VSCode/CLI).claude/ + .mcp.jsonJSONL-транскрипт (поле model)
cursorCursor.cursor/ + .cursor/mcp.json—
qwenQwen Code.qwen/settings.json—
kiloKilo Code (аддон + CLI).kilo/kilo.jsonc и .kilocode/mcp.jsonenv KILO_MODEL / конфиг .kilo
opencodeOpenCode (SST)opencode.json + .opencode/plugins/—

Ось модели — это данные, а не код: scripts/model_profiles.py отображает семейства вендоров (claude, glm/z.ai) × ранги способностей → конкретные id моделей; переопределяется в .tausik/config.json, ключ model_profiles.families. Матрица маршрутизации выдаёт абстрактный ранг, активное семейство резолвит его в реальную модель — поэтому сессия на z.ai GLM уезжает к GLM-моделям без единой правки кода. См. Kilo + z.ai.

БД: Избранные таблицы (Schema v67) ​

ТаблицаНазначение
metaМетаданные (schema_version)
epicsЭпики
storiesСтори (→ epic)
tasksЗадачи (→ story, scope, defect_of, plan, AC)
sessionsСессии (start, end, summary, handoff)
memoryПамять проекта (pattern, gotcha, convention, context, dead_end)
decisionsАрхитектурные решения
eventsАудит-лог (gate_bypass, status_changed, claimed)
explorationsИсследования (time-boxed)
memory_edgesГрафовые связи между записями памяти и решениями
fts_tasksFTS5 полнотекстовый индекс по задачам
fts_memoryFTS5 индекс по памяти
fts_decisionsFTS5 индекс по решениям
task_logsСтруктурированные логи задач (phase, message)
fts_task_logsFTS5 индекс по логам задач
rolesРеестр ролей (гибрид: метаданные + harness/roles/{slug}.md)
session_activityPer-tool-call таймстемпы для gap-based active time
verification_runsVerify cache: file_hash + timestamp для QG-2 reuse (10 min TTL)

Шлюзы качества ​

gate_registry.py        → GATE_REGISTRY: одно объявление на встроенный гейт
                        → GateSpec(name, phase, default_config, impl)
                        → phase: scoped | post_scope
default_gates.py        → DEFAULT_GATES = универсальные (из реестра)
                                        ∪ stack-scoped (из stack_registry)
                                        ∪ post-scope (из реестра)
gate_runner.py          → run_gates(trigger, files)   [только фаза scoped]
                        → диспетч через GATE_REGISTRY[name].impl,
                          имя вне реестра → run_command_gate()
gate_post_scope.py      → run_post_scope_gates()      [фаза post_scope]
                        → verify_first, changelog + по строке в gate_runs
service_task.py         → _run_quality_gates() (вызывается из task_done)

Добавить встроенный гейт — это один GateSpec. До gate-registry-single-source требовалось четыре правки, а два post-scope гейта жили лишь в одном из четырёх мест: gates status их не перечислял, gates enable/disable до них не доставал, и они не писали строку в gate_runs — то есть НИЧТО не могло доказать, что QG-2-гейт отработал.

Scoped-гейты — (gate_config, files) -> (passed, output), судят объявленный скоуп задачи. Универсальные (всегда включены): filesize, class_surface, tdd_order, ruff, mypy, bandit, bootstrap_drift, memory_route, renar_drift_schema, renar_drift_provenance, cross_model_parity.

cross_model_parity спрашивает одно: не уехала ли возможность к одному хосту молча. Он ЗАПУСКАЕТ настоящие генераторы механизмов в чистое дерево и сравнивает хосты, делящие одну точку расширения: хуки claude против хуков qwen, плагины против плагинов. Сравнения ПОПЕРЁК форм нет — спрашивать, «не потерял ли Claude плагин OpenCode», значит задавать вопрос без смысла. Гейт НЕ требует одинакового поведения: у Cursor точки расширения нет вовсе, и равняться там не на что. Требуется, чтобы различие было НАЗВАНО, с причиной; объявление, которому больше не соответствует ни одно живое различие, отвергается так же громко, как необъявленное различие. Срабатывает только на правках слоя хостов (bootstrap/, scripts/hooks/, harness/opencode/) — гейт, спрашивающий про кроссмодельность на каждой задаче, становится налогом, а налог выключают.

bootstrap_drift проверяет три звена цепи «правка → развёртывание → вступает в силу»: scripts/ против развёрнутого профиля, harness/ против его разворота, и развёрнутый профиль против процесса, который из него работает (running_source_drift: снимок содержимого при старте процесса, сравнение на task-done). Долгоживущий MCP-сервер, под которым перезаписали профиль, исполняет старую копию; гейт отказывает закрытию и называет лекарство — перезапустить сервер или закрыть через CLI, который есть свежий процесс.

Гейт проверяется мутацией, а не тем, что он зелёный. Зелёный прогон доказывает лишь, что гейт не возразил, — не то, что он возразил бы хоть чему-то. Правило держит tests/test_gates_catch_their_violation.py: каждый гейт из gate_registry.GATE_REGISTRY стоит ровно в одной из двух таблиц. COVERED — гейт прогоняется через собственный impl_for реестра на ОБОИХ концах: настоящее нарушение, собранное под tmp_path, обязано вернуть failed, чистый вход, собранный так же, — passed (один конец без другого пуст: гейт, красный на любом входе, красную проверку проходит не хуже верного). EXCUSED — гейт, который из синтетического дерева не завести (привязан к сервису, вердикт внешнего инструмента, severity warn), с причиной и именами красного и зелёного теста в модуле, который его гоняет; имена сверяются с AST того модуля. Список закрыт: новый гейт без строки краснит ленту. Мутация не может остаться в дереве по построению — всё строится под tmp_path, и запись за его пределы во время прогона таблицы ловится перехватом open, sqlite3.connect и subprocess (три канала, которыми билдеры пользуются), а не снимком git status, который под xdist гонку с соседним воркером проигрывает.

class_surface — единственное исключение из «судят объявленный скоуп»: он игнорирует список файлов и мерит весь репозиторий (~0.65 с). Класс уезжает за лимит через свои базы, поэтому пофайловый прогон этого не увидит никогда — та же слепота, из-за которой модуль дорос до 406 строк, никого не заблокировав. Гейт ограничивает составную публичную поверхность класса после наследования, которую пофайловый filesize структурно видеть не может: god-объект, собранный из миксинов, держит каждый файл ниже строкового лимита. Они дополняют друг друга — «этот класс делает слишком много» и «этот файл слишком длинный, чтобы его читать» суть разные дефекты. Счёт объявлен нижней границей (AST, никогда import, чтобы гейт мог измерить ветку, которую ещё никто не читал), а известные превышающие классы держит baseline-храповик в tausik/gates.json, который может только сокращаться.

Post-scope гейты — принимают контекст закрытия и правят QG-2-отчёт: verify_first (обязателен свежий подписанный зелёный verify) и changelog (конвенция #275). get_gates_for_trigger их отфильтровывает, поэтому run_gates никогда не вызовет их с чужой сигнатурой.

Stack-scoped гейты: pytest, tsc, eslint, js-test, go-vet, go-test, golangci-lint, cargo-check, cargo-test, clippy, phpstan, phpcs, phpunit, javac, ktlint, ansible-lint, terraform-validate, helm-lint, kubeconform, hadolint.

Адаптация RENAR — advisory-first («лайт») ​

TAUSIK — лёгкий zero-dep фреймворк, поэтому RENAR (стандарт рассуждения/управления) внедряется advisory-first, а не тяжёлой обязательной церемонией. Адаптация поднимается по лестнице с явными условиями входа на каждую ступень:

СтупеньЧтоСтатус
1. АртефактыSPEC / ADAPT / conformance в SQLite-субстрате + one-way экспорт renar/done (RENAR-1)
2. Advisory-сигналыQG-0 выдаёт неблокирующий нудж, когда high-stakes задача (tier substantial/deep или complex) стартует без связанного SPEC и без ADAPT — gate_qg0_renar.renar_qg0_advisory, тоггл renar.qg0_advisory (по умолчанию вкл)done (1.5)
3. Хардгейт по доказательствуповысить конкретный advisory до блокирующего только когда реальный дефект упрётся в его отсутствие (аудит #91)2.0
4. RENAR-2 подписанный/неизменяемый ADAPTподпись ADAPT (ed25519) → tz_immutable=true + delta-ADAPT — необратимо, только по команде пользователя2.0

Философия: RENAR усиливает SENAR, делая интерпретацию видимой на естественном гейте (QG-0), не блокируя агента — fail-soft на advisory, fail-closed только на доказанных гейтах. Это осознанная политика лёгкой адаптации, а не «недоделанный RENAR».

Orchestrator-worker (авто-переключение модели через сабагентов) ​

Главная сессия — координатор (планирование, AC, ревью). Задачу complexity ≤ medium можно делегировать воркеру-сабагенту с рекомендованными моделью, reasoning effort и standard speed. Такую поверхность выбора модели дают Claude Code и Codex; TAUSIK хранит scaffolding/state делегирования, а сам spawn делает агент хоста. Маршруты Kilo/GLM остаются advisory, пока поверхность этого хоста не подтверждена живой проверкой.

ШагКоманда / механизм
Делегироватьtausik task delegate <slug> — пишет {рекоменд. модель, parent session} в meta kv (без миграции). complex отвергается (остаётся у координатора).
Handoff-контрактtausik task handoff <slug> — детерминированный JSON {slug, goal, acceptance_criteria, scope, scope_exclude, model, skills}; trimmed профиль WORKER_SKILLS (без plan/explore). Оркестратор передаёт его в Agent tool; воркер возвращает обратно (round-trip identity).
Распознавание in-sessiontask start делегированной задачи показывает worker mode (operating contract) и подавляет orchestrator-only баннер модели.
Scope hard-gateворкер ограничен scope — scope_write_gate блокирует edits вне scope_paths, а делегированная задача без scope блокируется до объявления (нет legacy fail-open для воркеров).
Summary-backtausik task summary-back <slug> "<summary>" [--gates …] — воркер возвращает структурный результат (в meta, виден в task show), чтобы координатор взял его без транскрипта воркера.

Состояние делегирования — CLI-first (без MCP, чтобы избежать doc-count drift) и целиком в таблице meta (delegation:<slug>, worker_summary:<slug>). Телеметрия маршрута отдельно хранит selected, applied, rejected и unavailable. Сам выбор ещё не означает применение: applied записывается, когда воркер входит в worker mode через task start. Работающая сессия координатора программно не переключается; QG-0/QG-2, доказательства и закрытие задачи остаются у координатора.

Hooks (anti-drift, см. hooks.md) ​

Все hook-файлы в scripts/hooks/ регистрируются через bootstrap/bootstrap_generate.py (Claude Code) и bootstrap/bootstrap_qwen.py (Qwen Code). Hook-скрипты non-blocking (exit 0), ошибки в stderr. Общие helper'ы в scripts/hooks/_common.py.

Memory Aggregates ​

service_knowledge_aggregates.py содержит чистые функции для re-injection памяти:

  • build_memory_block(be, ...) — компактный markdown (decisions + conventions + dead ends) ≤50 строк, вызывается из /start, /checkpoint, SessionStart hook
  • build_compact_memory_tail(be) — построчная выжимка, встраиваемая в динамический блок CLAUDE.md
  • build_memory_compact(be, last_n) — агрегация task_logs: фазы + топ-слова + топ-файлы

Обе выжимки спрашивают граф памяти через memory_supersedes.live_head: запись, которую отменило ЖИВОЕ ребро supersedes, не печатается, её строка достаётся следующей живой записи, а выжившая запись говорит (supersedes #N) на строке, которую занимает в любом случае. Скрывает только это отношение и только пока заменившая запись сама не заархивирована — иначе старая есть лучшее из оставшегося знания. Нечитаемый граф не отменяет ничего: выжимка вырождается в то, что печаталось до фильтра.

Аналогично scripts/model_routing.py + plugin_data.py — чистые модули, импортируемые из CLI/MCP handlers.

Prompt caching ​

TAUSIK опирается на автоматический prompt caching от Anthropic — это удерживает стоимость агентских прогонов в разумных границах. Сам фреймворк не делает API-вызовов (это делает Claude Code), но структура того, что TAUSIK кладёт в каждый ход, определяет: попадёт префикс в кеш или перебиллится заново. Кешируемая поверхность по приоритету:

ПоверхностьГде живётПочему кешируется хорошо
System prompt + схемы инструментовИнжектится Claude Code'ом из .claude/mcp/project/tools.py и tools_extra.pyИдентично между ходами в рамках сессии — самый длинный стабильный префикс
CLAUDE.mdКорень проектаЧитается раз за сессию и реинжектится; стабилен пока tausik_update_claudemd не перепишет dynamic-блок
Описания MCP-инструментовТе же tools.pyЛюбая правка инвалидирует кеш — изменение формулировки переписывает весь префикс
Skills (SKILL.md)harness/skills/<name>/SKILL.mdПодгружаются только при активации скилла

Что инвалидирует кеш в середине сессии. Любая правка перечисленных файлов между ходами переписывает префикс и заставляет следующий ход платить cache_creation_input_tokens вместо cache_read_input_tokens. Главный нарушитель — tausik_update_claudemd: его прогон в середине сессии переписывает dynamic-state блок (номер сессии, счётчики задач и т.д.), и весь префикс CLAUDE.md перекешируется. Зови его на границах сессии (/start, /checkpoint, /end), а не между рядовыми tool-вызовами.

Как проверить, что caching реально работает. Anthropic возвращает cache_creation_input_tokens (префикс только что записан) и cache_read_input_tokens (последующий ход попал в кеш) в usage-блоке каждого ответа. scripts/validate_prompt_caching.py парсит транскрипт Claude Code (JSONL) и выдаёт обе суммы + hit-rate:

bash
python scripts/validate_prompt_caching.py --auto
# или
python scripts/validate_prompt_caching.py path/to/transcript.jsonl

Exit code 0 = caching активен (cache_read_input_tokens > 0); 1 = префикс нестабилен (creation > 0, reads = 0); 2 = API вообще не вернул cache-поля. См. troubleshooting.md секцию «Prompt caching не активен» — типовые причины.

Тестирование ​

bash
pytest tests/ -v                    # все тесты (6096)
pytest tests/test_tausik_backend.py   # backend CRUD
pytest tests/test_tausik_service.py   # service logic
pytest tests/test_tausik_cli.py       # CLI smoke
pytest tests/test_gates.py          # quality gates + stack auto-enable
pytest tests/test_vendor.py         # vendor skills + persistence
pytest tests/test_graph_memory.py   # graph memory edges
pytest tests/test_mcp_integration.py # MCP handlers
pytest tests/test_senar.py          # SENAR compliance
pytest tests/test_e2e_workflow.py   # E2E workflow

См. Принципы тестирования — когда добавлять тесты, маппинг scoped pytest, анти-паттерны (в т.ч. копипаста без нового поведения).