docs(близнец): найден — задваивается любая команда git, а не коммит; правила 251-252

Находка М-408. Охота, шедшая со смены 21, закрыта.

Открылось не поиском. Смена 29 по своей ошибке сделала пробный коммит и откатила его
одной командой `git reset --soft HEAD~1` — голова уехала на ДВА коммита назад, и работа
целой смены осталась в индексе вне истории. Журнал ссылок показал две записи
`reset: moving to HEAD~1` подряд при одной поданной команде.

Проверено вторично, на другой команде: `git reset --soft <номер>` подан один раз —
в журнале снова ДВЕ записи. Задвоение не случайность и не разовый сбой среды.

ЧЕМ ЭТО ОБЪЯСНЯЕТ ПРЕЖНЕГО БЛИЗНЕЦА ЦЕЛИКОМ. Смены 21-29 искали его у коммита и
описывали так: «второй коммит с тем же заголовком, внутри один docs/observer/STATUS.md».
Теперь ясно: задваивается любая команда git, а между двумя заходами успевает отработать
хук status-md-generator — он переписывает STATUS.md и оставляет его в индексе. Второму
заходу коммита есть что коммитить: ровно этот один файл.

⇒ Лечение, найденное сменами вслепую («снимать STATUS.md из индекса ПЕРЕД коммитом»),
работало по правильной причине, но закрывало следствие. Двенадцать чистых коммитов
подряд — не отсутствие близнеца, а ОБЕЗОРУЖЕННЫЙ близнец. Правило снятия — в силе.

251. ЛЮБАЯ КОМАНДА GIT МОЖЕТ ИСПОЛНИТЬСЯ ДВАЖДЫ. ЗАДАВАЙ ЕЁ ТАК, ЧТОБЫ ВТОРОЙ РАЗ
НИЧЕГО НЕ МЕНЯЛ. Откат — только абсолютным адресом, никогда `HEAD~1`. У коммита
задвоение безвредно; у относительного отката оно съело работу целой смены.
252. ОТЧЁТ КОМАНДЫ О САМОЙ СЕБЕ ДОКАЗАТЕЛЬСТВОМ НЕ ЯВЛЯЕТСЯ. `git log` внутри той же
строки показал верную голову — второй заход случился ПОЗЖЕ. Перемеряй следующей командой.

Своя четвёртая ошибка за смену, названная честно: пробный коммит был сделан на
предположении «коммитить нечего» вместо замера. Хук уже положил STATUS.md в индекс.
Ошибка вскрыла близнеца — но ошибкой быть не перестала.

