Promoted .brainstorm/tdd-criteria.md to claude-skills/.wiki/concepts/tdd-criteria-design.md (commits a03d2804+2ad6f4ce+5fb648d4 in claude-skills repo). Created 4 tasks in claude-skills/.tasks/STATUS.md: - tdd-criteria-skill-write (ready) — write skills/tdd-criteria/SKILL.md - tdd-criteria-hermes-mapping (blocked by skill-write) — mapping.yaml entry - tdd-criteria-build-install (blocked by above two) — build.sh + install.sh - tdd-criteria-review (blocked-umbrella per §5) — code-review checkpoint Lead rationale (anti-vandalism / contract-vs-artefact) and 4+4 bright-line structure (Ironclad + Permissive accepted-risk zones) in design doc; runtime SKILL.md is implementation task.
23 KiB
TDD-критерии: когда можно без
Открыто: 2026-05-07 Status: brainstorm in progress
Постановка (от user)
Разработка через тесты показывает свою эффективность. Готов внедрить её почти во все задачи. Но нужно определить критерии, когда всё-таки можно без неё — иначе TDD превращается из инструмента в догму, и каждая задача начинает оплачивать налог TDD даже там, где он не окупается.
Ключевая опасность
«Carve-out для категории» легко превращается в «удобную лазейку». Если правило формулировать как «без TDD можно для exploratory» — каждая задача задним числом оказывается «exploratory». Критерий должен:
- Быть bright-line (а не «по ощущениям»)
- Принуждать к моменту осознанного выбора (не «по умолчанию проскочило»)
- Делать пропуск видимым (commit, код-ревью могут это проверить)
Открытые вопросы
- Какой реальный опыт стоит за «TDD показывает эффективность»? — нужен якорь, чтобы критерии калибровались на фактах, а не на мнениях
- Где живёт правило: global
~/.claude/CLAUDE.md(overridesuperpowers:test-driven-development) или проектные CLAUDE.md? - Как обеспечить аудит: явная маркировка в commit (
[skip-tdd: <reason>]), или достаточно ревью diff?
Рабочая гипотеза (моя стартовая позиция, до обсуждения)
TDD = default. Carve-outs допустимы по типу артефакта, не «по ощущению задачи».
Permissive (можно без TDD):
- Untestable surface: CSS/visual polish, layout, config-файлы, документация, prompt-инжиниринг
- Throwaway: явный spike/POC с обещанием выкинуть (или переписать с TDD при принятии)
- One-shot: миграции данных, ETL backfill, ad-hoc скрипты-однодневки
Ironclad (TDD обязателен):
- Bug fixes — всегда начинать с red-теста, воспроизводящего баг (никаких исключений: иначе не убедимся, что починили именно его)
- Pure logic, бизнес-правила, branching
- Публичные API / контракты
- Security, auth/authz, money/dates/identifiers
Anti-loophole:
- При пропуске TDD — явная маркировка в commit (
[skip-tdd: visual],[skip-tdd: spike]) - Spike → если код пошёл в продакшен, заводится тех-долг «backfill tests»
Trade-off
Маркировка [skip-tdd: ...] создаёт friction в каждом commit. Это и есть смысл — friction блокирует превращение carve-out в норму.
Обсуждение
Раунд 1 (2026-05-07 утро) — якорный вопрос обратно к user
Agent не пошёл сразу в формальное multi-persona meeting (нет scenarios/tdd-criteria.md, буфер ещё незрел) и не стал перечислять carve-outs в вакууме. Recommend-don't-menu: остаёмся в single-thread брейнсторме, потому что буфер сам залочен на якорном вопросе. Personas без factual anchor сгенерят гипотетику, потратят раунды.
Запрошено у user: какой проект / тип артефакта; что было до TDD vs после; контр-кейс, где TDD замедлил.
Раунд 2 (2026-05-07) — user указал на наблюдаемые симптомы + контекст «не все используют Claude»
User: «modules-db и pilonuxt буксуют, полны багов, не могут уйти в прод. Посмотри, в каких проектах TDD использовали в общих тасках» + «у нас есть claude-skills. Не все используют Клода».
Из второго: правило не должно быть Claude-specific. Локация в ~/.claude/CLAUDE.md (как было предложено в раунде 4) — ошибка. Правило — методология, не агент-инструкция.
Раунд 3 (2026-05-07) — cross-project research через projects-meta
Agent выполнил Step 0 freshness gate (meta_status → 5.4h cache → node sync.js → 13 проектов, 0 errors, freshly synced 2026-05-07T03:27Z), затем tasks_search "tdd" / "test" + tasks_get по modules-db, pilonuxt, books.
Поиск «tdd»: 0 хитов в активных задачах ни одного проекта. Поиск «test»: 1 хит (claude-skills/hermes-mvp-coverage — про smoke-test, не TDD).
books (контрастный кейс — фичи систематически закрываются, активные двигаются):
books-ops-mcpPhase 2 (CLOSED, прод): «3 unit-tested helpers (TDD) + integration tests via docker-compose + smoke 7/7». Helpers —enforceLimit,checkSql,capPayload(defence-in-depth для read-only SQL).books-analyst-mcpPhase 1.1: после real-API smoke вылез баг handler-обёртки → сначала написанhandler-body.test.js10 кейсов черезConfiguration.fetchApi-перехват, потом fix (de3360c). Классический red-test для bug-fix.analyst-report-formats(CLOSED):_test/11 unit-тестов для 5 pluggable formatters.- Закрытые крупные фичи:
pluggable-schedulerPhase 1-9,agenda-admin-ui,registry-gc,books-analyst-mcp. Активный затык —ozon-knowledge-base-ingest(anti-bot, не баги кода).
modules-db (буксует):
ai-fill: «❌ Phase 7 (Testing) — unit tests, smoke test» — тесты отложены ПОСЛЕ Phases 1-6. Test-after.fill-fields-ai-sdk-migration: SDK v5→v6 signature change, endpoint висит несколько дней без contract-теста.entity-drawer-refactor: vitest упомянут в shell-setup, но в Progress нет тестовых артефактов; Playwright smoke в Phase 5 (после refactor).hydration-mismatchшумит на всех страницах после миграции.
pilonuxt (буксует):
- 2 задачи в кэше.
design-system-cleanup: битые hex'ы, font preload — нет тестов и нечем их писать.snolla-contract-refresh(CLOSED): пересборка клиента к внешнему контракту, без тестов; открытый item — «pnpm buildзависает».
Раунд 4 (2026-05-07) — user сообщил n=1 + увольнение автора + «реши сам»
User: «Это программист выбирал, я не настаивал. Теперь уже не узнаем, он уволился. Давай ты сам порешаешь».
Что это меняет:
- Нельзя говорить «коллективный опыт books validates TDD» — n=1, автор недоступен, мотивация неизвестна.
- Опираться на: (а) observable evidence — где TDD применялся, фичи закрылись; где не применялся (modules-db/pilonuxt), фичи буксуют. (б) First principles — где TDD физически дешевле/дороже исходя из природы артефакта.
- Урок «программист уволился — его конвенции исчезли»: правило должно быть кодифицировано вне головы конкретного человека. Поэтому промоушен в общий artefakt — не опционально.
Agent в этом раунде предложил ~/.claude/CLAUDE.md как location → корректно отвергнуто на следующем раунде user-ом (Claude-only, не для всех агентов). Промежуточная попытка fallback'а на «shared agent-agnostic wiki via projects-meta knowledge_ingest» тоже устарела — финальная локация в раунде 5.
Раунд 5 (2026-05-07) — user поправил локацию: claude-skills, не правила
User: «У нас есть проект claude-skills. Изучи принципы. Туда его, а не в правила».
Agent изучил ~/projects/claude-skills/: README.md, .wiki/concepts/repo-layout.md, skills/project-discipline/SKILL.md, skills/recommend-dont-menu/SKILL.md, hermes/mapping.yaml. Принципы:
skills/<name>/— source of truth.SKILL.mdс frontmatter (name,version,description).dist/<name>.skill— committed архивы (для Claude).dist-hermes/<category>/<name>/— committed pre-converted Hermes-flavour tree.hermes/mapping.yaml— required entry per skill. Modes:auto(apply replace-rules),manual(Hermes-flavour rewrite),skip,pending.- Конкретный прецедент
recommend-dont-menu: rule переехал из~/.claude/CLAUDE.mdв skill ровно по причине «Moving to a skill makes it portable: cross-agent compatibility is explicit».
→ TDD-criteria — это policy skill, его место в claude-skills. Локация раунда 4 (~/.claude/CLAUDE.md) была повторением старой ошибки; правка применена в секции «Локация правила» ниже.
Раунд 6 (2026-05-07) — user сформулировал самый сильный аргумент: TDD как защита от vandalism
User: «возмущает, что несколько раз наблюдал, как эти уроды стирали целые простыни чужого кода и рапортовали "смотри, как я пиздато сделал, тут не получалось, но я почистил и стало пиздато". А потом по гиту собирали назад. Был бы код покрыт тестами — хуй бы они так сделали».
Это аргумент про существование поведения, не про корректность. Все мои четыре Ironclad-аргумента (bug fix, pure logic, third-party, security/money) — про защиту от неправильного поведения. Этот — про защиту от удалённого поведения.
Механизм: без теста контракт кода = «лежит в repo» (артефакт, не инвариант). Агент видит «грязно» → удаляет → коммитит «стало чище» → success-отчёт. Что код реализовывал реальное поведение — нигде не записано кроме самого кода, которого теперь нет. С тестом контракт = «X(Y)=Z» (инвариант). Удалить X = test fails = success невозможен. Тест — адвокат поведения, когда поведения уже нет.
Для контекста разработки с участием агентов (Claude, ChatGPT, будущих незнакомых) это означает: TDD-default — не «good practice», это единственная защита от well-intentioned destruction.
Что меняется в Final decision:
- Структура 4 Ironclad + 4 Permissive сохраняется (bright-line требует binary properties; этот аргумент — rationale, не критерий).
- Permissive переформулируется честнее: не «зоны где TDD не нужен», а «зоны принятого риска агентской дезорганизации». Ты явно соглашаешься: здесь агент может прибрать-удалить без signal-а, и ты accept-ишь recovery cost (eyeball-проверка для visual, throwaway по контракту для spike, one-shot после run-а уже неактуален, wrapper легко реконструировать).
- В Ironclad: recovery cost > defending cost, поэтому signal обязателен.
- В Final decision добавляется секция «Почему TDD-default — контракт, а не качество» как ведущий rationale.
- В будущем
claude-skills/skills/tdd-criteria/SKILL.mdэто становится ключевым «Why this exists».
Final decision (2026-05-07)
TDD = default. Bright-line carve-outs — четыре, по природе артефакта.
Почему TDD-default — это контракт, а не качество (ведущий rationale)
В контексте разработки с участием агентов (Claude, ChatGPT, нанятые программисты приходящие и уходящие) тест выполняет функцию, которой нет ни у документации, ни у код-ревью: он делает поведение инвариантом, а не артефактом.
- Без теста контракт = «код лежит в repo». Агент видит «грязно» → удаляет → коммитит «стало чище» → success-отчёт. Поведение существовало только в самом коде, который теперь удалён. Восстановление —
git revertпосле того как заметили; до тех пор silent regression. - С тестом контракт = «X(Y)=Z». Удалить X → test fails → pipeline red → success невозможен. Тест — адвокат поведения в момент, когда поведения уже нет.
Этот аргумент сильнее остальных четырёх (bug fix / pure logic / contract / security) потому что они про корректность поведения, а этот — про существование. Без него классические аргументы решают локальную задачу, но оставляют уязвимость к well-intentioned destruction. Он — основа TDD-default; всё остальное — частные случаи.
Permissive (зоны принятого риска агентской дезорганизации, skip + маркер)
Не «зоны где TDD не нужен», а зоны где принимаешь риск silent deletion и accept-ишь recovery cost. Вход в зону — явный, маркируется в commit subject.
| Категория | Триггер | Маркер | Recovery cost |
|---|---|---|---|
| Visual / config | CSS, layout, design tokens, .env.example, prompt-тексты, wiki, README |
[skip-tdd: visual] |
Eyeball на следующем render-е |
| Spike | Explicit POC с обещанием выкинуть | [skip-tdd: spike] |
Throwaway по контракту, deletion = no problem |
| One-shot | Миграции данных, ETL backfill, скрипты-однодневки | [skip-tdd: oneshot] |
После run-а уже неактуален |
| Wrapper | Транзитный код без логики (re-export, glue ≤10 строк) | [skip-tdd: wrapper] |
Reconstruct cost ≈ delete cost |
Ironclad (без исключений, без маркера — TDD обязателен)
- Bug fixes — red-test, воспроизводящий баг, до fix-а. Подтверждено
books-analyst-mcphandler-body.test.js10 кейсов черезConfiguration.fetchApi-перехват доde3360c. - Pure logic с bounded inputs — helpers, parsers, validators, formatters, любая
(known input) → (known output)без I/O. Подтвержденоbooks-ops-mcpPhase 2:enforceLimit/checkSql/capPayload. - Third-party contract consumption — SDK clients, API-обёртки. Контр-кейс прямо сейчас:
modules-db/fill-fields-ai-sdk-migrationвисит после AI SDK v5→v6 без contract-теста. - Security / auth / money / identifiers — без изменений vs стартовой гипотезы.
Anti-loophole
- Skip без категории не существует. Категория — одна из четырёх явных, не «других причин». Без маркера — нарушение.
- Spike survivor: если spike-код пережил merge в master → тем же merge-commit создаётся
[backfill-tests-<slug>]task в.tasks/STATUS.md. Иначе категория «spike» становится loophole. - Friction чисто социальная (видна в
git log --oneline); pre-commit hook опционален и обсуждается отдельно.
Локация правила
Корректировка после раунда 5 (user поправил).
Правило живёт как skill в ~/projects/claude-skills/skills/tdd-criteria/SKILL.md, не в ~/.claude/CLAUDE.md (Claude-only) и не в projects-wiki/concepts/ (пассивный artefact).
Обоснование:
claude-skills— единственный механизм с встроенным cross-agent rollout:install.sh→~/.claude/skills/,build-hermes.py+mapping.yaml→dist-hermes/<category>/<name>/. Любой будущий агент получает свой target.- Прямой прецедент —
recommend-dont-menu. Цитата из его SKILL.md: «Original rule lived in~/.claude/CLAUDE.mdas a per-machine instruction. Moving to a skill makes it portable: cross-agent compatibility is explicit.» Ровно тот же refactoring, который user уже один раз делал. - Skill — активный trigger-driven artefact: description-frontmatter + триггер-фразы → pull-механизм. Concept-страница в wiki — push-механизм («помни читать»), который не работает для cross-cutting policy.
Структура skill:
name: tdd-criteria,version: 0.1.0, multi-linedescription:с явными триггер-фразами- секции: «When this runs» (trigger phrases) → «Default mode (TDD-by-default)» → «Permissive carve-outs (4 категории + маркеры)» → «Ironclad (4 правила)» → «Anti-loophole» → «Out of scope» → «Why this exists»
- mapping.yaml entry: вероятно
mode: autoбез replace-rules (правило агент-агностично — pure policy, не упоминает Claude-tool-refs); categorysoftware-development. Пометитьpendingесли хочется отдельный Hermes-аудит.
Проектные CLAUDE.md могут расширять Permissive (например karu — проект чистого CSS) или расширять Ironclad через локальный override. Не могут сужать Ironclad. Триггер-line в шаблоне project-bootstrap не добавляем автоматически — skill активируется по description при релевантных запросах, форсированный pull в каждый проект — отдельное решение.
Trade-off (зафиксирован честно)
- Supportive evidence — n=1. books мог быть продуктивнее по причинам не связанным с TDD. Но направление эффекта (где помогало vs где не применялось) согласуется с first principles, инверсия маловероятна.
- Friction в UI-итерациях.
[skip-tdd: visual]50 раз подряд приweb-design-system— раздражает. Это и есть замысел: friction — fence, не bug. Снимать не раньше 2 недель usage. - Альтернатива «не кодифицировать вовсе» отвергнута: именно «не кодифицировано» привело к ситуации, что после ухода автора в books невозможно реконструировать его критерий. Уволенный человек — argument за кодификацию, не против.
Где остановились (2026-05-07)
- Final decision выше — готово к промоушену.
- Status: ready-for-promotion.
- Resume: запустить
meeting-room-promote-brainstorm→ routing:domain→ target project:claude-skills(неprojects-wikiчерез knowledge_ingest, не~/.claude/CLAUDE.md). Действие промоушена: (а) написать~/projects/claude-skills/skills/tdd-criteria/SKILL.mdнапрямую файлом + (б) добавить entry вhermes/mapping.yaml+ (в)bash scripts/install.sh tdd-criteria+bash scripts/build.sh tdd-criteria+python scripts/build-hermes.py+ (г) commit (push по grant-у). Импл-таски вclaude-skills/.tasks/: (1) написатьSKILL.mdс разделами When/Default/Permissive/Ironclad/Anti-loophole/Why; (2)mapping.yamlentry (mode: auto,category: software-development, без replace-rules — правило агент-агностично); (3) install + build + build-hermes; (4) опционально pre-commit hook на[skip-tdd: <one-of-four>](отдельная таска, не блокер). Review-таскаtdd-criteria-reviewсоздаётся автоматически скилом промоушена.