From a47327d99829c95769bcc211dcf793a4b9931fed Mon Sep 17 00:00:00 2001 From: Seekers2001 Date: Sat, 12 Sep 2026 17:49:13 +0800 Subject: [PATCH] fix(governance): harden ADR audit boundaries --- .../docs-governance/scripts/audit-docs.py | 7 ++--- skills/docs-governance/scripts/audit-docs.py | 7 ++--- tests/test_docs_governance_audit.py | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/.agents/skills/docs-governance/scripts/audit-docs.py b/.agents/skills/docs-governance/scripts/audit-docs.py index 53f35ee36..15f6c3468 100644 --- a/.agents/skills/docs-governance/scripts/audit-docs.py +++ b/.agents/skills/docs-governance/scripts/audit-docs.py @@ -344,8 +344,9 @@ def check_status_resurrection( resurrected: list[str] = [] if match: for value in CODE_PATH_RE.findall(match.group("body")): - if plausible_deletion_path(value): - resolved = resolve_within_root(root, root, value) + normalized = value.replace("\\", "/") + if plausible_deletion_path(normalized): + resolved = resolve_within_root(root, root, normalized) if resolved is not None and resolved.exists(): resurrected.append(value) if resurrected: @@ -435,7 +436,7 @@ def check_log( def parse_adr_status(text: str) -> str | None: patterns = ( - r"(?im)^(?:[-*]\s+)?(?:\*\*(?:status|\u72b6\u6001)\*\*|(?:status|\u72b6\u6001))\s*[:\uFF1A]\s*`?([a-z]+)`?(?:\s+by\s+ADR-\d+)?\s*$", + r"(?im)^(?:[-*]\s+)?(?:\*\*(?:status|\u72b6\u6001)\*\*|(?:status|\u72b6\u6001))\s*[:\uFF1A]\s*`?([a-z]+)`?(?:\s+by\s+ADR-\d{3,4})?\s*$", r"(?im)^##\s+(?:status|\u72b6\u6001)\s*\n+\s*`?([a-z]+)`?\s*$", ) for pattern in patterns: diff --git a/skills/docs-governance/scripts/audit-docs.py b/skills/docs-governance/scripts/audit-docs.py index 53f35ee36..15f6c3468 100644 --- a/skills/docs-governance/scripts/audit-docs.py +++ b/skills/docs-governance/scripts/audit-docs.py @@ -344,8 +344,9 @@ def check_status_resurrection( resurrected: list[str] = [] if match: for value in CODE_PATH_RE.findall(match.group("body")): - if plausible_deletion_path(value): - resolved = resolve_within_root(root, root, value) + normalized = value.replace("\\", "/") + if plausible_deletion_path(normalized): + resolved = resolve_within_root(root, root, normalized) if resolved is not None and resolved.exists(): resurrected.append(value) if resurrected: @@ -435,7 +436,7 @@ def check_log( def parse_adr_status(text: str) -> str | None: patterns = ( - r"(?im)^(?:[-*]\s+)?(?:\*\*(?:status|\u72b6\u6001)\*\*|(?:status|\u72b6\u6001))\s*[:\uFF1A]\s*`?([a-z]+)`?(?:\s+by\s+ADR-\d+)?\s*$", + r"(?im)^(?:[-*]\s+)?(?:\*\*(?:status|\u72b6\u6001)\*\*|(?:status|\u72b6\u6001))\s*[:\uFF1A]\s*`?([a-z]+)`?(?:\s+by\s+ADR-\d{3,4})?\s*$", r"(?im)^##\s+(?:status|\u72b6\u6001)\s*\n+\s*`?([a-z]+)`?\s*$", ) for pattern in patterns: diff --git a/tests/test_docs_governance_audit.py b/tests/test_docs_governance_audit.py index 72daa2688..d7f8b1ee8 100644 --- a/tests/test_docs_governance_audit.py +++ b/tests/test_docs_governance_audit.py @@ -400,6 +400,20 @@ def test_adr_scope_rejects_missing_inline_superseded_successor(project: Path) -> assert "ADR inline successor does not exist" in result.stdout +def test_adr_scope_rejects_short_inline_superseded_successor(project: Path) -> None: + adr_dir = project / "docs" / "adr" + adr_dir.mkdir(parents=True) + (adr_dir / "README.md").write_text( + "[decision](0001-storage.md)\n", encoding="utf-8" + ) + (adr_dir / "0001-storage.md").write_text( + "# ADR-0001\n\n**Status**: superseded by ADR-42\n", encoding="utf-8" + ) + result = run_audit(project, "adr") + assert result.returncode == 1 + assert "ADR has no parseable status" in result.stdout + + def test_adr_scope_audits_common_adr_prefixed_filenames(project: Path) -> None: adr_dir = project / "docs" / "adr" adr_dir.mkdir(parents=True) @@ -567,6 +581,19 @@ def test_spine_scope_detects_recreated_root_level_deletion_zone_file( assert "Deletion-zone target has been recreated" in result.stdout +def test_spine_scope_detects_windows_path_deletion_zone_file(project: Path) -> None: + target = project / "subdir" / "legacy.py" + target.parent.mkdir() + target.write_text("# recreated\n", encoding="utf-8") + (project / "PROJECT_STATUS.md").write_text( + "# Status\n\n## Deletion Zone\n\n- `subdir\\\\legacy.py`\n", + encoding="utf-8", + ) + result = run_audit(project, "spine") + assert result.returncode == 1 + assert "Deletion-zone target has been recreated" in result.stdout + + @pytest.mark.parametrize( "value", (