Requirement 001 phase 6, S0: - Speech: the security class shows as "Sécurité" and is spoken as in maritime radio, "say-cure-ee-tay" three times. A hex run of 7 or more characters with a letter and a digit (commit, session, message ids) is spelled in the NATO alphabet; every other digit is spoken on its own in ICAO pronunciation (42305: four two tree zero fife). The spelling runs inside clip() before the length cut, so a phrase still fits SPEECH_LIMIT; the displayed text keeps its digits and hashes. - English: every orchestrator label, tooltip, message, error, script output, document and test string is English (Regeln -> Firewall, Hierarchie -> Hierarchy, Steuerung -> Controls, Ausführen -> Run, ...); the page declares lang="en" and formats dates as en-GB. Task filter ids are English. German stays only in the skill trigger phrases that quote the operator, and as identifiers: the view keys (view=) and the leitstand id. - tests/test_english.py scans every tracked text file for umlauts and German words, with an allowlist of the quoted trigger phrases. BREAKING CHANGE: the Stream search tokens art: and seit: are now kind: and since:; an old art: or seit: token counts as a plain word. Evidence: make test 657 OK, ruff and markdownlint clean (/tmp/s0-make-test-1.log); e2e 17 OK, none skipped (/tmp/s0-e2e-1.log); MEDUSA 86 files, 24 findings on reworded strings accepted after an independent skeptic (21 path:line, 0 rejected, no behaviour change in signing, approving, rules, denying, offices, events), rescan 0 open. 🎯 Fix: 93 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: – 🛡️ Security: 100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
3.3 KiB
Python
65 lines
3.3 KiB
Python
"""Pin the repository to English: no German text outside the operator's quoted trigger phrases."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import subprocess
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
WORDS = (
|
|
"und", "nicht", "oder", "wird", "werden", "wurde", "kein", "keine", "keinen", "eine", "einen", "einem", "einer",
|
|
"ist", "sind", "auf", "für", "bitte", "noch", "schon", "alle", "aus", "zum", "zur", "bei", "beim", "nach", "über",
|
|
"dieser", "diese", "dieses", "jetzt", "heißt", "sich", "auch", "wenn", "dann", "mehr", "kann", "muss", "soll",
|
|
"gibt", "ohne", "vom", "von", "läuft", "wählen", "speichern", "löschen", "abbrechen", "schließen", "bearbeiten",
|
|
"anzeigen", "öffnen", "zeigen", "aufnehmen", "vorlesen", "regeln", "regel", "regelwunsch", "steuerung",
|
|
"ausführen", "ressourcen", "hierarchie", "freigabe", "freigeben", "ablehnen", "warten", "wartet", "offen",
|
|
"erledigt", "neu", "neuer", "angenommen", "abgelehnt", "gestoppt", "abgelaufen", "unbekannt",
|
|
"fehlgeschlagen", "verweigert", "besetzt", "abberufen", "aufgabe", "aufgaben", "entfernt", "entfernen", "gemergt",
|
|
"bleibt", "stoppt", "erzwingt", "verwirft", "hintergrund", "kategorien", "ansicht", "quelle", "namen", "suchen",
|
|
"anheften", "entscheiden", "entscheidungen", "nacht", "abend", "himmel", "sonnenuntergang", "sonnenaufgang",
|
|
"aufnahme", "nachricht", "nachrichten", "einstellungen", "sprache", "stumm", "fertig", "fehler", "gesperrt",
|
|
"signiert", "heute", "gestern", "stunden", "minuten", "sekunden", "warnung", "hinweis", "sofort", "zeit",
|
|
"weiter", "kopieren", "kopiert", "starten", "gestartet", "beenden", "beendet",
|
|
)
|
|
GERMAN = re.compile(r"[äöüÄÖÜß]|\b(?:" + "|".join(WORDS) + r")\b", re.IGNORECASE)
|
|
ALLOWED = (
|
|
'"rechtlich prüfen"', '"lizenz prüfen"', '"aufräumen"', '"branches aufräumen"', '"worktrees aufräumen"',
|
|
'"was soll ich tun"', '"was muss ich tun"', '"was fehlt noch"', '"welche skills gibt es"', '"skill übersicht"',
|
|
'"übersicht"', '"zuständigkeiten"', '"verwaiste branches"', '"alte worktrees"', '"was kann weg"', '"ä"',
|
|
"(Regeln → Firewall, Hierarchie → Hierarchy, Ressourcen → Resources,", "Steuerung → Controls, Ausführen → Run)",
|
|
)
|
|
SELF = Path(__file__).resolve().relative_to(REPO_ROOT).as_posix()
|
|
|
|
|
|
def german_lines() -> list[str]:
|
|
"""Returns:
|
|
Every `path:line` of a tracked text file that carries German once the
|
|
allowed trigger phrases are removed from it.
|
|
"""
|
|
files = subprocess.run(["git", "ls-files"], cwd=REPO_ROOT, capture_output=True, text=True, check=True).stdout.split()
|
|
hits = []
|
|
for name in files:
|
|
if name == SELF:
|
|
continue
|
|
try:
|
|
text = (REPO_ROOT / name).read_text(encoding="utf-8")
|
|
except (UnicodeDecodeError, IsADirectoryError, FileNotFoundError):
|
|
continue
|
|
for number, line in enumerate(text.splitlines(), 1):
|
|
for phrase in ALLOWED:
|
|
line = line.replace(phrase, "")
|
|
if GERMAN.search(line):
|
|
hits.append(f"{name}:{number}")
|
|
return hits
|
|
|
|
|
|
class TestEnglish(unittest.TestCase):
|
|
def test_no_german_outside_quoted_trigger_phrases(self):
|
|
self.assertEqual(german_lines(), [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("\n".join(german_lines()))
|