#!/usr/bin/env python3 """Находки без НОСИТЕЛЯ: утверждения, которые исчезнут при выносе секции в архив. Зачем. Смена 06.09 пять раз наткнулась на один класс: находка живёт только в отчёте или в переписке, ряда в регистре и строки в бэклоге у неё нет — и после выноса секции в архив её больше не найти. Отдельно проверено, что и обратное бывает: секция, ВЫГЛЯДЯЩАЯ хроникой по заголовку, оказалась реестром живых решений (`platform/docs/STACK_DECISIONS.md`, решения 1–36). Поэтому перед любым выносом сначала прогоняют это, а решают глазами. Что считается носителем: ссылка на ряд регистра (`PD-123`), на строку бэклога («строка 281», «строки 49 и 161»), на ноту решения (`D39.202`, `D22.6`) или на эрратту («эррата 06.09-в»). ⚠ Прибор отвечает на СВОЙ вопрос: он не судит, важно ли утверждение, и не умеет отличать правило от рассказа. Ноль здесь означает «всё сослалось», а не «выносить безопасно» — читать всё равно надо. Использование: python3 docs/scripts/carriers.py <файл> [--from N] [--to M] [--quiet] Код возврата: 0 — находок без носителя нет; 1 — есть (число в последней строке). """ import argparse import re import sys # Утверждение, которое автор пометил как важное: именно они переживают вынос # хуже всего, потому что читаются как знание, а не как хроника. MARKED = re.compile(r"⛔|⚠⚠") # Носители. `D\d` покрывает и `D22.6`, и `D39.202`; эррата ловится отдельно, # потому что пишется датой, а не номером. CARRIER = re.compile( r"PD-\d+" r"|строк[аиеу]?\s*\d+" r"|строки\s*\d+" r"|\bD\d+(?:\.\d+)*\b" r"|эррат[аеы]\s*\d\d\.\d\d" ) # Строки, которые несут разметку, а не утверждение. SKIP = re.compile(r"^\s*(\||#{1,6}\s|```|>\s*$)") def scan(path: str, lo: int, hi: int): out = [] with open(path, encoding="utf-8") as fh: for n, raw in enumerate(fh, 1): if n < lo or (hi and n > hi): continue line = raw.rstrip("\n") if SKIP.match(line) or len(line.strip()) < 40: continue if MARKED.search(line) and not CARRIER.search(line): out.append((n, line.strip())) return out def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("path") ap.add_argument("--from", dest="lo", type=int, default=1) ap.add_argument("--to", dest="hi", type=int, default=0) ap.add_argument("--quiet", action="store_true") a = ap.parse_args() with open(a.path, encoding="utf-8") as fh: total = sum(1 for _ in fh) hi = a.hi or total found = scan(a.path, a.lo, a.hi) if not a.quiet: for n, line in found: print(f" ✗ {a.path}:{n}: {line[:150]}") # ⚠ Контрольная величина рядом с нулём — норма CLAUDE.md: «0 находок» и # «искал не там» в выводе неразличимы, поэтому осмотренное печатается всегда. print(f"\ncarriers: {len(found)} помеченных утверждений без носителя") print(f" осмотрено строк: {hi - a.lo + 1} (с {a.lo} по {hi} из {total})") print(" носителем считается: ряд PD · строка бэклога · D-нота · эррата") print(" ⚠ НЕ проверяется: важность утверждения · правило это или хроника —") print(" ноль означает «всё сослалось», а не «выносить безопасно».") return 1 if found else 0 if __name__ == "__main__": sys.exit(main())