diff --git a/docs/ORCHESTRATOR_SESSION_PROMPT.md b/docs/ORCHESTRATOR_SESSION_PROMPT.md index be45a6b8..0c105456 100644 --- a/docs/ORCHESTRATOR_SESSION_PROMPT.md +++ b/docs/ORCHESTRATOR_SESSION_PROMPT.md @@ -47,7 +47,7 @@ - **Свип зонных решений владельца (урок D39.99 п.4):** при каждом лендинге зоны вычитать из зонного журнала НОВЫЕ «решения владельца» и вынести на confirm списком; решение, живущее только в зонном доке при противоречащем каноне, — дефект синхронизации, чинится ратификацией или опровержением, не молчанием. - **Анти-паттерны приёмки:** приёмка проверяет НАПРАВЛЕНИЕ и решения пака, не только клеймы отчёта. Шесть ловушек: след отчёта (проверяешь лишь названные автором места) · связность вместо истинности (форма отчёта убеждает сама) · соглашательство с чёткой позицией (распространенная ошибка ллм моделей склонных соглашаться с человеком) · рационализация задним числом · слепой участок общих моделей обученных на одинх и тех же данных (выводы одной модели усиливают уверенность в правильности другой) · экономия усилия на неудобном. Механика-минимум: ≥1 верификатор выносит мнение по промту+диффу ДО чтения отчёта · ≥1 ищет дефекты ВНЕ карты отчёта · экспериментальный клейм без ре-рана (хотя бы редуцированного) в D-ноте помечается «со слов сессии», не «исполнением» · «решение владельца», известное только со слов сессии, НЕ ратифицируется — список confirm/deny владельцу ДО лендинга, если решение несущее · к утверждениям самой приёмки та же дисциплина «заявление=команда» (догадку фактом не подавать) · оценочные эпитеты в ноты не писать. - **Пре-рег дисциплина полигона:** фриз коммитом ДО платных вызовов (единственный коммит сессии); изменения после = новый experiment-ID; девиации — явно в отчёте; стоп-гейты по бюджету легитимны; «если не влезает — стоп и пинг, не резать молча». -- **Лендинг:** микро-дефекты доков чинишь сам с пометкой «испр. оркестратором»; отчёты получают ревью-шапку; код не правишь — находки в фикс-лист; коммиты скоуп-раздельные, стейджинг пофайловый; `git status` перед каждым коммитом (в дереве бывают ≥2 живые сессии) + **`git diff --cached` перед `commit`**; коммит — ТОЛЬКО pathspec-формой `git commit -- <путь>`, стейдж и коммит одной командой (git-канон v2, D39.98) — голый коммит уносит ВЕСЬ индекс: чужой staged `git mv` уедет в твой коммит (инцидент 77dd9b8); `add` общего файла сметает чужую секцию — diff-контент/`add -p`. **Лендинг, двигающий файлы или состав активных промтов, обновляет `docs/README.md` тем же коммитом** (норма D39.80; урок 02.08: README объявлял заленденный пак «можно запускать» с мёртвой ссылкой — поймал не процесс, а новая сессия). Статус/голову README не несёт вовсе — единственный носитель состояния = PROGRESS CURRENT-STATE. **Перед коммитом ратификации — механический чек головы:** `grep "голова D" docs/PROGRESS.md` обязан показать номер ТОЛЬКО ЧТО ратифицированной ноты (голова отставала дважды: D39.81 и D39.83 — оба раза «Текущее» обновлено, число головы забыто). +- **Лендинг:** микро-дефекты доков чинишь сам с пометкой «испр. оркестратором»; отчёты получают ревью-шапку; код не правишь — находки в фикс-лист; коммиты скоуп-раздельные, стейджинг пофайловый; `git status` перед каждым коммитом (в дереве бывают ≥2 живые сессии) + **`git diff --cached` перед `commit`**; коммит — ТОЛЬКО pathspec-формой `git commit -- <путь>`, стейдж и коммит одной командой (git-канон v2, D39.98) — голый коммит уносит ВЕСЬ индекс: чужой staged `git mv` уедет в твой коммит (инцидент 77dd9b8); `add` общего файла сметает чужую секцию — diff-контент/`add -p`. **Лендинг, двигающий файлы или состав активных промтов, обновляет `docs/README.md` тем же коммитом** (норма D39.80; урок 02.08: README объявлял заленденный пак «можно запускать» с мёртвой ссылкой — поймал не процесс, а новая сессия). Статус/голову README не несёт вовсе — единственный носитель состояния = PROGRESS CURRENT-STATE. **Перед коммитом ратификации — механический чек головы, и он теперь ИНСТРУМЕНТ, а не памятка:** `python3 docs/scripts/counts.py --check` печатает голову по трём носителям (последняя нота D-лога · шапка-диапазон · CURRENT-STATE) и все производные числа доков, ненулевой код = расхождение; тот же скрипт зовёт зонный хук `docs/scripts/githooks/pre-commit` при каждом коммите, задевающем D-лог или PROGRESS (ПРЕДУПРЕЖДАЕТ, не блокирует — жёсткий гейт только словом владельца). Заведено потому, что голова отставала ТРИЖДЫ у трёх разных оркестраторов (D39.81 · D39.83 · D39.112 п.5б): одна и та же ошибка у независимых сессий на одном поле — свойство поля. **Производные числа (счёт очереди, зоны, вес открытых строк реестра) руками больше не переписывать** — носитель несёт команду, литерал сверяется этой же командой в том же касании. - **Findings-ledger процесс (концерн 5):** каждая находка — явная диспозиция до закрытия; вопросы «к бэкенду» из ресёрчей маршрутизируются обратно; completeness-critic на границах фаз. - **Ратификация:** D-блоки (образцы D39.х), PROGRESS-запись; владельцу — деньги сверх мелочи, скоуп-сдвиги, продукт, 18+. - **Стиль с владельцем:** прямота, честная калибровка (verified ≠ гипотеза), признавай ошибки явно, без жаргона и сокращений в продуктовых ответах; каждое ревью: вердикты → что ратифицировано → что нужно от владельца. diff --git a/docs/README.md b/docs/README.md index 7c12189c..3b643ee4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ - `experiments/` — эмпирика полигона: [00-provider-quirks.md](experiments/00-provider-quirks.md) — **читать перед любым вызовом провайдера**; [08-cost-model-v2.md](experiments/08-cost-model-v2.md) — денежная модель; [09-pilot-protocol.md](experiments/09-pilot-protocol.md) — пилот Ф2.5; остальные 01–20 — отчёты закрытых экспериментов (судьба — в баннерах/D-логе; 18–20 — с ревью-шапками приёмки D39.108, шапка первична). - `research/` — фактура ресёрчей 01–25; у принятых — ревью-шапки, часть тел под ⚠ superseded: **читай баннер прежде содержимого**. Ключевые для навигации: 15 голос · 16 ридер-IDE · 17 внешняя критика · 18 рычаги качества · 19 нарезка · 20 банк-майнинг · 21 обзор транспорта · 22 доменные харнессы · 23 шов движок↔платформа (транспорт superseded D39.106) · 25 холодное ревью шва — форма D39.106, отвергнутые альтернативы, требования к эмиттеру (читать перед любым кодом стыка) · 24 арбитраж банка (ПРИНЯТ D39.102: консилиум закрыт классом, вход фикс-пака банка — §G). - [PROGRESS.md](PROGRESS.md) — журнал: CURRENT-STATE + **ЕДИНЫЙ БЭКЛОГ** (единственный трекер) + живой хвост хроники. НЕ источник решений. +- `scripts/counts.py` — **производные числа доков считаются им, а не руками** (голова по трём носителям · счёт очереди и зон · вес открытых строк регистра платформы); `--check` даёт ненулевой код на расхождении. Его же зовёт зонный хук `scripts/githooks/pre-commit` при коммите, задевающем D-лог или PROGRESS — предупреждает, не блокирует. Заведено по D39.112 п.5б: голова отставала трижды у трёх разных оркестраторов. - Активные хендофф-промты сессий (состав обновляется при каждом лендинге — норма D39.80): [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояния не дублирует) · [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md) (полигон, отложен) · **платформа: [../platform/docs/PLATFORM_SESSION_PROMPT.md](../platform/docs/PLATFORM_SESSION_PROMPT.md) — ОТРАБОТАН (P0 ПРИНЯТ D39.107; P1+P2 ПРИНЯТЫ и залендены D39.109, регистр до PD-107); следующий промт платформы — по слову владельца (реконсилятор/тейлер/юниты, D39.107 п.3(3))** · фронт: `frontend/docs/S3_SESSION_PROMPT.md` (замок ОТКРЫТ — контракт ратифицирован D39.99; первый шаг — правки спеки: D39.100 + **новые В-трис/В-кватер от №15, D39.110** — управляемый потолок в главах и запрет проектировать кросс-origin, CORS в платформе нет). Зонные журналы фронта/платформы — `frontend-PROGRESS.md` / `platform-PROGRESS.md` в их зонах (решение владельца 04.08: прогресс зон только там). **Бэкенд: [BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT.md](BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT.md)** (фикс-пак банка, строка 128+129; санкция D39.103) · **Полигон: [POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT.md](POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT.md)** (эксп-21 «топология ролей»: скрин моделей → бейк-офф топологий на невиданном срезе; потолки предварительные, ЗАПУСК = слово владельца; D39.108). Очередь и состояние — только CURRENT-STATE. - Зоны фронта (чужие, читать при касании стыка; каждая ведёт СВОЙ зонный бэклог — единый бэклог их строк не принимает, D39.84): [../frontend/](../frontend/) — веб-интерфейс: промт фронт-сессий S0–S7 + [STACK_DECISIONS.md](../frontend/docs/STACK_DECISIONS.md) (пины версий точными числами и ловушки, сверены с вебом 02.08) + [BACKLOG.md](../frontend/docs/BACKLOG.md) · [../platform/](../platform/) — SaaS control plane: README + [BACKLOG.md](../platform/BACKLOG.md) (П-1..П-5) + `docs/` (промт P0 · зонный журнал `platform-PROGRESS.md`). - `archive/` — история ([правила архива](archive/README.md)): закрытые промты (`prompts/`) · отчёты с ревью-шапками (`reports/` — на них ссылаются приёмки) · исполненные арх-доки (`architecture/`) · слайсы хроники `PROGRESS-*.md`. Инструкции оттуда не исполнять. diff --git a/docs/scripts/counts.py b/docs/scripts/counts.py new file mode 100644 index 00000000..22c68257 --- /dev/null +++ b/docs/scripts/counts.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Печатает производные числа доков, которые до сих пор переписывались руками. + +Заведён после верификации записей №15 (D39.112 п.4): за одну сессию четыре из десяти +рукописных копий производных чисел разошлись с источником. Норма D39.101 требует считать +очередь «только скриптом» — скрипта при этом не существовало, и норма исполнялась глазами. + +Числа, которые печатает этот файл, в доках писать литералами больше не надо: строка носителя +несёт команду. Кто хочет литерал — обязан сверить его этой командой в том же касании. + +Использование: python3 docs/scripts/counts.py [--check] + без флага — печатает числа для вставки в доки + --check — сверяет литералы, найденные в доках, с пере-счётом; ненулевой код = расхождение +""" + +from __future__ import annotations + +import re +import sys +from collections import Counter +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +PROGRESS = ROOT / "docs" / "PROGRESS.md" +DLOG = ROOT / "docs" / "architecture" / "05-decisions-log.md" +REGISTER = ROOT / "platform" / "docs" / "DEFECT_REGISTER.md" + +# Строка таблицы бэклога: «| 145 | ...» или «| 13а | ...». Заголовки-разделители («| **— ... —** |») +# и шапка таблицы под этот вид не подходят и потому не считаются. +BACKLOG_ROW = re.compile(r"^\| (\d+[а-яё]?) \|") +REGISTER_ROW = re.compile(r"^\| (PD-\d+) \|") +NOTE = re.compile(r"^## (D39\.\d+)") + + +def cells(line: str) -> list[str]: + return [c.strip() for c in line.split("|")] + + +def backlog() -> dict: + rows = [l for l in PROGRESS.read_text(encoding="utf-8").splitlines() if BACKLOG_ROW.match(l)] + ids = [BACKLOG_ROW.match(l).group(1) for l in rows] + dupes = [i for i, n in Counter(ids).items() if n > 1] + strict = sum(1 for l in rows if cells(l)[3] == "бэкенд") + wide = sum(1 for l in rows if "бэкенд" in cells(l)[3]) + soon = [BACKLOG_ROW.match(l).group(1) for l in rows if "скоро" in cells(l)[4]] + blockers = sum(1 for l in rows if "блокер-очереди" in cells(l)[4]) + return { + "всего": len(rows), + "бэкенд строго": strict, + "бэкенд широко": wide, + "скоро": len(soon), + "скоро поимённо": soon, + "блокеров очереди": blockers, + "дубли ID": dupes, + } + + +def register() -> dict: + rows = [l for l in REGISTER.read_text(encoding="utf-8").splitlines() if REGISTER_ROW.match(l)] + weight = Counter() + status = Counter() + open_ids = [] + for l in rows: + c = cells(l) + st, sev = c[6], c[3].replace("*", "") + if st == "open": + status["open"] += 1 + open_ids.append(c[1]) + # Вес читается по вхождению слова: колонка часто несёт оговорку («major (для промта…)»). + weight["major" if "major" in sev else "minor" if "minor" in sev else "info" if "info" in sev else sev] += 1 + elif st.startswith("fixed"): + status["fixed"] += 1 + elif st.startswith("accepted-risk"): + status["accepted-risk"] += 1 + else: + status["иное: " + st[:32]] += 1 + return { + "всего строк": len(rows), + "по статусу": dict(status), + "вес открытых": dict(weight), + "открытые": open_ids, + } + + +def head() -> dict: + """Голова = последняя нота D-лога; она же обязана стоять в шапке CURRENT-STATE.""" + notes = [NOTE.match(l).group(1) for l in DLOG.read_text(encoding="utf-8").splitlines() if NOTE.match(l)] + last = notes[-1] if notes else None + first = PROGRESS.read_text(encoding="utf-8").splitlines()[:6] + claimed = None + for l in first: + m = re.search(r"голова (D39\.\d+)", l) + if m: + claimed = m.group(1) + break + banner = re.search(r"D1–(D39\.\d+)", DLOG.read_text(encoding="utf-8").splitlines()[0]) + return { + "последняя нота D-лога": last, + "голова в CURRENT-STATE": claimed, + "шапка D-лога": banner.group(1) if banner else None, + "сходится": last == claimed == (banner.group(1) if banner else None), + } + + +def main() -> int: + b, r, h = backlog(), register(), head() + check = "--check" in sys.argv + + print("ГОЛОВА") + for k, v in h.items(): + print(f" {k}: {v}") + print("\nБЭКЛОГ (docs/PROGRESS.md)") + for k, v in b.items(): + print(f" {k}: {v if k != 'скоро поимённо' else '·'.join(v)}") + print("\nРЕГИСТР ПЛАТФОРМЫ (platform/docs/DEFECT_REGISTER.md)") + for k, v in r.items(): + print(f" {k}: {v if k != 'открытые' else ' '.join(v)}") + + if not check: + return 0 + + bad = [] + if not h["сходится"]: + bad.append( + f"голова разошлась: последняя нота {h['последняя нота D-лога']}, " + f"CURRENT-STATE {h['голова в CURRENT-STATE']}, шапка D-лога {h['шапка D-лога']}" + ) + if b["дубли ID"]: + bad.append(f"дубли ID строк бэклога: {b['дубли ID']}") + # Литералы в доках: ищем только те формы, которые доки реально используют. + prog = PROGRESS.read_text(encoding="utf-8") + m = re.search(r"всего \*\*(\d+)\*\* строк", prog) + if m and int(m.group(1)) != b["всего"]: + bad.append(f"счёт очереди в CURRENT-STATE {m.group(1)} против пере-счёта {b['всего']}") + m = re.search(r"зона бэкенд \*\*(\d+)\*\* строго .*?/ \*\*(\d+)\*\* широко", prog) + if m and (int(m.group(1)), int(m.group(2))) != (b["бэкенд строго"], b["бэкенд широко"]): + bad.append( + f"счёт зоны бэкенд {m.group(1)}/{m.group(2)} против пере-счёта " + f"{b['бэкенд строго']}/{b['бэкенд широко']}" + ) + for path in (PROGRESS, ROOT / "platform" / "docs" / "platform-PROGRESS.md"): + text = path.read_text(encoding="utf-8") + for mm in re.finditer(r"\*\*(\d+) major\*\*", text): + if int(mm.group(1)) != r["вес открытых"].get("major", 0): + bad.append( + f"{path.name}: «{mm.group(1)} major» против пере-счёта " + f"{r['вес открытых'].get('major', 0)}" + ) + + if bad: + print("\nРАСХОЖДЕНИЯ:") + for line in bad: + print(" ✗ " + line) + return 1 + print("\nЛитералы в доках сходятся с пере-счётом.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/scripts/githooks/pre-commit b/docs/scripts/githooks/pre-commit new file mode 100755 index 00000000..dec810ea --- /dev/null +++ b/docs/scripts/githooks/pre-commit @@ -0,0 +1,54 @@ +#!/bin/sh +# Зонный pre-commit фрагмент docs/. Запускается тем же диспетчером .git/hooks/pre-commit, +# что и фрагмент фронта (он перебирает */scripts/githooks/pre-commit). +# +# Зачем: за одну сессию №15 четыре из десяти рукописных копий производных чисел разошлись с +# источником, а голова CURRENT-STATE отставала ТРЕТИЙ раз в истории проекта — у трёх разных +# оркестраторов (D39.81, D39.83, D39.112 п.5б). Одна и та же ошибка у независимых сессий на одном +# поле — свойство поля, а не сессий, поэтому проверка механическая, а не нормой прозой. +# +# ⚠ ПРЕДУПРЕЖДАЕТ, НЕ БЛОКИРУЕТ (решение оркестратора №15, 07.08): фрагмент лежит в общем +# диспетчере и сработает на коммите ЛЮБОЙ сессии, включая те, которым я не писал промт. Жёсткий +# гейт, останавливающий чужой коммит, ставится только словом владельца. Обход, если понадобится: +# git commit --no-verify. + +staged=$(git diff --cached --name-only) +[ -z "$staged" ] && exit 0 + +dlog='docs/architecture/05-decisions-log.md' +prog='docs/PROGRESS.md' + +printf '%s\n' "$staged" | grep -qE "^($dlog|$prog)$" || exit 0 + +# Содержимое ПОСЛЕ коммита: для застейдженного файла это индекс, для нетронутого — HEAD. +after() { + if printf '%s\n' "$staged" | grep -qx "$1"; then git show ":$1" 2>/dev/null; else git show "HEAD:$1" 2>/dev/null; fi +} + +note=$(after "$dlog" | grep -oE '^## D39\.[0-9]+' | tail -1 | sed 's/^## //') +banner=$(after "$dlog" | head -1 | grep -oE 'D1–D39\.[0-9]+' | sed 's/^D1–//') +claimed=$(after "$prog" | head -6 | grep -oE 'голова D39\.[0-9]+' | head -1 | sed 's/^голова //') + +warn=0 +if [ -n "$note" ] && [ -n "$claimed" ] && [ "$note" != "$claimed" ]; then + echo "pre-commit ⚠ docs: голова разошлась — последняя нота D-лога $note, CURRENT-STATE говорит $claimed." >&2 + echo " Норма промта оркестратора: голова бампается при КАЖДОМ аппенде ноты, в том же коммите." >&2 + warn=1 +fi +if [ -n "$note" ] && [ -n "$banner" ] && [ "$note" != "$banner" ]; then + echo "pre-commit ⚠ docs: шапка-диапазон D-лога $banner при последней ноте $note." >&2 + warn=1 +fi + +# Производные числа доков — сверка пере-счётом (docs/scripts/counts.py). Считает по РАБОЧЕМУ +# дереву: при частичном стейдже это advisory, а не приговор. +if command -v python3 >/dev/null 2>&1 && [ -f docs/scripts/counts.py ]; then + out=$(python3 docs/scripts/counts.py --check 2>&1) || { + echo "pre-commit ⚠ docs: литералы в доках разошлись с пере-счётом:" >&2 + printf '%s\n' "$out" | grep '✗' >&2 + warn=1 + } +fi + +[ "$warn" -eq 1 ] && echo "pre-commit: это ПРЕДУПРЕЖДЕНИЕ, коммит не остановлен." >&2 +exit 0