From 2c60b823b4a830f15ad339f5d7ceaf117a9527e7 Mon Sep 17 00:00:00 2001 From: jaylfc Date: Thu, 13 Aug 2026 17:06:23 +0000 Subject: [PATCH] doc-gate: treat rename (R) and copy (C) as structural triggers git --name-status reports renames as R100/R087/... and copies as C100/...; the parser truncates to the first character, so they arrive as R and C. Those statuses were silently ignored by the trigger set, meaning a renamed route module fired no rule and required no doc. Fix: add R and C to the trigger set alongside A and D. The satisfaction set (all_paths) is intentionally left as A/M only so that a renamed require_doc still does not count as a doc update. Tests added: - rename triggers rule by name - copy triggers rule by name - deletion still triggers (pinning) - rename does not satisfy require_doc (pinning) - rename plus added doc passes --- scripts/check_doc_gate.py | 16 +++++------ tests/test_check_doc_gate.py | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/scripts/check_doc_gate.py b/scripts/check_doc_gate.py index abd970363..87b5b71ed 100644 --- a/scripts/check_doc_gate.py +++ b/scripts/check_doc_gate.py @@ -9,13 +9,13 @@ scripts/, tinyagentos/, docs/, desktop/ path mentioned in the configured doc set actually exists on disk. diff-gate -- path -> doc rule engine (Layer B). A configured rule fires - when a *structural* change matches one of its `when_changed` - globs. By default only added/deleted files (status A/D) are - structural; set `on_modify = true` on a rule to also count - plain modifications (status M) for that rule. Any changed - file (any status) can *trigger* a rule. Only ADDED or MODIFIED - files (status A/M) can *satisfy* a rule by matching a - require_doc glob; a deleted require_doc does NOT count. + when a *structural* change matches one of its `when_changed` + globs. By default added, deleted, renamed, and copied files + (status A/D/R/C) are structural; set `on_modify = true` on a + rule to also count plain modifications (status M) for that + rule. Only ADDED or MODIFIED files (status A/M) can *satisfy* + a rule by matching a require_doc glob; a renamed, copied, or + deleted require_doc does NOT count. Config lives in docs/doc-gate.toml. Rules are data, not code: add more by editing the TOML, no changes to this file required. @@ -264,7 +264,7 @@ def evaluate_rules( rule_structural_paths = [ path for status, path in changed_status - if (status in ("A", "D") or (on_modify and status == "M")) + if (status in ("A", "D", "R", "C") or (on_modify and status == "M")) and not _is_test_path(path) ] diff --git a/tests/test_check_doc_gate.py b/tests/test_check_doc_gate.py index 82845e52c..544aa16a5 100644 --- a/tests/test_check_doc_gate.py +++ b/tests/test_check_doc_gate.py @@ -168,6 +168,59 @@ def test_on_modify_false_explicit_still_default(self): assert failures == [] +class TestEvaluateRulesRenameCopy: + """Rename (R) and copy (C) must trigger rules but must not satisfy require_doc.""" + + def test_rename_triggers_rule_by_name(self): + """A rename of a when_changed path must trigger the rule.""" + config = _base_config() + changed = [("R", "tinyagentos/routes/themes.py")] + failures = evaluate_rules(changed, [], config) + assert len(failures) == 1 + assert "test_route" in failures[0] + + def test_copy_triggers_rule_by_name(self): + """A copy of a when_changed path must trigger the rule.""" + config = _base_config() + changed = [("C", "tinyagentos/routes/themes.py")] + failures = evaluate_rules(changed, [], config) + assert len(failures) == 1 + assert "test_route" in failures[0] + + def test_deletion_still_triggers_rule(self): + """A deletion of a when_changed path must still trigger the rule (pinning).""" + config = _base_config() + changed = [("D", "tinyagentos/routes/themes.py")] + failures = evaluate_rules(changed, [], config) + assert len(failures) == 1 + assert "test_route" in failures[0] + + def test_rename_does_not_satisfy_require_doc(self): + """Renaming the require_doc does NOT satisfy it (pinning). + + If the satisfaction set were widened to include R, this would pass + silently and the assertion below would fail. + """ + config = _base_config() + changed = [ + ("R", "tinyagentos/routes/themes.py"), + ("R", "CHANGELOG.md"), + ] + failures = evaluate_rules(changed, [], config) + assert len(failures) == 1 + assert "test_route" in failures[0] + + def test_rename_with_doc_added_passes(self): + """A route rename with the required doc added passes.""" + config = _base_config() + changed = [ + ("R", "tinyagentos/routes/themes.py"), + ("A", "CHANGELOG.md"), + ] + failures = evaluate_rules(changed, [], config) + assert failures == [] + + class TestReferencedPathsScan: """Invariants layer: glob expansion, tombstones, extractor precision."""