test(config): add POLICY-CONFIG-004 hard gate for user-facing .env.example comments (v1.0.48)
All checks were successful
技能自动化发布 / release (push) Successful in 8s

This commit is contained in:
2026-07-17 19:06:21 +08:00
parent 5ec9648148
commit 144455fbe0
6 changed files with 114 additions and 2 deletions

View File

@@ -29,6 +29,7 @@ POLICY_IDS = (
"POLICY-CONFIG-001",
"POLICY-CONFIG-002",
"POLICY-CONFIG-003",
"POLICY-CONFIG-004",
"POLICY-TESTING-001",
"POLICY-TESTING-002",
"POLICY-RPA-001",
@@ -86,6 +87,22 @@ CONFIG_ENV_FORBIDDEN_PATTERNS = (
re.compile(r"\benviron\.get\b"),
)
CONFIG_004_SOURCE = "development/CONFIG.md §.env.example 注释 = 配置管理用户文案"
CONFIG_004_ENV_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
CONFIG_004_CJK_RE = re.compile(r"[\u4e00-\u9fff]")
# 注释中禁止出现的开发向指向 / 黑话(大小写不敏感子串)
CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS = (
"development/",
"adapter.md",
"rpa.md",
"data_paths.md",
"config.md",
"account-manager",
"见 development",
"adapter 档位",
"adapter档位",
)
TESTING_REAL_TARGET_ALLOWED_ROOT_TESTS = frozenset({"test_adapter_profile_policy.py"})
TESTING_001_FORBIDDEN_SUBSTRINGS = (
@@ -149,6 +166,77 @@ def _policy_msg(policy_id: str, source: str, detail: str) -> str:
return f"{policy_id} [{source}]: {detail}"
def _strip_env_quotes(value: str) -> str:
trimmed = value.strip()
if (
(trimmed.startswith('"') and trimmed.endswith('"'))
or (trimmed.startswith("'") and trimmed.endswith("'"))
):
return trimmed[1:-1]
return trimmed
def _iter_env_example_active_entries(path: str):
"""Yield (lineno, key, value, header_comments, inline_comment) for active KEY= lines."""
pending: list[str] = []
with open(path, encoding="utf-8") as handle:
for lineno, line in enumerate(handle, 1):
stripped = line.strip()
if not stripped:
pending = []
continue
if stripped.startswith("#"):
pending.append(stripped.lstrip("#").strip())
continue
eq = stripped.find("=")
if eq <= 0:
pending = []
continue
key = stripped[:eq].strip()
if not CONFIG_004_ENV_KEY_RE.match(key):
pending = []
continue
rest = stripped[eq + 1 :]
inline: str | None = None
if not rest.startswith('"') and not rest.startswith("'"):
hash_at = rest.find(" #")
if hash_at >= 0:
inline = rest[hash_at + 2 :].strip()
rest = rest[:hash_at]
value = _strip_env_quotes(rest)
yield lineno, key, value, list(pending), inline
pending = []
def _config_004_comment_offenders(path: str) -> list[str]:
offenders: list[str] = []
for lineno, key, _value, headers, inline in _iter_env_example_active_entries(path):
if not headers:
offenders.append(
f".env.example:{lineno}: {key}= 缺少行头注释(须用中文说明「是什么」)"
)
elif not any(CONFIG_004_CJK_RE.search(part) for part in headers):
offenders.append(
f".env.example:{lineno}: {key}= 行头注释须含中文(说明「是什么」)"
)
if not inline:
offenders.append(
f".env.example:{lineno}: {key}= 缺少行尾注释(须用中文说明「怎么填」)"
)
elif not CONFIG_004_CJK_RE.search(inline):
offenders.append(
f".env.example:{lineno}: {key}= 行尾注释须含中文(说明「怎么填」)"
)
combined = "\n".join([*headers, inline or ""]).lower()
for token in CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS:
if token.lower() in combined:
offenders.append(
f".env.example:{lineno}: {key}= 注释禁止含 {token!r}(面向用户,勿写开发文档指向/黑话)"
)
break
return offenders
def _rel(skill_root: str, path: str) -> str:
return os.path.relpath(path, skill_root).replace("\\", "/")
@@ -385,6 +473,23 @@ class TestPolicyConfig003(unittest.TestCase):
)
class TestPolicyConfig004(unittest.TestCase):
def test_env_example_comments_are_user_facing(self) -> None:
path = os.path.join(get_skill_root(), ".env.example")
self.assertTrue(os.path.isfile(path), msg=".env.example missing")
offenders = _config_004_comment_offenders(path)
self.assertEqual(
offenders,
[],
msg=_policy_msg(
"POLICY-CONFIG-004",
CONFIG_004_SOURCE,
"\n".join(offenders)
or "every active KEY in .env.example needs Chinese header+inline comments",
),
)
class TestPolicyTesting001(unittest.TestCase):
def test_root_tests_do_not_hardcode_real_targets(self) -> None:
skill_root = get_skill_root()