Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/duplication-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,13 @@ jobs:
#
# autobot_shared + autobot-slm-backend + autobot-slm-frontend/src: 4,729
# duplicated lines, 135 clones, 259,937 lines scanned (1.82%).
SLM_MAX_DUP_LINES: '4729'
#
# #16401: that measurement included the Ansible-mirrored SLM agent tree,
# now excluded from the scan below (see the "Gate the previously ungated
# trees" step). Re-measured with the mirror excluded on run 34632034006
# (base affea4b38, workflow/test/doc-only change): 2,945 duplicated lines,
# 129 clones, 258,231 lines scanned in 754 files.
SLM_MAX_DUP_LINES: '2945'
JSCPD_VERSION: '5.0.6'
steps:
- uses: actions/checkout@v7
Expand All @@ -119,6 +125,13 @@ jobs:
# figure for an UNMEASURED pin. The gate prints it on every run now, and an
# UNMEASURED pin fails with the figure to pin (#16319), so one jscpd run per
# scope is enough.
#
# #16401: autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/ is a
# mandatory mirror of autobot-slm-backend/slm/agent/, kept byte-identical by
# ansible/tests/detect_agent_code_drift_test.py. jscpd counted the mirror as
# a clone of its own source on every agent change, so it is excluded below --
# the drift test already guarantees the two trees cannot differ, so there is
# nothing here this guard can ask anyone to deduplicate.
- name: Gate the previously ungated trees
id: slm_scope
run: |
Expand All @@ -127,7 +140,7 @@ jobs:
autobot_shared autobot-slm-backend autobot-slm-frontend/src \
-k 70 -l 8 --skip-comments \
-f python,typescript,javascript,vue \
-i "**/node_modules/**,**/__pycache__/**,**/tests/**,**/test_*,**/*_test.py,**/*.test.ts,**/__tests__/**,**/*.stories.*,**/generated/**,**/migrations/**,**/mocks/**,**/i18n/**" \
-i "**/node_modules/**,**/__pycache__/**,**/tests/**,**/test_*,**/*_test.py,**/*.test.ts,**/__tests__/**,**/*.stories.*,**/generated/**,**/migrations/**,**/mocks/**,**/i18n/**,**/ansible/roles/slm_agent/files/**" \
-r console 2>&1 | tee /tmp/slm-gate.log
python3 scripts/duplication_gate.py /tmp/slm-gate.log "${SLM_MAX_DUP_LINES}"

Expand Down
7 changes: 7 additions & 0 deletions docs/developer/RATCHET_BASELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ declares `EXCLUDED_PREFIXES` **and** pins it with
scope and the hook's `exclude:` diverge. That pairing is what makes the exclusion
a decision rather than an accident.

`duplication-guard.yml`'s SLM scope is the same shape (#16401): it excludes
`autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/`, the Ansible
mirror that `ansible/tests/detect_agent_code_drift_test.py` already forces
byte-identical to its source, so jscpd stopped counting the mandatory copy as a
clone of the original. `duplication_guard_excludes_agent_mirror_test.py` fails
if that ignore entry disappears or widens past the one path.

### 3. Derive the population a second way before freezing it

Once, at freeze time, and again whenever the matcher changes — not a permanently
Expand Down
99 changes: 99 additions & 0 deletions repo_tests/duplication_guard_excludes_agent_mirror_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2025-2026 mrveiss
# SPDX-License-Identifier: Apache-2.0
"""The SLM scope's jscpd run must ignore the drift-checked agent mirror (#16401).

``autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/`` is a mandatory
Ansible-shipped mirror of ``autobot-slm-backend/slm/agent/``, kept byte-identical
by ``ansible/tests/detect_agent_code_drift_test.py``. Before #16401, the SLM
scope's jscpd invocation had no ignore for it, so every change to agent code
(made in both copies, because the drift test requires it) grew the clone count
and tripped the absolute pin (#16319) -- 4,740 duplicated lines measured against
a 4,729 pin on merge train g (#16392).

These tests read the ignore list off the real workflow step, translate its glob
to a regex, and check it against real paths -- rather than asserting a literal
substring -- so the test still catches a *widened* pattern that happens to keep
the old text as a substring (e.g. broadening to ``**/ansible/**``).
"""

