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

198 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.yaml``dist-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` создаётся автоматически скилом промоушена.