Files
discussions/.archive/2026-05-07-tdd-criteria.md
vitya 8c76244c01 Promote tdd-criteria → claude-skills wiki + 4 tasks; archive buffer
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.
2026-05-07 07:04:22 +03:00

23 KiB
Raw Blame History

TDD-критерии: когда можно без

Открыто: 2026-05-07 Status: brainstorm in progress


Постановка (от user)

Разработка через тесты показывает свою эффективность. Готов внедрить её почти во все задачи. Но нужно определить критерии, когда всё-таки можно без неё — иначе TDD превращается из инструмента в догму, и каждая задача начинает оплачивать налог TDD даже там, где он не окупается.

Ключевая опасность

«Carve-out для категории» легко превращается в «удобную лазейку». Если правило формулировать как «без TDD можно для exploratory» — каждая задача задним числом оказывается «exploratory». Критерий должен:

  1. Быть bright-line (а не «по ощущениям»)
  2. Принуждать к моменту осознанного выбора (не «по умолчанию проскочило»)
  3. Делать пропуск видимым (commit, код-ревью могут это проверить)

Открытые вопросы

  • Какой реальный опыт стоит за «TDD показывает эффективность»? — нужен якорь, чтобы критерии калибровались на фактах, а не на мнениях
  • Где живёт правило: global ~/.claude/CLAUDE.md (override superpowers: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-mcp Phase 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-mcp Phase 1.1: после real-API smoke вылез баг handler-обёртки → сначала написан handler-body.test.js 10 кейсов через Configuration.fetchApi-перехват, потом fix (de3360c). Классический red-test для bug-fix.
  • analyst-report-formats (CLOSED): _test/ 11 unit-тестов для 5 pluggable formatters.
  • Закрытые крупные фичи: pluggable-scheduler Phase 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 обязателен)

  1. Bug fixes — red-test, воспроизводящий баг, до fix-а. Подтверждено books-analyst-mcp handler-body.test.js 10 кейсов через Configuration.fetchApi-перехват до de3360c.
  2. Pure logic с bounded inputs — helpers, parsers, validators, formatters, любая (known input) → (known output) без I/O. Подтверждено books-ops-mcp Phase 2: enforceLimit/checkSql/capPayload.
  3. Third-party contract consumption — SDK clients, API-обёртки. Контр-кейс прямо сейчас: modules-db/fill-fields-ai-sdk-migration висит после AI SDK v5→v6 без contract-теста.
  4. 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.yamldist-hermes/<category>/<name>/. Любой будущий агент получает свой target.
  • Прямой прецедент — recommend-dont-menu. Цитата из его SKILL.md: «Original rule lived in ~/.claude/CLAUDE.md as 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-line description: с явными триггер-фразами
  • секции: «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); category software-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.yaml entry (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 создаётся автоматически скилом промоушена.