from __future__ import annotations

import re
from pathlib import Path

import yaml
from repo_tests._paths import repo_root

WORKFLOW = Path(".github/workflows/duplication-guard.yml")

#: The path the drift test forces byte-identical to its source (#16401).
MIRROR_PATH = "autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/health_collector.py"
#: The canonical source of that mirror -- real duplication-guard work, must stay scanned.
CANONICAL_SOURCE_PATH = "autobot-slm-backend/slm/agent/health_collector.py"
#: A different Ansible role's mirrored files -- an ignore "widened beyond that path" would
#: also start skipping this, and nothing guards ITS drift.
SIBLING_ROLE_MIRROR_PATH = "autobot-slm-backend/ansible/roles/backend/files/permission_rules.yaml"


def _slm_scope_run() -> str:
document = yaml.safe_load((repo_root() / WORKFLOW).read_text(encoding="utf-8"))
job = document["jobs"][next(iter(document["jobs"]))]
for step in job["steps"]:
if step.get("id") == "slm_scope":
return step["run"]
raise AssertionError("no step with id 'slm_scope' in duplication-guard.yml")


def _ignore_patterns() -> list[str]:
"""The comma-separated globs passed to jscpd's ``-i`` flag, as a list."""
run = _slm_scope_run()
match = re.search(r'-i "([^"]+)"', run)
assert match, 'slm_scope step has no -i "..." ignore list'
return match.group(1).split(",")


def _glob_to_regex(pattern: str) -> re.Pattern:
"""Minimal ``**``/``*`` glob translator, matched against real repo-relative paths.

``**`` becomes ``.*`` (crosses directory boundaries, jscpd/micromatch semantics);
a bare ``*`` becomes ``[^/]*`` (stays within one path segment). Everything else is
a literal, escaped segment-by-segment so a literal ``/`` never becomes part of a
regex quantifier.
"""
segments = [re.escape(part).replace(r"\*", "[^/]*") if part != "**" else ".*" for part in pattern.split("/")]
return re.compile("^" + "/".join(segments) + "$")


def test_the_slm_scope_has_an_ignore_matching_the_drift_checked_mirror() -> None:
"""AC1: the mirror the drift test guards must be excluded, not just something near it."""
patterns = _ignore_patterns()
matching = [p for p in patterns if _glob_to_regex(p).match(MIRROR_PATH)]
assert matching, (
f"no ignore glob in the SLM scope matches {MIRROR_PATH!r} -- "
f"the drift-checked mirror is being scanned as duplication again"
)


def test_the_mirror_ignore_does_not_also_swallow_the_canonical_source() -> None:
"""The canonical agent tree is real code; excluding it would hide actual duplication."""
patterns = _ignore_patterns()
matching = [p for p in patterns if _glob_to_regex(p).match(CANONICAL_SOURCE_PATH)]
assert not matching, (
f"an SLM-scope ignore glob ({matching}) also matches the canonical source "
f"{CANONICAL_SOURCE_PATH!r} -- too broad, it would hide real duplication there"
)


def test_the_mirror_ignore_does_not_widen_to_a_sibling_roles_files() -> None:
"""AC3: a widened glob (e.g. ``**/ansible/**``) must fail this test.

A sibling role's mirrored files have no drift test of their own, so excluding
them from jscpd would be a silent, undeclared exemption -- exactly what
RATCHET_BASELINES.md rule 1 says a ratchet must not carry.
"""
patterns = _ignore_patterns()
matching = [p for p in patterns if _glob_to_regex(p).match(SIBLING_ROLE_MIRROR_PATH)]
assert not matching, (
f"an SLM-scope ignore glob ({matching}) also matches {SIBLING_ROLE_MIRROR_PATH!r} -- "
f"the mirror exclusion has widened past the one path #16401 justified"
)
Loading