Skip to content

Commit 640d47a

Browse files
authored
[infra] Add AGENTS.md version freshness check (#903)
1 parent beaee3f commit 640d47a

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ jobs:
4747
echo "$HOME/.local/bin" >> $GITHUB_PATH
4848
- name: Check code style
4949
run: ./tools/lint.sh -c
50+
- name: Check AGENTS.md freshness
51+
run: python3 tools/check-agents-md.py
5052

5153
install_sh_tests:
5254
name: install.sh tests (${{ matrix.os }})

tools/check-agents-md.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
################################################################################
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#################################################################################
19+
"""Assert the version facts quoted in AGENTS.md still match their sources.
20+
21+
AGENTS.md hard-codes the supported Python range, the Ruff line length, and the
22+
Java version. Those values live authoritatively in python/pyproject.toml and the
23+
root pom.xml, so the prose drifts silently when they are bumped. This check
24+
derives the expected phrasing from the sources and fails if AGENTS.md no longer
25+
contains it, forcing the guide to be updated alongside the source of truth.
26+
27+
Regex-only and dependency-free so it runs on any Python 3 without a build step.
28+
"""
29+
30+
import re
31+
import sys
32+
from pathlib import Path
33+
34+
REPO_ROOT = Path(__file__).resolve().parent.parent
35+
AGENTS_MD = REPO_ROOT / "AGENTS.md"
36+
PYPROJECT = REPO_ROOT / "python" / "pyproject.toml"
37+
POM_XML = REPO_ROOT / "pom.xml"
38+
39+
40+
def _search(pattern: str, text: str, source: Path) -> str:
41+
match = re.search(pattern, text)
42+
if not match:
43+
sys.exit(f"error: could not find {pattern!r} in {source}")
44+
return match.group(1)
45+
46+
47+
def expected_phrases() -> list[tuple[str, str]]:
48+
"""Return (phrase, source-description) pairs AGENTS.md must contain."""
49+
pyproject = PYPROJECT.read_text(encoding="utf-8")
50+
pom = POM_XML.read_text(encoding="utf-8")
51+
52+
requires_python = _search(r'requires-python\s*=\s*"([^"]+)"', pyproject, PYPROJECT)
53+
lower = re.search(r">=\s*(\d+)\.(\d+)", requires_python)
54+
upper = re.search(r"<\s*(\d+)\.(\d+)", requires_python)
55+
if not lower or not upper:
56+
sys.exit(f"error: cannot parse requires-python {requires_python!r} in {PYPROJECT}")
57+
min_python = f"{lower.group(1)}.{lower.group(2)}"
58+
# Upper bound is exclusive, so the highest supported minor is one below it.
59+
max_python = f"{upper.group(1)}.{int(upper.group(2)) - 1}"
60+
61+
line_length = _search(r"line-length\s*=\s*(\d+)", pyproject, PYPROJECT)
62+
java_version = _search(r"<target\.java\.version>(\d+)</target\.java\.version>", pom, POM_XML)
63+
64+
return [
65+
(f"{min_python} through {max_python}", f"requires-python in {PYPROJECT.name}"),
66+
(f"{line_length}-character line length", f"ruff line-length in {PYPROJECT.name}"),
67+
(f"Java {java_version}", f"target.java.version in {POM_XML.name}"),
68+
]
69+
70+
71+
def main() -> int:
72+
agents_md = AGENTS_MD.read_text(encoding="utf-8")
73+
failures = [
74+
(phrase, source)
75+
for phrase, source in expected_phrases()
76+
if phrase not in agents_md
77+
]
78+
if failures:
79+
print("AGENTS.md is stale relative to its sources:", file=sys.stderr)
80+
for phrase, source in failures:
81+
print(f" - expected {phrase!r} (from {source})", file=sys.stderr)
82+
print(
83+
"\nUpdate AGENTS.md to match, or adjust this check if the fact moved.",
84+
file=sys.stderr,
85+
)
86+
return 1
87+
print("AGENTS.md version facts match their sources.")
88+
return 0
89+
90+
91+
if __name__ == "__main__":
92+
raise SystemExit(main())

0 commit comments

Comments
 (0)