local-qa · git:20260924.2784cb2 · 2026-09-24 · sha256 b8470efe325fe773
local-qa git:20260924.2784cb2A
Immutable. This exact content never changes and is served at /api/v1/blob/b8470efe325fe773.
--- name: local-qa description: > Ворота ПОСТОЯННОЙ ЖИЗНИ детали: у каждой запчасти, которую мы изобрели / починили / перепочинили / подкорректировали, обязаны появиться два спутника — ТЕСТ, который гоняет регресс-сетка, и ПАСПОРТ, по которому её починит слабейший починщик. Триггеры: "/qa", "полный QA", "сделай QA", "покрой тестами", "напиши тест на", "паспорт детали", "документация на деталь", "обнови документацию", "тесты написаны?", "документация есть?", "cover with tests", "write a passport". Делает 8 шагов: (0) RECALL и границы детали; (1) прогон /tt, если ещё не было (горячая проверка — не дублируем); (2) РЕПРО-тест, который КРАСНЕЕТ на баге, если деталь чинили; (3) написать тест по чек-листу 10 классов (встроенный `--selftest` или файл `_test_X.py`); (4) доказать, что СЕТКА его видит и гоняет — «тест есть» ≠ «тест зелёный»; (5) сломать деталь нарочно и убедиться, что тест краснеет (тест, ни разу не покрасневший, не тест); (6) паспорт по единому шаблону + обновление при каждой перепочинке; (7) видимость — карта покрытия, MOC контура, пер-машинность отчёта; (8) вердикт числом из сетки, а не словом «покрыто». НЕ ДУБЛЬ `/tt`: `/tt` = «работает ли то, что мы только что собрали» (разово, горячо); `/qa` = «заметим ли мы, когда оно сломается, и сможет ли Антон починить это без меня» (навсегда). Канон: `reglament-test-i-dokumentaciya-na-kazhduyu-zhivuyu-detal` (+ поправки 28.07 и 29.07), `reglament-tri-pravila-protiv-[человек]-kachestva`, [[coverage-and-docs-every-part]], [[repairability-first-how-we-build]], [[fix-root-cause-not-symptoms]], [[silent-degradation-vs-crash]], [[test-after-build-skill]]; движки: `regress_run.py`, `coverage_map.py`, `part_passport.py`. permissions: [filesystem, shell] risk_level: shell-local processes_untrusted_data: false disable-model-invocation: false origin: [машина флота] related: reglament-test-i-dokumentaciya-na-kazhduyu-zhivuyu-detal --- # /qa — тест и паспорт на каждую деталь > 🧒 **При докладе Антону:** заверши блоком «Простыми словами» ([[eli5-always]]). Только ему — не внутри паспортов. **Когда зову:** родилась НОВАЯ деталь · починил старую · чиню ВТОРОЙ раз ту же (перепочинка) · подкрутил поведение. Любой из четырёх случаев = деталь меняется, значит её спутники устарели. **Граница с соседями (не дублируем, каждый уровень ссылается вниз — АК-47):** | | вопрос | когда | живёт сколько | |---|---|---|---| | `/tt` | работает ли то, что мы только что собрали? | сразу после сборки, горячий контекст | один прогон | | **`/qa`** | **заметим ли, когда сломается, и починит ли это Антон без меня?** | **перед тем как отпустить деталь в жизнь** | **навсегда: тест в сетке + паспорт** | | `/retro` | что мы вообще насобирали за сессию | конец сессии | аудит строки «покрыто? ✅/❌» | --- ## Шаг 0 — RECALL и границы детали - Что именно правили: файл, зона (`~/.claude/scripts` · `$IMPORTS_ROOT` · `$MACHINE_BUS_DIR/_engine` · скилл · хук). - Деталь уже известна системе? `grep -n "<имя>" 00-System/Coverage-Map.md` и `ls 00-System/Parts/*/<имя>.md`. - Есть ли уже тест/паспорт — их **обновляем**, а не пишем второй ([[«один источник, ноль копий»|coverage-and-docs-every-part]]). - Деталь МЁРТВАЯ (никто не ссылается, не трогали 30 дней) и мы её не оживляем → **не покрываем, а предлагаем снести** (`00-System/Dead-Code-Candidates.md`). Док, который врёт, дороже отсутствующего. - ⚠️ НОВАЯ деталь покрывается ВСЕГДА, безусловно — «подождём, приживётся ли» запрещено (anton 28.07). ## Шаг 1 — `/tt` прогнан? Нет → сперва `/tt` (живой прогон + внешний ломатель Codex/Grok/Gemini). `/qa` не заменяет горячую проверку и не повторяет её. ## Шаг 2 — Чинили? Сперва тест, который КРАСНЕЕТ Порядок при починке и особенно при **перепочинке**: 1. Написать проверку, воспроизводящую баг **на текущем сломанном коде** → она обязана упасть. Не упала — ты не понял баг, лечишь не то. 2. Только потом чинить корень ([[fix-root-cause-not-symptoms]]). 3. Прогнать снова → зелёный. Проверка остаётся в сетке навсегда как регрессия. **Чиню второй раз ту же деталь** = сигнал, что первый фикс лечил симптом. Тогда обязательно: строка в паспорт в раздел «что ломается чаще всего» + вопрос «какой КЛАСС я закрываю?» (одна деталь чинится дважды → скорее всего болеют и соседи по контуру). ## Шаг 3 — Написать тест: чек-лист 10 классов **Куда:** встроенный `--selftest` внутри самой детали — дефолт (едет вместе с деталью на все машины, невозможно «забыть доставить»); отдельный `_test_<имя>.py` — когда нужны тяжёлые фикстуры или деталь нельзя дёрнуть флагом. `.ps1` → `-SelfTest` + `Invoke-SelfTest`. **Правила прогона:** только фейковые данные во временной папке (`tempfile.TemporaryDirectory`), ни одного живого файла флота; ни одной живой отправки наружу (транспорты — заглушками); встроенный selftest обязан укладываться в **60 секунд** (лимит сетки), поэтому реальные таймауты не «выжидаем», а временно подменяем константы модуля. **Чек-лист (проходить по всем десяти, отсутствие пункта — осознанное решение, а не забывчивость):** 1. **[человек] путь** — делает то, ради чего написана. 2. **Второй актор** — параллельный писатель/процесс: ничего не потеряно (гонка настоящими процессами, не только в голове). 3. **Повтор** — идемпотентность: второй одинаковый вызов не плодит дубль. Проверять по РЕАЛЬНО записанному маркеру, а не по тому, что «мы вроде бы пишем». 4. **Мусорный вход** — пусто, без обязательной шапки, кривой путь, отсутствующая зависимость. 5. **Отказ громкий** — при отказе exit-код ненулевой И состояние НЕ изменилось (проверить размер/хэш файла после отказа). 6. **Exit-коды** — каждый описанный код воспроизведён тестом. Код, который никто не проверил, врёт первым. 7. **Кодировки и пути** — BOM, CRLF, UTF-16, `C:` vs `/Users`, `$VARS` вместо хардкода машины. 8. **Слой видимости** — доказательство работы существует: лог, счётчик, строка. «Сработало, но не видно» = ❌ ([[silent-degradation-vs-crash]]). 9. **Ноль-результат ≠ здоровье** — пустая папка / нет данных / нечего сторожить обязаны давать ТРЕВОГУ, а не тихий зелёный ноль. 10. **Тихая деградация** — упавшая рельса/недоставленное сообщение не превращаются в `exit 0`. ## Шаг 4 — Доказать, что СЕТКА его гоняет Тест, который никто не запускает, не существует (канон 29.07): ```bash python3 ~/.claude/scripts/regress_run.py --list | grep -i "<имя детали>" ``` Не нашёлся → **корень чинить в сетке, а не в своей памяти**: деталь лежит в зоне, которую сетка не обходит (так `[шина]/_engine` был невидим до 29.07), либо имя файла попало в исключения (`daemon`, `server`, `watcher_loop`, `listener`), либо встроенный selftest не распознан (нужны И флаг `--selftest`, И `def selftest`). ## Шаг 5 — Сломать нарочно: тест обязан покраснеть Тест, который **ни разу не был красным**, не тест, а украшение. Испортить деталь на минуту (убрать гард, вернуть старую строку) → прогнать → убедиться, что покраснел именно тот пункт → вернуть. Если с первого написания всё зелёное — почти всегда проверяется не то. ## Шаг 6 — Документация: docstring в коде, паспорт только для меж-файлового ⭐ **Поправка канона 07.08 (§5.8, вышла ПОСЛЕ рождения скилла): у Python-детали основной док = docstring В САМОМ ФАЙЛЕ, отдельный md-пересказ одного файла ⛔.** Docstring несёт: назначение · вход/выход · кто дёргает · рельса (чей оплаченный бак жжёт) · updated-дата; правка кода = правка docstring **тем же коммитом**; тест назван в docstring. Паспорт в `00-System/Parts/<контур>/` пишется только когда есть **МЕЖ-файловая картина** (деталь + сторож + промпты + launchd — как связаны и кто кого будит); пересказывать в нём содержимое одного файла — дубль, который разъедется. Генератор паспортов: `python ~/.claude/scripts/part_passport.py` (ест `coverage_map.py --dump-rows`); руками пишутся только поля, которых машина не знает. Поля (docstring — обязательные; паспорт — когда картина шире файла): - **Что делает** — одной фразой, без жаргона; - **Вход / Выход** — аргументы и что реально появляется на диске/экране; - **Кто дёргает** — рутины, промпты, другие детали (иначе паспорт не ответит на «а кто её вызывает?»); - **Зависит от**; - **⭐ Что ломается чаще всего** — руками, из РЕАЛЬНЫХ сбоев, а не из фантазии; - **⭐ Как понять, что сломалась** — exit-коды, строка в логе, отсутствие heartbeat, счётчик; - **⭐ Как починить руками** — конкретные команды, первым делом прогон selftest; - **Тест** — путь, число проверок, что именно они сторожат; - **Граница** — чего деталь НЕ делает (тут рождаются ложные ожидания). **Обновление дока = часть починки, а не «потом».** Поменял поведение/флаг/exit-код → правишь docstring (и паспорт, если он есть) в том же заходе. Поймал новый сбой → он дописывается в «что ломается чаще всего» вместе с лечением. Перепочинка начинается с чтения дока: если он описывает старое поведение, ты чинишь по вранью. ⚠️ MOC контура и общие реестры — **дописываем строку, не перегенерируем файл целиком** ([[reglament-obshchiy-fayl-nikogda-ne-perepisyvay-tselikom]]). ⚠️ **Паспорт ездит дальше, чем движок.** Паспорта лежат в волте (все 6 узлов), а детали — в `~/.claude/scripts` (receive-only, кладёт хаб). Поэтому на узле легко встретить документацию детали, которой тут нет (поймано 29.07: `have.py part_passport.py` → «✅ ЕСТЬ» и показал `.md`, а самого генератора на [машина флота] нет). Два следствия: (1) в паспорте пишем РЕАЛЬНЫЙ путь и, если деталь hub-only, говорим это прямо, а не оставляем `[путь владельца]`; (2) прежде чем сказать «движка нет» или отдать работу другому узлу — `python3 ~/.claude/scripts/have.py <имя>`, и проверить, что совпадение не документация ([[text-detectors-overcount-use-ground-truth]], гейт доставки `lint_script_delivery.py`). ## Шаг 7 — Видимость и пер-машинность - Карта покрытия: `python3 ~/.claude/scripts/coverage_map.py --json` — деталь обязана числиться живой, с тестом и доком. - Строка в MOC контура + счётчик деталей обновлён. - ⚠️ Отчёт о прогоне — **пер-машинный**. Общий файл, куда пишут все узлы, теряет чужие результаты молча (замер 28–29.07: ноут написал в 16:38, хаб затёр в 18:28, красные тесты ноута исчезли). Общий допустим только как сводка, пересобираемая из пер-машинных файлов. - Деталь живёт в синкаемой зоне → доставка по [[reglament-lyubaya-pochinka-raskatyvaetsya-na-ves-flot]]: пакет в `_transit`, `deploy_register.py --tier 1` с машинными apply/verify, табло `fleet_fix_audit.py`. `~/.claude/**` у пиров receive-only — правку туда кладёт ХАБ, локальная будет откачена. ## Шаг 8 — Вердикт ЧИСЛОМ ```bash python3 ~/.claude/scripts/regress_run.py # ⚠️ не на бегу: полный прогон = минуты ``` В отчёте Антону: **сколько зелёных / сколько красных / что именно красное**. Формулировки «тесты написаны», «покрыто», «документация есть» без числа из сетки — запрещены, это то же самое, что «раскатано» без таблицы применения. - ✅ **PASS** — тест в сетке, зелёный, хоть раз краснел; паспорт с тремя ручными полями; деталь видна в карте покрытия. - ⚠️ **PARTIAL** — что-то из этого отсутствует; назвать поимённо и завести задачу. - ❌ **FAIL** — тест не гоняется сеткой ИЛИ паспорт описывает не то поведение. ## Границы - Markdown-оркестратор, своего движка НЕ заводит: гоняет `regress_run.py`, `coverage_map.py`, `part_passport.py`. (Новый движок = новая деталь, которой снова нужны тест и паспорт — так растёт долг.) - Не покрываем мёртвое: его сносим списком через Антона (снос необратим). - Секреты и содержимое `secrets/` в паспорта не попадают. - Публичная документация — отдельный контур (Mission-2) и отдельный гейт согласования.