run_all.sh reproduces every file under analysis/data/ from zero: it clones the in-scope components if missing, exports group issue metadata from git.lab, and regenerates the inventories. Reruns are diff-clean -- no wall-clock time enters an output; 'days since' is measured against the management repo's HEAD date. The management repo is inventoried at main, not at the analysis branch, so this analysis does not observe its own commits. inv_repo.py aborts the run if anything outside analysis/ was modified.
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
"""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()
|