diff --git a/.archive/2026-05-07-tdd-criteria.md b/.archive/2026-05-07-tdd-criteria.md new file mode 100644 index 0000000..ad30138 --- /dev/null +++ b/.archive/2026-05-07-tdd-criteria.md @@ -0,0 +1,197 @@ +# 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: ]`), или достаточно ревью 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//` — source of truth. `SKILL.md` с frontmatter (`name`, `version`, `description`). +- `dist/.skill` — committed архивы (для Claude). `dist-hermes///` — 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-]` 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///`. Любой будущий агент получает свой 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: ]` (отдельная таска, не блокер). Review-таска `tdd-criteria-review` создаётся автоматически скилом промоушена. diff --git a/.wiki/log.md b/.wiki/log.md index 7ddc022..78c8483 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -28,3 +28,6 @@ Events: `started`, `promoted`, `registered`, `archived`. 2026-05-06 designed hermes-skills-rollout (.brainstorm/hermes-skills-rollout.md — domain spec, target promote: claude-skills) 2026-05-06 promoted hermes-skills-rollout → claude-skills/.wiki/concepts/hermes-skills-rollout-design.md (commits b3dc1251+5ae14fd6+0f65e057); tasks created: claude-skills#hermes-converter-mvp (ready), claude-skills#hermes-flavour-mcp-setups (blocked), claude-skills#hermes-installer-skill (blocked), claude-skills#hermes-mvp-coverage (blocked), claude-skills#hermes-converter-ci (blocked, deferred), common#tasks-close-normalize-body (ready, discipline pre-req), claude-skills#using-tasks-close-coverage-gate (ready, discipline pre-req) 2026-05-06 archived hermes-skills-rollout → .archive/2026-05-06-hermes-skills-rollout.md +2026-05-07 designed tdd-criteria (.brainstorm/tdd-criteria.md — domain spec, target promote: claude-skills; 6-round arc with cross-project research via projects-meta) +2026-05-07 promoted tdd-criteria → claude-skills/.wiki/concepts/tdd-criteria-design.md (commits a03d2804+2ad6f4ce+5fb648d4); tasks created: claude-skills#tdd-criteria-skill-write (ready), claude-skills#tdd-criteria-hermes-mapping (blocked), claude-skills#tdd-criteria-build-install (blocked), claude-skills#tdd-criteria-review (blocked, umbrella per §5) +2026-05-07 archived tdd-criteria → .archive/2026-05-07-tdd-criteria.md