diff --git a/docs/METRICS.md b/docs/METRICS.md index 0f74d2d..043391b 100644 --- a/docs/METRICS.md +++ b/docs/METRICS.md @@ -79,9 +79,10 @@ Always populated. These are the foundational signal/noise proxy. **`stabilization_ratio`** — of all files touched in the window, the fraction that had *no* subsequent modification within `churn_days` of any -prior touch. Files touched exactly once count as stabilized. Core quality +prior touch. Files touched exactly once count as stabilized. Core rework signal: closer to `1.0` = changes persist; closer to `0.0` = constant -rework. +rework. Persistence means a file was not revisited within the window — it +is not evidence of correctness (unreviewed or defective code persists too). **`churn_events`** — count of files modified 2+ times with at least one consecutive pair of modifications ≤ `churn_days` apart. diff --git a/docs/ONE-PAGER.md b/docs/ONE-PAGER.md index 68f5b5b..94a030e 100644 --- a/docs/ONE-PAGER.md +++ b/docs/ONE-PAGER.md @@ -69,7 +69,7 @@ iris /path/to/repo --push ## Who It's For - **Engineering leaders** who need to understand AI's impact on delivery quality -- **Platform teams** evaluating which AI tools produce the most durable code +- **Platform teams** understanding how AI-assisted changes hold up after they land - **FinOps / compliance** preparing for EU AI Act requirements (August 2026) --- diff --git a/iris/i18n.py b/iris/i18n.py index 2bfee96..20fb183 100644 --- a/iris/i18n.py +++ b/iris/i18n.py @@ -129,7 +129,7 @@ "finding_stabilization_high": ( "Stabilization ratio is {ratio} — " "the majority of changes persisted without further modification. " - "This suggests durable delivery." + "Persistence indicates low rework, not verified correctness." ), "finding_revert_high": ( "Revert rate is {rate} " @@ -326,7 +326,7 @@ "Stabilization ratio per change type: " "Feature {feat_ratio}, Fix {fix_ratio}, Refactor {refactor_ratio}. " "Comparing stability across intents reveals which types of changes " - "produce durable outcomes and which require further iteration." + "are revisited soon after landing and which are not." ), # CLI @@ -430,7 +430,7 @@ "trend_finding_stabilization_up": ( "Stabilization improved by {delta}pp in the last {recent} days " "compared to the {baseline}-day baseline. " - "This suggests delivery is becoming more durable." + "Less of the recent code is being reworked." ), "trend_finding_churn_up": ( "Churn rate increased by {delta}pp recently. " @@ -1007,7 +1007,7 @@ "finding_stabilization_high": ( "A taxa de estabilização é {ratio} — " "a maioria das alterações persistiu sem modificação posterior. " - "Isso sugere entrega durável." + "Persistência indica pouco retrabalho, não correção verificada." ), "finding_revert_high": ( "A taxa de revert é {rate} " @@ -1206,7 +1206,7 @@ "Taxa de estabilização por tipo de mudança: " "Feature {feat_ratio}, Fix {fix_ratio}, Refactor {refactor_ratio}. " "Comparar a estabilidade entre intenções revela quais tipos de mudanças " - "produzem resultados duráveis e quais requerem iteração adicional." + "são revisitadas logo após entrarem e quais não são." ), # CLI @@ -1311,7 +1311,7 @@ "trend_finding_stabilization_up": ( "A estabilização melhorou {delta}pp nos últimos {recent} dias " "comparado à baseline de {baseline} dias. " - "Isso sugere que a entrega está se tornando mais durável." + "Menos do código recente está sendo retrabalhado." ), "trend_finding_churn_up": ( "A taxa de churn aumentou {delta}pp recentemente. " diff --git a/tests/test_i18n_parity.py b/tests/test_i18n_parity.py new file mode 100644 index 0000000..df0c45a --- /dev/null +++ b/tests/test_i18n_parity.py @@ -0,0 +1,92 @@ +"""Structural guards for the i18n string tables. + +The narrative formats strings by key with named placeholders, so a key +missing from one language is a runtime KeyError and a placeholder missing +from one language is a runtime format error — neither is caught by any +other test. These guards fail before a report does. + +The last test is a wording guard for Absolute Rule #4 (metrics are +hypotheses): the three stabilization strings must not infer durability from +persistence. Rewording them is a deliberate act — update that test in the +same change. + +Runnable as a plain script: `python tests/test_i18n_parity.py`. +""" + +import re +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from iris.i18n import get_strings + +_LANGS = ("en", "pt-br") +_PLACEHOLDER = re.compile(r"\{(\w+)\}") + + +def _placeholders(text: str) -> set[str]: + return set(_PLACEHOLDER.findall(text)) + + +def test_every_language_has_the_same_keys() -> None: + key_sets = {lang: set(get_strings(lang)) for lang in _LANGS} + reference = key_sets["en"] + for lang, keys in key_sets.items(): + missing = reference - keys + extra = keys - reference + assert not missing, f"{lang} is missing keys: {sorted(missing)}" + assert not extra, f"{lang} has extra keys: {sorted(extra)}" + + +def test_every_key_has_the_same_placeholders_in_every_language() -> None: + en = get_strings("en") + mismatches = [] + for lang in _LANGS[1:]: + other = get_strings(lang) + for key, text in en.items(): + if not isinstance(text, str) or not isinstance(other.get(key), str): + continue + if _placeholders(text) != _placeholders(other[key]): + mismatches.append((key, lang, _placeholders(text), _placeholders(other[key]))) + assert not mismatches, f"placeholder drift: {mismatches}" + + +def test_stabilization_findings_format_in_every_language() -> None: + # The keys rewritten to drop the durability over-inference must still + # accept exactly the arguments narrative.py passes them. + for lang in _LANGS: + s = get_strings(lang) + assert s["finding_stabilization_high"].format(ratio="85%") + assert s["finding_stabilization_low"].format(ratio="40%", unstable_pct="60%") + assert s["trend_finding_stabilization_up"].format(delta="3", recent=30, baseline=90) + assert s["explain_intent_stability_body"].format( + feat_ratio="80%", fix_ratio="60%", refactor_ratio="90%", + ) + + +def test_stabilization_findings_do_not_claim_durability() -> None: + # Persistence is not evidence of correctness; the wording must not imply it. + for lang in _LANGS: + s = get_strings(lang) + for key in ("finding_stabilization_high", "trend_finding_stabilization_up", + "explain_intent_stability_body"): + assert "durable" not in s[key].lower(), (lang, key) + assert "durável" not in s[key].lower(), (lang, key) + assert "duráveis" not in s[key].lower(), (lang, key) + + +if __name__ == "__main__": + tests = [fn for name, fn in globals().items() if name.startswith("test_")] + failed = 0 + for fn in tests: + try: + fn() + print(f"ok {fn.__name__}") + except AssertionError as exc: + failed += 1 + print(f"FAIL {fn.__name__}: {exc}") + if failed: + print(f"\n{failed} failure(s)") + sys.exit(1) + print(f"\n{len(tests)} tests passed")