Files
management/analysis/scripts/inv_claims.py
T

84 lines
3.2 KiB
Python
Raw Normal View History

"""Extract checkable claims from the management repo's docs -> claims.tsv.
Phase 1 only *extracts*; nothing here judges whether a claim is true.
Verification happens in Phase 2.
A script cannot recognise a claim semantically, so extraction is trigger
based: a doc line is emitted when it matches at least one pattern that
marks it as asserting something checkable about the world (a component,
a count, a status, a version, a path, a date, the mirror topology, an
issue). Recall is favoured over precision -- a false positive costs a
Phase-2 glance, a false negative loses evidence.
Only management-repo docs are scanned: the mandate scopes claims to the
management repo's statements about its components.
"""
import re
from common import DOC_SUFFIXES, MGMT_REPO, cell, days_since, last_commit, \
tracked_files, write_tsv
COMPONENT_WORDS = (
r"ThreadNet[- ](?:Web|Call|Git|Operating|Server Suite)|threadnet-call|"
r"thread-net-git|threadnet-operating|threadnet-web|axion1337\.chat-gitops|"
r"game-operating|gameserver|management"
)
TRIGGERS = [
("component-ref", re.compile(COMPONENT_WORDS, re.I)),
("count", re.compile(
r"\b(ein|zwei|drei|vier|fünf|sechs|sieben|acht|neun|zehn|\d+)\s+"
r"(Repos?|Produkt-Repos?|Projekte?|Issues?|Commits?|Pipelines?|Themes?|"
r"Hosts?|Mirrors?)\b", re.I)),
("status", re.compile(
r"\b(erledigt|offen|live|aktiv|geschlossen|leer|umgezogen|entfallen|"
r"abgelöst|veraltet|überholt|scharf|grün|rot|tabu|kanonisch|"
r"zurückgestellt|verifiziert|bereinigt)\b", re.I)),
("version", re.compile(r"\bv?\d+\.\d+\.\d+\b|\b\d+\.\d+\.\d+-[\w.]+\b")),
("path-claim", re.compile(r"`[^`]*(?:/[^`]*|\.(?:md|ya?ml|json|py|ts|toml|crt))`")),
("date-claim", re.compile(r"\b(seit|Stand|bis|am|ab)\s+\d{4}-\d{2}-\d{2}\b", re.I)),
("mirror-topology", re.compile(
r"\b(Mirror|Spiegel|gespiegelt|Gitea|rohana|Push-Mirror|kanonisch|Flux-Source)\b",
re.I)),
("issue-ref", re.compile(r"(?:#\d+\b|/-/issues/\d+|\b[A-Z]{3,8}-\d{2}\b)")),
]
FENCE_RE = re.compile(r"^\s*(```|~~~)")
def main():
rows = []
docs = [f for f in tracked_files("management", MGMT_REPO)
if any(f.endswith(s) for s in DOC_SUFFIXES)]
for src in sorted(docs):
iso, _, _ = last_commit("management", MGMT_REPO, src)
text = (MGMT_REPO / src).read_text(encoding="utf-8")
in_fence = False
for lineno, line in enumerate(text.splitlines(), start=1):
if FENCE_RE.match(line):
in_fence = not in_fence
continue
stripped = line.strip()
if len(stripped) < 12:
continue
hits = [name for name, rx in TRIGGERS if rx.search(stripped)]
if not hits:
continue
rows.append([
src, lineno, ";".join(hits), "yes" if in_fence else "no",
iso, days_since(iso), cell(stripped)[:400],
])
rows.sort(key=lambda r: (r[0], r[1]))
write_tsv(
"claims.tsv",
["path", "line", "triggers", "in_code_block", "doc_last_commit_date",
"doc_days_since_change", "claim_text"],
rows,
)
if __name__ == "__main__":
main()