88 lines
4.5 KiB
Python
88 lines
4.5 KiB
Python
#!/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())
|