mirror of
https://github.com/wshobson/agents.git
synced 2026-09-14 18:04:20 +08:00
fix(garden): scope agent name normalization to the frontmatter block
Review feedback on PR #676. The substitution used a MULTILINE pattern with count=1, which lands on the frontmatter name in every agent the repo has today but would blank a body `name:` line in an agent whose frontmatter lacked one. Normalization is now scoped to the opening frontmatter block, so a `name:` in the body stays content and still counts toward a divergence. Three tests cover differing body names, matching body names, and a file with no frontmatter. No change to current output: still 11 divergent, 0 verbatim reported.
This commit is contained in:
+18
-2
@@ -55,8 +55,10 @@ COUNT_RE = re.compile(r"\b(\d+)\s+(plugins|agents|subagents|skills|commands)\b")
|
||||
COUNT_NOUN_ALIASES = {"subagents": "agents"}
|
||||
|
||||
# An agent's frontmatter `name:` is plugin-namespaced, so two copies of the same
|
||||
# agent can never hash alike. Normalize it away before comparing bodies.
|
||||
# agent can never hash alike. Normalize it away before comparing bodies, scoped to
|
||||
# the opening frontmatter block so a `name:` line in the body still counts as content.
|
||||
AGENT_NAME_LINE_RE = re.compile(r"^name:.*$", re.MULTILINE)
|
||||
AGENT_FRONTMATTER_RE = re.compile(r"\A(---\n)(.*?)(\n---\n)", re.DOTALL)
|
||||
|
||||
|
||||
# ── Findings ─────────────────────────────────────────────────────────────────
|
||||
@@ -392,6 +394,20 @@ def check_marketplace_consistency(report: Report) -> None:
|
||||
)
|
||||
|
||||
|
||||
def normalized_agent_text(text: str) -> str:
|
||||
"""Blank the plugin-namespaced frontmatter `name:` so sibling copies compare equal.
|
||||
|
||||
Only the opening frontmatter block is touched. A `name:` line in the body is real
|
||||
content and must still count toward a divergence.
|
||||
"""
|
||||
match = AGENT_FRONTMATTER_RE.match(text)
|
||||
if not match:
|
||||
return text
|
||||
opener, frontmatter, closer = match.groups()
|
||||
frontmatter = AGENT_NAME_LINE_RE.sub("name:", frontmatter, count=1)
|
||||
return f"{opener}{frontmatter}{closer}{text[match.end() :]}"
|
||||
|
||||
|
||||
def actual_counts() -> dict[str, int]:
|
||||
"""Live component totals, counted the same way the adapters discover them.
|
||||
|
||||
@@ -451,7 +467,7 @@ def check_agent_divergence(report: Report) -> None:
|
||||
continue
|
||||
bodies: dict[str, list[Path]] = defaultdict(list)
|
||||
for path in paths:
|
||||
normalized = AGENT_NAME_LINE_RE.sub("name:", path.read_text(encoding="utf-8"), count=1)
|
||||
normalized = normalized_agent_text(path.read_text(encoding="utf-8"))
|
||||
bodies[hashlib.md5(normalized.encode("utf-8")).hexdigest()].append(path)
|
||||
|
||||
if len(bodies) > 1:
|
||||
|
||||
@@ -412,6 +412,43 @@ class TestAgentDivergence:
|
||||
assert finding.severity == "warning"
|
||||
assert "2 copies in 2 different versions" in finding.message
|
||||
|
||||
def test_body_name_lines_still_count_as_content(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""Only the frontmatter name is normalized; a `name:` in the body is real content."""
|
||||
_patch_paths(monkeypatch, tmp_path)
|
||||
_write_agent(tmp_path, "alpha", "reviewer.md", "Example config:\n\nname: alpha-thing\n")
|
||||
_write_agent(tmp_path, "beta", "reviewer.md", "Example config:\n\nname: beta-thing\n")
|
||||
|
||||
report = Report()
|
||||
check_agent_divergence(report)
|
||||
assert [f.kind for f in report.findings] == ["AGENT_BODY_DIVERGENT"]
|
||||
|
||||
def test_body_name_lines_matching_stay_verbatim(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""Same body `name:` plus differing frontmatter names is still a verbatim copy."""
|
||||
_patch_paths(monkeypatch, tmp_path)
|
||||
_write_agent(tmp_path, "alpha", "reviewer.md", "Example config:\n\nname: shared\n")
|
||||
_write_agent(tmp_path, "beta", "reviewer.md", "Example config:\n\nname: shared\n")
|
||||
|
||||
report = Report()
|
||||
check_agent_divergence(report)
|
||||
assert report.findings == []
|
||||
|
||||
def test_agent_without_frontmatter_does_not_crash(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
_patch_paths(monkeypatch, tmp_path)
|
||||
for plugin in ("alpha", "beta"):
|
||||
agents_dir = tmp_path / "plugins" / plugin / "agents"
|
||||
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||
(agents_dir / "bare.md").write_text("No frontmatter here.\n")
|
||||
|
||||
report = Report()
|
||||
check_agent_divergence(report)
|
||||
assert report.findings == []
|
||||
|
||||
def test_groups_variants_in_message(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Three copies sharing two bodies report as 3 copies / 2 versions."""
|
||||
_patch_paths(monkeypatch, tmp_path)
|
||||
|
||||
Reference in New Issue
Block a user