🪤 Попутно: ловушка GIT_TRACE2_EVENT на боевом коммите файла не создала, причина НЕ
УСТАНОВЛЕНА. Замерено, что сама она исправна: пишет на `git status`, на `git commit`
и в связке с `rm`. Не гадаю; второй датчик (история, `sort | uniq -d`) дал тот же ответ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-07 07:53:47 +03:00
parent 32770b26d2
commit 5b909844e2
2 changed files with 104 additions and 10 deletions
@@ -11051,6 +11051,63 @@ const s = `ушло ${dollary(x)} денег`; → после чистки:
🟢 **Заведён сторож на класс** `utverzhdenie-o-kode-ne-po-syromu.test.mjs` — он же
стережёт единственность реза.
---
## М-408 (смена 29). 🔴🔴🔴 БЛИЗНЕЦ НАЙДЕН. ОН НЕ У КОММИТА — ОН У ЛЮБОЙ КОМАНДЫ GIT
**Как открылся.** Не поиском. Я по своей ошибке сделал пробный коммит, откатил его одной
командой `git reset --soft HEAD~1` — и через минуту обнаружил, что **голова уехала на два
коммита назад**, а работа целой смены лежит в индексе без коммита.
**Журнал ссылок (`git reflog`) — вот доказательство, и оно однозначное:**
```
cc5738778 HEAD@{0}: reset: moving to HEAD~1 ← ВТОРАЯ. Её я не давал
32770b26d HEAD@{1}: reset: moving to HEAD~1 ← моя, одна-единственная
b8c37e64c HEAD@{2}: commit: проба
32770b26d HEAD@{3}: commit: fix(ночь): рез примечаний ел живой код
```
**Проверено вторично, на другой команде.** Возвращая голову, я дал
`git reset --soft 32770b26d` — тоже ровно один раз. В журнале снова **две** записи
`reset: moving to 32770b26d`. Задвоение не случайность и не разовый сбой среды.
### 🔑 Чем это объясняет всю прежнюю охоту за близнецом
Смены 21–29 искали близнеца **у коммита** и описывали его так: «после каждого `git commit`
рождается второй коммит с тем же заголовком, а внутри — **один файл `docs/observer/STATUS.md`**».
Теперь понятно всё до конца:
1. Задваивается **не коммит, а любая команда git** — она исполняется дважды.
2. Между двумя заходами успевает отработать хук `status-md-generator`: он **переписывает
`docs/observer/STATUS.md` и оставляет его в индексе**.
3. Второму заходу коммита есть что коммитить — ровно этот один файл. Отсюда близнец
с тем же заголовком и одним файлом внутри.
⇒ **Лечение, найденное сменами вслепую («снимать `STATUS.md` из индекса ПЕРЕД коммитом»),
работало по правильной причине**, но закрывало только следствие: второму заходу нечего
коммитить, и он тихо падает с «нечего фиксировать». Двенадцать чистых коммитов подряд —
не отсутствие близнеца, а **обезоруженный близнец**.
### 🔴 И почему на этот раз он ударил, а раньше нет
Потому что `reset --soft HEAD~1` — команда **относительная**. Два исполнения уносят голову
не на один шаг, а на два. У коммита задвоение было безвредно; у отката — съело работу целой
смены. Спасло только то, что коммит остался в журнале ссылок и его удалось вернуть.
**251. 🔑🔑 ЛЮБАЯ КОМАНДА GIT МОЖЕТ ИСПОЛНИТЬСЯ ДВАЖДЫ. ЗАДАВАЙ ЕЁ ТАК, ЧТОБЫ ВТОРОЙ РАЗ
НИЧЕГО НЕ МЕНЯЛ.** Откат — только **абсолютным адресом** (`reset --soft <номер коммита>`),
никогда `HEAD~1`. Абсолютная команда, исполненная дважды, даёт тот же итог; относительная —
уезжает вдвое дальше. Проверено на обеих: `reset --soft 32770b26d` дважды — голова на месте.
**252. 🔑 ПОСЛЕ ЛЮБОЙ КОМАНДЫ, МЕНЯЮЩЕЙ ГОЛОВУ, ПЕРЕМЕРЬ ГОЛОВУ ОТДЕЛЬНОЙ КОМАНДОЙ.**
Мой `git log` внутри той же строки показал верную голову — второй заход случился **позже**,
уже после того, как команда отчиталась. 🔑 Отчёт команды о самой себе доказательством
не является; доказательство — замер следующей командой.
---
🔴 **Своя ошибка, названная честно.** Семь мест в `cli.test.mjs` правил не через Edit,
а программой из-под оболочки — ровно то правило затеи, что нарушила смена 28 и о чём
предупредила в передаче. Проверил сразу же: правка дожила, все семь на месте. Но правило
@@ -203,10 +203,10 @@
| Тел у реза примечаний было / стало | **10 / 1** |
| Живых ночей | **три**, все три удались |
| Живых рождений за смену 29 | **ноль** — работали руками надзирателя |
| Новых правил | **247, 248, 249, 250** (4), свод вырос до **250** |
| Находок метода | **М-407** |
| Новых правил | **247252** (6), свод вырос до **252** |
| Находок метода | **М-407, М-408 (близнец найден)** |
| Решений владельца | **176** живых (новых за смену 29 нет) |
| 🔴 Ошибок надзирателя | **три, все названы ниже** |
| 🔴 Ошибок надзирателя | **четыре, все названы ниже** |
🔴 **Свои ошибки за смену 29, названные честно:**
@@ -216,6 +216,11 @@
2. **Мои подложки были зелены на сломанном резе** — не хватало пары знаков (правило 248).
3. **Первый вид сторожа поймал сам себя**, а второй перепрыгивал через отрицание и врал
номером строки. Оба поймал не я, а прогон.
4. 🔴🔴 **Сделал пробный коммит, решив, что коммитить нечего** — а хук уже положил
в индекс `STATUS.md`, и коммит состоялся. Откатывая его относительной командой,
потерял голову на два шага назад. **Работа целой смены висела вне истории**, вернул
через журнал ссылок. 🔑 Именно эта ошибка и вскрыла близнеца — но ошибкой быть
не перестала: «коммитить нечего» надо было ЗАМЕРИТЬ, а не предположить.
---
@@ -247,22 +252,54 @@
### 🔴 Что осталось незакрытым
**0. 🔴🔴 БЛИЗНЕЦ: причина названа, ловушка стоит.**
🔑 **За смены 24–29 ловушка стояла на многих коммитах подряд — близнец не родился ни разу.**
Ставь на каждый свой коммит и дальше:
**0. 🔴🔴🔴 БЛИЗНЕЦ НАЙДЕН. ОХОТА, ШЕДШАЯ С 21-й СМЕНЫ, ЗАКРЫТА.**
Он **не у коммита**. Задваивается **любая команда git** — она исполняется дважды.
Открылось не поиском: смена 29 откатила свой пробный коммит одной командой
`git reset --soft HEAD~1` — и голова уехала **на два коммита назад**, а работа целой
смены осталась в индексе без коммита. Журнал ссылок показал две записи вместо одной.
Проверено вторично на `reset --soft <номер>` — снова две. Не случайность.
🔑 **Чем это объясняет прежнего близнеца целиком:** между двумя заходами успевает
отработать хук `status-md-generator` — он переписывает `docs/observer/STATUS.md`
и оставляет его в индексе. Второму заходу коммита есть что коммитить: ровно этот
один файл. Отсюда и близнец — тот же заголовок, внутри один `STATUS.md`.
⇒ Лечение, найденное сменами вслепую («снимать `STATUS.md` из индекса **ПЕРЕД** коммитом»),
**работало по правильной причине**, но закрывало следствие. Двенадцать чистых коммитов —
не отсутствие близнеца, а **обезоруженный близнец**. Правило снятия STATUS.md — в силе.
> 🔴🔴 **ПРАВИЛО 251, ГЛАВНОЕ ИЗ НОВЫХ: ОТКАТ — ТОЛЬКО АБСОЛЮТНЫМ АДРЕСОМ.**
> ```sh
> git reset --soft <номер коммита> # 🟢 дважды — тот же итог
> git reset --soft HEAD~1 # 🔴 дважды — уедешь на ДВА назад
> ```
> У коммита задвоение безвредно. У отката оно **съело работу целой смены** — спасло
> только то, что коммит остался в журнале ссылок (`git reflog`) и его удалось вернуть.
> 🔑 **ПРАВИЛО 252: отчёт команды о самой себе доказательством не является.** `git log`
> внутри той же строки показал верную голову — второй заход случился ПОЗЖЕ, уже после
> того, как команда отчиталась. Перемеряй голову **следующей отдельной командой**.
Ловушку ставить по-прежнему:
```sh
GIT_TRACE2_EVENT="<АБСОЛЮТНЫЙ путь>/lovushka.json" git commit -F <записка>
```
🪤 **СЧИТАТЬ ПО КОМАНДНОЙ СТРОКЕ, А НЕ ПО ЧИСЛУ ЗАПУСКОВ.** Одна команда коммита порождает
**3235 запусков git** — это её собственные хуки. Считать надо те, у кого в командной строке
**32 запуска git** — это её собственные хуки. Считать надо те, у кого в командной строке
есть слово `commit`:
```js
z.event === 'start' && Array.isArray(z.argv) && z.argv.includes('commit')
```
> 🪤🔴 **ОДИН ФАЙЛ ЛОВУШКИ — ОДИН ЗАХОД** (правило 241). Два захода в один файл — и она
> покажет близнеца там, где его нет. Проверять такую тревогу не ловушкой, а историей:
> `git log --format="%s" <от>..HEAD | sort | uniq -d`. Пусто — близнеца нет.
> 🪤 **У смены 29 ловушка на настоящем коммите файла НЕ СОЗДАЛА, и причина НЕ УСТАНОВЛЕНА.**
> Замерено, что сама она исправна: пишет на `git status`, пишет на `git commit`, пишет
> в связке с `rm`. На боевом коммите — не написала. Не гадать; если повторится, второй
> датчик даёт тот же ответ: `git log --format="%s" <от>..HEAD | sort | uniq -d` —
> пусто значит близнеца нет.
> 🪤🔴 **ОДИН ФАЙЛ ЛОВУШКИ — ОДИН ЗАХОД** (правило 241).
**Работа смене 30, по убыванию доходности:**