textmachine/docs/scripts/carriers.py

88 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Находки без НОСИТЕЛЯ: утверждения, которые исчезнут при выносе секции в архив.
Зачем. Смена 06.09 пять раз наткнулась на один класс: находка живёт только в
отчёте или в переписке, ряда в регистре и строки в бэклоге у неё нет — и после
выноса секции в архив её больше не найти. Отдельно проверено, что и обратное
бывает: секция, ВЫГЛЯДЯЩАЯ хроникой по заголовку, оказалась реестром живых
решений (`platform/docs/STACK_DECISIONS.md`, решения 136). Поэтому перед любым
выносом сначала прогоняют это, а решают глазами.
Что считается носителем: ссылка на ряд регистра (`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())