diff --git a/docs/superpowers/zamer/2026-08-02-ZAMER-metoda.md b/docs/superpowers/zamer/2026-08-02-ZAMER-metoda.md index 18fd3946..9dba6f12 100644 --- a/docs/superpowers/zamer/2026-08-02-ZAMER-metoda.md +++ b/docs/superpowers/zamer/2026-08-02-ZAMER-metoda.md @@ -10828,3 +10828,95 @@ const s = `ушло ${dollary(x)} денег`; → после чистки: 🟢 **Два реза сведены в один дом.** `bezPrimechaniy` перенесён к грубому `tolkoKod` с развилкой «какой брать». Врозь они и стоили смене 26 сторожа, который физически не мог покраснеть: искала команду, живущую строкой, а взяла рез, который строки режет. + +--- + +## М-406. Смена 28: сторож был зелёным по ДВУМ причинам разом, и одна из них случайная + +**Развилка окупилась ВОСЬМОЙ раз подряд.** Ночи не было; все шесть приборов сошлись знак +в знак с числами смены 27. + +### Правило 243 сработало на своём же списке — и опять сняло больше половины + +Смена 27 передала список «пять доступных настоящих долгов». Проверил каждую строку +отдельно, как велит свежее правило. **Доступным оказался ОДИН.** + +| долг | приговор замера смены 28 | +|---|---| +| `readResult` (`spawn.mjs`) | ❌ **не долг, а мёртвый остаток**. Дороги, которой он писан — «запусти, дождись конца, возьми итог», — в затее нет: `startWorker` отдаёт ЖИВОГО ребёнка. Все четверо читающих потоки делают ДРУГОЕ (надзиратель — живьём по строке; `budget` — ищет замеры предела; судья — берёт последнее событие `result` и текст приговора; `dengi-bez-zapisi` — первую цену). Расход, ради которого он писан, затея **нарочно не ведёт**: не считает, а спрашивает у прибора системы | +| `mailFailedSince` (`mailer.mjs`) | 🔒 **заперт словом владельца.** Замысел, строки 750 и 1073: «затея видит, что сводка не ушла (отправлялка записала свою неудачу) — тут признака жизни мало, нужно ЯВНОЕ слово». Это часть правила ворот «отклик владельца» | +| **`zadachaKonchena` (`items.mjs`)** | ✅ **настоящий, доступный, закрыт этой сменой** | +| `reshyonnyeSNevypolnimymi` (`items.mjs`) | 🔒 **заперт тем же словом.** Единственное место, откуда пункт становится невыполнимым, — ответ владельца «нет» в `questions.mjs`, а он мёртв целиком. Заводить ленджер невыполнимых — значит строить провод к источнику, которого нет | +| `kudaOtnesyon` | ❌ ложная тревога, закрыта сменой 26 | + +🔑 **Третья смена подряд с той же долей:** 14 ложных из 33 долгов, 3 из 4 разъездов, +теперь 4 из 5. Списки в этом хозяйстве стабильно наполовину чужие — и стабильно +разбираются замером за час. + +### 🟢 Что закрыто: «свободных пунктов нет» ≠ «задача сделана» + +В блоке 5а надзирателя стояло упрощение `otlozhennyePunkty.size === 0` — «отложенных нет, +значит всё засчитано». Оно врало в **третьем случае, безнадзорном**. + +Сюда приходят по ДВУМ разным причинам: + +1. пунктов вправду не осталось — всё закрыто или отложено; +2. пункт **есть**, но его файлы держит живой сосед, и занять их из-под руки не вышло. + +Во втором случае отложенных нет — и на **недоделанной задаче** поднимался флаг приёмщику, +а владельцу уходило «Пункты задачи кончились и все засчитаны». Ровно та беда, от которой +предостерегает примечание строкой выше того же файла. + +**Доказано вырезанием:** сторож написан первым, покраснел (`items_all_verified` = `true` +на недоделанной задаче), после починки зелёный, при возврате упрощения — снова красный, +и ровно один, посчитанный до прогона. **Прибор №6 подтвердил независимо:** имя ушло +из приговора. + +### 🔴🔴 ГЛАВНОЕ, ЧТО КУПИЛА СМЕНА: правило 242 сработало, и ответ оказался ТРЕТЬИМ + +После починки полный прогон дал красное **у чужого сторожа** — `primechanie-ne-rashoditsya-s-pravdoy.test.mjs`, +проверка 4. Вчерашнее правило 242 велит не откатываться сразу, а спросить: что он бережёт? + +Он бережёт согласие примечания с правдой, и по замыслу он **прав**: правило подключили — +шапка обязана это признать. Шапку я поправил. Он всё равно краснел. + +**Причина:** сторож брал `items.mjs` **сырым файлом** и искал в нём фразу-маркер +«НЕТ НИ ОДНОГО». А такая фраза стоит там **в живом коде** — в тексте ошибки +`'в плане задачи нет ни одного пункта — считать нечего'`. + +⇒ Пока шапка врала тем же словом, **обе половины совпадали, и сторож молчал**. То есть он +был зелёным **по двум причинам разом, и одна из них случайная**. Напиши смена 26 шапку +другими словами — он краснел бы на пустом месте; подключи кто-то правило и поправь шапку +честно — он краснеет, не умея сказать почему. Ровно это и случилось со мной. + +**244. 🔑 У СТОРОЖА, ЧИТАЮЩЕГО ФАЙЛ СЫРЫМ, СПРОСИ: НЕ ОТВЕЧАЕТ ЛИ ЗА ПРИМЕЧАНИЕ ТО, +ЧТО ПРИМЕЧАНИЕМ НЕ ЯВЛЯЕТСЯ.** Это обратная сторона правила 234. То говорит: рез может +срезать вместе с шелухой зерно. Это говорит: **отсутствие реза может подсунуть шелуху +вместо зерна.** Ищешь слова файла О СЕБЕ — режь всё, кроме примечаний. + +**245. 🔑 У КРАСНОГО ЧУЖОГО СТОРОЖА ТРИ ИСХОДА, А НЕ ДВА.** Смена 27 знала два: «прав он — +откатись» и «прав ты — чини своё». Есть третий, и он встретился первым же: **прав он +по замыслу, но слеп по способу — тогда чинят САМОГО СТОРОЖА.** Отличить просто: спроси, +что именно он измерил, и совпадает ли измеренное с тем, что он берётся охранять. Здесь +он брался охранять слова файла о себе, а мерил весь файл целиком. + +**246. 🪤 `grep -i` НА РУССКИХ БУКВАХ РЕГИСТР НЕ ПРИВОДИТ.** Замерено дважды на одном +файле: `grep -c -i "НЕТ НИ ОДНОГО"` → **0**, `grep -c -i "нет ни одного"` → **1**, `node` +тем же выражением → нашёл. Мой первый замер сказал «фразы в файле нет» — и я чуть не пошёл +искать причину не там. 🔑 **Мерь тем же телом, каким меряет тот, кого проверяешь.** + +### Что сделано + +🟢 **Долг куска 7 закрыт** — надзиратель зовёт правило вместо упрощения. +🟢 **Слепота сторожа правила 235 закрыта** — заведён третий рез `tolkoPrimechaniya`, +обратный `bezPrimechaniy`, и обратность стережётся отдельной проверкой. Все три места +сторожа переведены на него, хотя слепота задевала одно: у двух других фразы-маркера +в коде сегодня нет — но это **удача, а не защита**. +🟢 **Три примечания приведены к правде** (правило 235): `items.mjs` — «живой зовущий есть, +и он ОДИН» (второго нет и не будет, пока нет куска 7); `spawn.mjs` — `readResult` назван +мёртвым остатком с поимённым разбором всех четверых, кто делает похожее; `items.mjs` — +`reshyonnyeSNevypolnimymi` помечен запертым, а не «некому сделать». + +🔴 **`readResult` НЕ снесён намеренно.** Снос необратимее записи, слова владельца на него +нет, а вместе с правилом ушли бы три его проверки. Это отдельное решение владельца, +а не побочный ход чужой работы. diff --git a/docs/superpowers/zamer/2026-08-18-razbor-33-dolgov.md b/docs/superpowers/zamer/2026-08-18-razbor-33-dolgov.md index 657d8b15..bf405c7e 100644 --- a/docs/superpowers/zamer/2026-08-18-razbor-33-dolgov.md +++ b/docs/superpowers/zamer/2026-08-18-razbor-33-dolgov.md @@ -140,6 +140,24 @@ app/vitest.config.tools.mjs:56 → globalSetup: ['../tools/night/storozh-boevo --- +## 🔴🔴 ПОПРАВКА СМЕНЫ 28 (07.08.2026): «доступных пять» — на деле ОДИН + +Смена 27 передала следующей список «осталось доступных пять». Правило 243 велит проверять +такой список замером, каждую строку отдельно. Проверил. **Доступным оказался один.** + +| строка списка | приговор замера | +|---|---| +| **`zadachaKonchena`** | ✅ **настоящий и доступный — ЗАКРЫТ сменой 28.** Упрощение `otlozhennyePunkty.size === 0` врало в третьем случае: пункт есть, но его файлы держит живой сосед. Отложенных нет — и на НЕДОДЕЛАННОЙ задаче поднимался флаг приёмщику. Доказано вырезанием, прибор №6 подтвердил независимо | +| `reshyonnyeSNevypolnimymi` | 🔒 **заперт словом владельца, а не «некому сделать»**. Единственный источник невыполнимых пунктов — ответ владельца «нет» в `questions.mjs`, а он мёртв целиком. Ленджер невыполнимых строить некуда: источника нет | +| `mailFailedSince` | 🔒 **заперт словом владельца.** Замысел, строки 750 и 1073: «затея видит, что сводка не ушла — тут признака жизни мало, нужно ЯВНОЕ слово». Это часть правила ворот «отклик владельца», и подключение меняет ночь владельца | +| `readResult` | ❌ **не долг, а мёртвый остаток.** Дороги «запусти, дождись конца, возьми итог» в затее нет: `startWorker` отдаёт живого ребёнка. Четверо читающих потоки делают ДРУГОЕ, разбор поимённо — в примечании над самим правилом. Не снесён намеренно: это решение владельца | +| `kudaOtnesyon` | ❌ ложная тревога, уже закрытая выше (раздел Г) | + +🔑 **Третья смена подряд, где список наполовину чужой:** 14 ложных из 33 долгов, 3 из 4 +разъездов, 4 из 5 «доступных». Правило проверяют мыслью, список — замером. + +--- + ## 🔑 Чему научил разбор **237. У ПРИБОРА, СУДЯЩЕГО «КТО ЭТО ЗОВЁТ», СПРОСИ СНАЧАЛА — А ВСЕ ЛИ ДОРОГИ ЗОВА ОН ЗНАЕТ.** diff --git a/tools/night/items.mjs b/tools/night/items.mjs index 2f5c6e1d..bf75a866 100644 --- a/tools/night/items.mjs +++ b/tools/night/items.mjs @@ -363,27 +363,33 @@ export function deferredTaskGate({ // ленджера невыполнимых пунктов затея на 02.08 не заводит нигде. Значит правило готово, // а живой провод к нему — нет: кто будет его делать, обязан сперва завести такой ленджер, // иначе звать это правило будет нечем. +// +// 🔒 ЗАМЕРЕНО 07.08.2026, СМЕНА 28 — И ЭТО НЕ «НЕКОМУ СДЕЛАТЬ», А «НЕЛЬЗЯ НАЧАТЬ». +// Единственное место затеи, откуда пункт вообще становится невыполнимым, — ответ владельца +// «нет» в `questions.mjs` (`item_marked_impossible`). Механизм вопросов владельцу мёртв +// С ОБЕИХ СТОРОН и ждёт его слова: подключение меняет ЕГО ночь. Значит и это правило +// заперто тем же решением — заводить ленджер невыполнимых, пока их неоткуда взять, +// значит строить провод к источнику, которого нет. export function reshyonnyeSNevypolnimymi(verified = [], impossible = []) { return [...new Set([...verified, ...impossible].map(String))]; } // ── кусок 7, задача 6 ──────────────────────────────────────────────────────────────── -// 🔴🔴 ЗОВУЩИХ У ЭТОГО ПРАВИЛА НЕТ НИ ОДНОГО. ЗАМЕРЕНО 06.08.2026, СМЕНА 26. +// 🟢 ЖИВОЙ ЗОВУЩИЙ ЕСТЬ, И ОН ОДИН. ЗАМЕРЕНО 07.08.2026, СМЕНА 28. // -// 🪤 Ниже стояло — и стоит до сих пор, дочитайте абзац, — «Зовут его двое». Это неправда -// в НАСТОЯЩЕМ времени: живых зовущих ноль, зовут только проверки (11). Оставляю прежний -// текст как есть нарочно, потому что он верно описывает ЗАМЫСЕЛ, — но читать его надо -// как «зваться оно должно двумя», а не «зовётся». -// -// 🟢 А вот в `supervisor.mjs` про то же самое сказано ЧЕСТНО: «ДОЛГ КУСКА 7 (позиция 7): -// ИМЕННО ЭТА СТРОКА заменяется на зов». Будущее время вместо настоящего — и примечание -// из вранья становится наказом. 🔑 Разница между тем и другим тут ровно в одном глаголе. -// Сегодня вместо этого правила там стоит упрощение `otlozhennyePunkty.size === 0`. -// -// 🔴 ЕДИНСТВЕННОЕ место затеи, где записано, что значит «задача кончена». Зовут его двое: +// 🔴 ЧИСЛО ЗДЕСЬ НАЗВАНО ТОЧНО, И ЭТО НЕ ПРИДИРКА. Замысел велит звать правило ДВОИМ: // блок 5а куска 4 (вслед за засчитанной заявкой) и блок 6а куска 7 (каждый круг). -// Второй дороги к этому правилу не заводить: разъедутся молча, и приёмщик пойдёт снимать -// недоделанную задачу — а владелец утром прочтёт «сделано». +// Подключён ПЕРВЫЙ — `supervisor.mjs`, блок 5а. Второго нет: куска 7 в затее нет вовсе. +// 🪤 Напиши здесь «зовут его двое» — и это снова стало бы правилом 235: задуманное +// в настоящем времени читается как доказательство подключения, будучи его заменой. +// Прежняя смена честно пометила ту же строку как враньё; чинить враньё половиной правды +// нельзя. Пока куска 7 нет — здесь стоит ОДИН. +// +// 🔴 ЕДИНСТВЕННОЕ место затеи, где записано, что значит «задача кончена». Второй дороги +// к этому правилу не заводить: разъедутся молча, и приёмщик пойдёт снимать недоделанную +// задачу — а владелец утром прочтёт «сделано». Ровно эту беду и держал прежний зов: +// вместо правила там стояло упрощение `otlozhennyePunkty.size === 0`, и на пункте, +// чьи файлы держит живой сосед, оно отвечало «всё засчитано» (замерено вырезанием). // // 🔴 Третий довод `otlozhennye` обязателен по существу (находка Н8). Отложенный пункт лежит // в списке `done` очереди — чтобы она не выдала его снова, — но в `items-verified.jsonl` diff --git a/tools/night/primechanie-ne-rashoditsya-s-pravdoy.test.mjs b/tools/night/primechanie-ne-rashoditsya-s-pravdoy.test.mjs index a83ff0ff..b3b80b0a 100644 --- a/tools/night/primechanie-ne-rashoditsya-s-pravdoy.test.mjs +++ b/tools/night/primechanie-ne-rashoditsya-s-pravdoy.test.mjs @@ -50,7 +50,15 @@ const chitat = (imya) => readFileSync(join(NOCH, imya), 'utf8'); // 🆕 Смена 27 перенесла сам рез в `schyot-zovushchih.mjs` — К ГРУБОМУ `tolkoKod`, чтобы оба // стояли рядом с развилкой «какой брать». Врозь они и стоили смене 26 этой ошибки. Проверка // 1Б ниже стережёт его по-прежнему, откуда бы он ни ввозился. -import { bezPrimechaniy } from './schyot-zovushchih.mjs'; +// 🆕🔴 ВТОРОЙ РЕЗ, И ОН ОБРАТНЫЙ ПЕРВОМУ. ЗАВЕДЁН СМЕНОЙ 28 ПО КРАСНОМУ, КОТОРОЕ ЭТОТ +// СТОРОЖ ВЫДАЛ САМ. Правда о подключении ищется в КОДЕ (`bezPrimechaniy` — режет примечания), +// а слова файла о себе — в ПРИМЕЧАНИЯХ. Пока вторую половину брали сырым файлом, за шапку +// мог ответить живой код: в `items.mjs` фраза-маркер «нет ни одного» стоит в тексте ошибки +// (`'в плане задачи нет ни одного пункта — считать нечего'`), и сторож считал её словами +// шапки. Он был зелёным по ДВУМ причинам разом, и одна — случайная. +// 🔑 Тот же урок, что у первого реза, только с другого конца: у датчика спрашивай не «что +// он режет», а «не отвечает ли за примечание то, что примечанием не является». +import { bezPrimechaniy, tolkoPrimechaniya } from './schyot-zovushchih.mjs'; export { bezPrimechaniy }; @@ -88,11 +96,36 @@ describe('примечание о подключении не расходитс expect(bezPrimechaniy('/* про questions */ const a = 1;')).toContain('const a = 1'); }); + it('1В. 🔴 обратный рез: за примечание не отвечает живой код — иначе сторож зелен случайно', () => { + // 🔴 СВОЯ НАХОДКА СМЕНЫ 28, КУПЛЕННАЯ КРАСНЫМ ЭТОГО ЖЕ СТОРОЖА. Проверка 4 ниже брала + // `items.mjs` СЫРЫМ файлом и искала в нём фразу-маркер «НЕТ НИ ОДНОГО». А такая фраза + // стоит там в живом коде — в тексте ошибки «в плане задачи нет ни одного пункта». + // Значит шапку файла подтверждала строка кода, которая о подключении не говорит НИЧЕГО. + // Пока шапка врала тем же словом, обе половины совпадали и сторож молчал; подключи + // правило и поправь шапку честно — и он краснеет, не умея сказать почему. + expect(tolkoPrimechaniya("const r = { reason: 'в плане нет ни одного пункта' };")).not.toContain('ни одного'); + expect(tolkoPrimechaniya('// 🔴 ЗОВУЩИХ НЕТ НИ ОДНОГО')).toContain('НЕТ НИ ОДНОГО'); + expect(tolkoPrimechaniya('/* многострочное\n НЕ ПОДКЛЮЧЁН */ const a = 1;')).toContain('НЕ ПОДКЛЮЧЁН'); + expect(tolkoPrimechaniya('/* многострочное */ const a = 1;')).not.toContain('const a = 1'); + // 🪤 Два реза обязаны быть ОБРАТНЫ друг другу, а не просто разными: что вырезал один, + // то оставил второй. Без этой строки они могли бы разойтись молча, и половина файла + // не попала бы ни к тому, ни к другому. + const obrazec = "// шапка\nconst a = 'тело';"; + expect(bezPrimechaniy(obrazec)).toContain('тело'); + expect(bezPrimechaniy(obrazec)).not.toContain('шапка'); + expect(tolkoPrimechaniya(obrazec)).toContain('шапка'); + expect(tolkoPrimechaniya(obrazec)).not.toContain('тело'); + }); + it('2. 🔴 вопросы владельцу: надзиратель про них молчит — и файл так и говорит', () => { // 🪤 Тоже по очищенному коду: напиши кто-нибудь в примечании надзирателя слово // «questions» — и плоский поиск объявил бы механизм подключённым. См. проверку 4. + // 🆕 Вторая половина — обратным резом, как у проверки 4. У `questions.mjs` фразы-маркера + // в живом коде сегодня НЕТ (замерено сменой 28), то есть сторож тут зелен по существу, + // а не по удаче. Рез ставится всё равно: напиши кто-нибудь завтра текст ошибки со словом + // «не подключён» — и эта проверка ослепла бы ровно так же, как ослепла четвёртая. const podklyucheno = bezPrimechaniy(chitat('supervisor.mjs')).includes('questions'); - expect(primechanieSoydyotsya(podklyucheno, chitat('questions.mjs')), + expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('questions.mjs'))), podklyucheno ? 'механизм вопросов ПОДКЛЮЧИЛИ, а примечание всё ещё говорит «не подключён» — поправьте шапку questions.mjs' : 'примечание в questions.mjs обещает подключение, которого нет', @@ -104,7 +137,8 @@ describe('примечание о подключении не расходитс // а режем только примечания — иначе сторож ослепнет насовсем (разбор у `bezPrimechaniy`). const cli = bezPrimechaniy(chitat('cli.mjs')); const podklyucheno = /['"`]answer['"`]|['"`]ack['"`]/.test(cli); - expect(primechanieSoydyotsya(podklyucheno, chitat('mailer.mjs')), + // 🆕 Вторая половина — обратным резом (смена 28), по тому же доводу, что у проверок 2 и 4. + expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('mailer.mjs'))), podklyucheno ? 'команды ответа владельца ЗАВЕЛИ, а примечание в mailer.mjs всё ещё говорит «писать нечем»' : 'примечание в mailer.mjs обещает команды, которых у cli.mjs нет', @@ -118,8 +152,12 @@ describe('примечание о подключении не расходитс // за работу — ровно та мина, от которой написан прибор зовущих, и я написал про неё // строкой выше за минуту до того, как в неё угодил. // ⇒ Примечания вырезаем ДО поиска, чужим проверенным чистильщиком. + // 🆕🔴 И ВТОРАЯ ПОЛОВИНА — ЧЕРЕЗ ОБРАТНЫЙ РЕЗ (смена 28). Сырой файл сюда подавать + // нельзя: фраза-маркер «нет ни одного» стоит в `items.mjs` в ТЕКСТЕ ОШИБКИ живого кода, + // и пока шапка врала тем же словом, обе половины совпадали случайно. Правду ищем + // в коде, слова файла о себе — в примечаниях; смешивать их и значит ослепнуть. const podklyucheno = /zadachaKonchena\s*\(/.test(bezPrimechaniy(chitat('supervisor.mjs'))); - expect(primechanieSoydyotsya(podklyucheno, chitat('items.mjs')), + expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('items.mjs'))), podklyucheno ? 'правило «задача кончена» ПОДКЛЮЧИЛИ — поправьте шапку items.mjs, там сказано «зовущих нет ни одного»' : 'примечание в items.mjs говорит о зовущих, которых нет', diff --git a/tools/night/schyot-zovushchih.mjs b/tools/night/schyot-zovushchih.mjs index e9b48f7a..8daded5d 100644 --- a/tools/night/schyot-zovushchih.mjs +++ b/tools/night/schyot-zovushchih.mjs @@ -101,7 +101,7 @@ function obrazetsLiTut(vydano) { return PERED_OBRAZTSOM.test(t) || SLOVA_PERED_OBRAZTSOM.test(t); } -// ━━━ 🔴🔴 ДВА РЕЗА, И ОНИ НЕ ВЗАИМОЗАМЕНИМЫ. РАЗВИЛКА — ЗДЕСЬ ━━━ +// ━━━ 🔴🔴 ТРИ РЕЗА, И ОНИ НЕ ВЗАИМОЗАМЕНИМЫ. РАЗВИЛКА — ЗДЕСЬ ━━━ // // Стоят рядом НАРОЧНО. Смена 26 обожглась ровно оттого, что они лежали в разных файлах: // искала команду, живущую строкой (`case 'answer':`), взяла грубый `tolkoKod` — и своими @@ -111,8 +111,13 @@ function obrazetsLiTut(vydano) { // |---|---| // | ИМЯ СО СКОБКОЙ (зов, `имя(`) | `tolkoKod` — грубый: срежет примечания, строки и образцы | // | СТРОКУ (`'events.jsonl'`, команду, ключ) | `bezPrimechaniy` — режет ТОЛЬКО примечания | +// | ЧТО ФАЙЛ ГОВОРИТ О СЕБЕ (шапку, наказ) | `tolkoPrimechaniya` — обратный второму | // // 🔑 Спрашивай у реза не «что он режет», а «не режет ли он вместе с шелухой зерно». +// 🆕🔑 И второй вопрос, купленный сменой 28: «а не отвечает ли за примечание то, что +// примечанием не является». Сторож правила 235 брал файл СЫРЫМ и искал в нём слова шапки — +// а слово нашлось в тексте ошибки живого кода. Он был зелёным по двум причинам разом, +// и одна из них случайная. Ищешь слова файла о себе — бери третий рез, а не сырой текст. /** * Вырезать ТОЛЬКО примечания, оставив строковые значения нетронутыми. @@ -127,6 +132,24 @@ export function bezPrimechaniy(text) { .replace(/\/\/[^\n]*/g, ' '); } +/** + * Оставить ТОЛЬКО примечания, выбросив весь живой код. Обратный `bezPrimechaniy`: + * что вырезал тот, то оставляет этот, и наоборот. Обратность стережётся проверкой — + * `primechanie-ne-rashoditsya-s-pravdoy.test.mjs`, 1В. + * + * Берут его там, где надо спросить «а что файл ГОВОРИТ О СЕБЕ»: слова о подключении, + * наказы следующей смене, шапка. По сырому файлу такой вопрос задавать нельзя — за шапку + * ответит живой код, случайно совпавший словами (замерено сменой 28 на `items.mjs`). + * + * 🪤 Та же краевая грубость, что у обратного, и с тем же следствием наоборот: `//` внутри + * строкового значения (ссылка `https://`) этот рез сочтёт началом примечания и утащит + * хвост строки в «слова о себе». Сторожам это безразлично, берущему для нового дела — нет. + */ +export function tolkoPrimechaniya(text) { + const najdeno = String(text ?? '').match(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g); + return najdeno ? najdeno.join('\n') : ''; +} + export function tolkoKod(text) { let out = ''; let i = 0; diff --git a/tools/night/spawn.mjs b/tools/night/spawn.mjs index a16a7d10..331321e8 100644 --- a/tools/night/spawn.mjs +++ b/tools/night/spawn.mjs @@ -615,6 +615,30 @@ export function masterskayaOtsechena({ potok, zakazanTools = false, zakazanMcp = }; } +// 🔴🔴 ЖИВЫХ ЗОВУЩИХ НЕТ, И ЭТО НЕ ДОЛГ, А МЁРТВЫЙ ОСТАТОК. ЗАМЕРЕНО 07.08.2026, СМЕНА 28. +// +// Правило берёт ГОТОВЫЙ вывод целиком и разбирает его ПОСЛЕДНЮЮ строку. Дороги, которой +// оно писано — «запусти работника, дождись конца, возьми итог», — в затее больше нет: +// `startWorker` отдаёт ЖИВОГО ребёнка, а не текст. Все четверо, кто читает потоки, названы +// поимённо, и каждый делает ДРУГУЮ работу — сводить их сюда было бы не починкой, а поломкой: +// +// · `supervisor.mjs` — разбирает поток ЖИВЬЁМ, по строке, по мере прихода: попутно кладёт +// кусок на диск до всякого разбора и считает замеры запаса. Ждать конца ему нельзя; +// · `budget.mjs` (`probeUtilization`) — перебирает ВСЕ строки, ища замеры предела, +// а не итог. Последняя строка ему не нужна вовсе; +// · `sudya-nochi.mjs` (`itogIzPotoka`) — берёт ПОСЛЕДНЕЕ событие вида `result` (а не +// последнюю строку: после итога в потоке бывает хвост), понимает `is_error` и считает +// пустой приговор за «не судил». Отдаёт ТЕКСТ приговора, а не объект события; +// · `dengi-bez-zapisi.mjs` (`razborSleda`) — берёт ПЕРВУЮ цену из следа-улики. +// +// 🪤 Расход (`total_cost_usd`), ради которого правило писалось, затея НАРОЧНО не ведёт: +// остаток запаса она не считает, а СПРАШИВАЕТ у прибора системы (`supervisor.mjs`, разбор +// потока). Подключать отсюда второй счёт денег значило бы завести вторую дорогу к тому же +// смыслу — ровно то, от чего хозяйство предупреждает в пяти местах. +// +// 🔴 Не снесено НАМЕРЕННО, и причина названа: снос — необратимее записи, слова владельца +// на него нет, а вместе с правилом ушли бы три его проверки. Сносить это — отдельное +// решение владельца, не побочный ход чужой работы. export function readResult(stdout) { const text = String(stdout ?? '').trim(); if (!text) return null; diff --git a/tools/night/supervisor.mjs b/tools/night/supervisor.mjs index cc23c291..c016e889 100644 --- a/tools/night/supervisor.mjs +++ b/tools/night/supervisor.mjs @@ -80,7 +80,7 @@ import { readJsonOrBroken } from './state.mjs'; // (кусок 1, задача 11), и ввозить его не надо: второй такой же рядом развёл бы два // правила на одно слово владельца. Здесь ввозятся только руки — кто читает каталог // команд, кто снимает исполненную и кто называет непонятое слово. -import { readCommand, consumeCommand, unknownCommands, COMMAND_WORDS, closeItem, afterItemDone, afterTaskDone } from './items.mjs'; +import { readCommand, consumeCommand, unknownCommands, COMMAND_WORDS, closeItem, afterItemDone, afterTaskDone, zadachaKonchena } from './items.mjs'; // --- дополнение к ввозам в шапке файла (кусок 4, задача 5) --------------------- // 🔴 Замок «пункт закрыт» написан в item-audit.mjs, а ЗОВЁТ его этот файл, каждый круг, // у каждого работника. До этой задачи он зеленел на своих проверках и в живой ночи @@ -2235,11 +2235,29 @@ export async function supervise({ // в ночь»: одна ночь оставила бы два противоречащих знака, и утром владелец // не знал бы, которому верить. Знак должен быть ОДИН. // - // 🔴🔴 ДОЛГ КУСКА 7 (позиция 7): ИМЕННО ЭТА СТРОКА заменяется на зов - // `zadachaKonchena({ items, verified, otlozhennye }).konchena`, и вместе - // с долгом 5 (невыполнимые пункты), у которого своего ленджера в затее - // пока нет вовсе. Не переводи её тот, кто правит блок 5а. - const vseZaschitany = otlozhennyePunkty.size === 0; + // 🟢 ДОЛГ КУСКА 7 (позиция 7) ЗАКРЫТ 07.08.2026, СМЕНА 28. Здесь стояло + // упрощение `otlozhennyePunkty.size === 0` — «отложенных нет, значит всё + // засчитано». Оно врало в ТРЕТЬЕМ случае, безнадзорном: сюда приходят + // не только когда пунктов не осталось, но и когда пункт ЕСТЬ, а его файлы + // держит живой сосед и занять их из-под руки не вышло (`svobodnyy.item` + // гасится выше словами «их уводят из-под руки»). Отложенных при этом нет — + // и на НЕДОДЕЛАННОЙ задаче поднимался флаг приёмщику, а владельцу уходило + // «Пункты задачи кончились и все засчитаны». Замерено сторожем в + // `supervisor.test.mjs`: без правила `items_all_verified` = true. + // + // 🔴 Правило спрашивает ДРУГОЕ и потому ловит этот случай: не «нет ли + // отложенных», а «ВСЕ ЛИ пункты плана засчитаны». Оно же закрывает пустой + // план — прежде «ни одного пункта» читалось как «все засчитаны». + // + // 🪤 Долг 5 (невыполнимые пункты) сюда НЕ подключён и подключён быть не может: + // единственный источник невыполнимых — ответ владельца в `questions.mjs`, + // а он мёртв целиком и ждёт слова владельца. Довода `impossible` у правила + // нет вовсе — оно считает по засчитанным и отложенным, и этого хватает. + const vseZaschitany = zadachaKonchena({ + items: punktyProgona.map(p => p?.id), + verified: closedItemsOfRun(runDirPath), + otlozhennye: otlozhennyePunkty, + }).konchena; if (!vseZaschitany) { const otlozheno = [...otlozhennyePunkty].join(', '); diff --git a/tools/night/supervisor.test.mjs b/tools/night/supervisor.test.mjs index c4669c00..d35eb65c 100644 --- a/tools/night/supervisor.test.mjs +++ b/tools/night/supervisor.test.mjs @@ -2798,6 +2798,42 @@ describe('очередь пунктов и конец задачи (провер expect(run.items_all_verified).toBe(false); expect(run.reviewer_starts).toBe(false); }); + + it('🔴🔴 пункт НЕ засчитан и НЕ отложен, но его файлы держит СОСЕД — приёмщику флага НЕТ', async () => { + // 🔴🔴 ТРЕТИЙ СЛУЧАЙ, И ОН БЫЛ БЕЗНАДЗОРНЫМ. Два соседних выхода охранялись: «все + // засчитаны» (флаг есть) и «пункт отложен» (флага нет). А «свободных пунктов не + // нашлось» бывает по ДВУМ причинам, и вторая — не «пунктов не осталось», а «пункт + // есть, но его файлы заняты живым соседом, и занять их из-под руки не вышло» + // (`svobodnyy = { item: null, … 'занять его файлы не удалось' }` в блоке 5а). + // + // Прежнее упрощение `otlozhennyePunkty.size === 0` в этом случае отвечало «все + // засчитаны» — потому что отложенных вправду нет. И тогда на НЕДОДЕЛАННОЙ задаче + // поднимался флаг приёмщику, а владельцу уходило «Пункты задачи кончились и все + // засчитаны». Пункт 2 при этом не тронут никем: сосед его только держит. + // + // 🔑 Ломается ТИХО и целиком невидимо — ровно тот класс, ради которого правило + // `zadachaKonchena` и написано: оно спрашивает не «нет ли отложенных», а «все ли + // пункты плана ЗАСЧИТАНЫ», и это разные вопросы. + zayavit('1'); + // Сосед живой: карточка с углом на диске и веткой — иначе `liveForeignCorners` + // его не увидит, и замок не удержится (замерено в блоке про живых соседей выше). + const sosedRoot = join(dir, 'sosed-2'); + mkdirSync(sosedRoot, { recursive: true }); + writeJsonAtomic(join(rdir, 'workers', 'w-2', 'worker.json'), { + worker_id: 'w-2', run_id: 'r-1', status: 'working', workspace: sosedRoot, base_branch: 'feat/x', + }); + // 🪤 Заявку кладём тем же способом, каким её кладёт жизнь, а не рукой в файл: ключ + // сравнения путей соберётся сам, и проверка не начнёт зависеть от его устройства. + expect(zanyatFayly(rdir, 'w-2', ['app/B.php'], {}).ok).toBe(true); + planPunktov4([ + { id: '1', title: 'Первый', tests: ['a.test.mjs'], files: ['app/A.php'], long: false }, + { id: '2', title: 'Второй', tests: ['b.test.mjs'], files: ['app/B.php'], long: false }, + ], ['w-1', 'w-2']); + await runSupervisor({ tickMs: 1000, script: [() => {}], head: () => 'sha-1', runTests: ZELENO }); + const run = readJson(join(rdir, 'run.json')); + expect(run.items_all_verified).toBe(false); + expect(run.reviewer_starts).toBe(false); + }); }); describe('непризнанный пункт: работник узнаёт, и попыток у него две', () => {