Make the derived doc numbers computable instead of hand-copied: a counting script, a docs zone hook that warns on head drift, and the norm rewritten as a command
This commit is contained in:
parent
2d06e2dd04
commit
3b498410ca
4 changed files with 216 additions and 1 deletions
|
|
@ -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 ≠ гипотеза), признавай ошибки явно, без жаргона и сокращений в продуктовых ответах; каждое ревью: вердикты → что ратифицировано → что нужно от владельца.
|
||||
|
|
|
|||
|
|
@ -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`. Инструкции оттуда не исполнять.
|
||||
|
|
|
|||
160
docs/scripts/counts.py
Normal file
160
docs/scripts/counts.py
Normal file
|
|
@ -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())
|
||||
54
docs/scripts/githooks/pre-commit
Executable file
54
docs/scripts/githooks/pre-commit
Executable file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue