Files
Yb-QaAutomationHub/scripts/governance_audit.py
T
xst e34a3c64ce feat: knowledge sync — 安徽运八需求知识沉淀回写知识库
知识库更新:
- historical_defects.md: +4条数据上报领域真实缺陷模式
  (阶段依赖链断裂/重试幂等/跨模块数据不一致/省份代码硬编码)
- common_missed_scenes.md: +11条数据上报专项易漏场景
  (多阶段依赖/第三方核验逐项/重试并发/跨模块一致性/省份隔离等)
- data_reporting_cases.md: 新增数据上报类需求优秀用例范式
  (8大覆盖框架+5条示例用例+7项关键风险点)
- terminology.md: +14条数据上报领域术语

系统修复:
- case_pipeline.py: 注册 data_reporting_cases.md + order_manage_cases.md
- governance_audit.py: 更新 case_generate/AGENTS 关键词期望(旧→新架构)
- AGENTS.md/README.md/.claude: 补全缺失关键词,治理审计通过
- .claude/skills/case_generate/: 创建向后兼容别名 skill
2026-07-13 14:22:31 +08:00

245 lines
7.0 KiB
Python

import argparse
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
WORKFLOW_TRIGGER_PATHS = {
"AGENTS.md",
"README.md",
"操作手册.md",
"scripts/case_pipeline.py",
"scripts/export_excel.py",
"scripts/governance_audit.py",
"knowledge_base/01_standards/test_case_template.md",
"knowledge_base/01_standards/definition_of_done.md",
"knowledge_base/01_standards/review_checklist.md",
"knowledge_base/01_standards/terminology.md",
"knowledge_base/01_standards/terminology_optional_saas.md",
}
WORKFLOW_TRIGGER_PREFIXES = (
".claude/",
"agents/",
"scripts/",
"knowledge_base/01_standards/",
)
STRUCTURE_TRIGGER_PREFIXES = (
".claude/",
"agents/",
"scripts/",
"knowledge_base/01_standards/",
)
IGNORED_PREFIXES = (
"output/",
"requirements/",
"knowledge_base/02_history/",
"knowledge_base/03_best_practices/",
)
CONSISTENCY_EXPECTATIONS = {
"AGENTS.md": [
"fleet_runner.py",
"case_pipeline.py",
"output/manifests/{BASE_NAME}.json",
"knowledge_base/00_project/project_profile.md",
"knowledge_base/01_standards/review_checklist.md",
"normalized_requirement_file",
],
".claude/commands/case_generate.md": [
"fleet_runner.py",
"qe-fleet run",
"case_generate",
"output/manifests/{BASE_NAME}.json",
],
".claude/skills/case_generate/SKILL.md": [
"fleet_runner.py",
"qe-fleet run",
"case_generate",
"output/manifests/{BASE_NAME}.json",
],
".claude/instructions.md": [
"scripts/case_pipeline.py",
"effective_terminology_files",
"knowledge_base/00_project/project_profile.md",
"knowledge_base/01_standards/review_checklist.md",
"normalized_requirement_file",
],
"README.md": [
"output/manifests/{BASE_NAME}.json",
"术语文件按需求内容自动识别生效范围",
"knowledge_base/00_project/project_profile.md",
"review_checklist.md",
"source_docs/requirements_raw",
"output/versions/{BASE_NAME}/v1/",
"VERSION_INDEX.md",
"最近 `3` 个版本",
],
"操作手册.md": [
"terminology_optional_saas.md",
"自动识别",
"project_profile.md",
"review_checklist.md",
"source_docs/",
"output/versions/{BASE_NAME}/v1/",
"snapshot_meta.json",
"最近 `3` 个版本",
],
}
def read_text(relative_path: str) -> str:
return (REPO_ROOT / relative_path).read_text(encoding="utf-8")
def git_status_paths() -> list[tuple[str, str]]:
try:
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=REPO_ROOT,
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as exc:
raise RuntimeError(exc.stderr.strip() or "无法读取 git status。") from exc
items: list[tuple[str, str]] = []
for raw_line in result.stdout.splitlines():
if not raw_line:
continue
status = raw_line[:2]
payload = raw_line[3:]
path = payload.split(" -> ")[-1].strip()
if path:
items.append((status, path))
return items
def matches_any_prefix(path: str, prefixes: tuple[str, ...]) -> bool:
return any(path.startswith(prefix) for prefix in prefixes)
def should_ignore(path: str) -> bool:
return matches_any_prefix(path, IGNORED_PREFIXES)
def is_workflow_trigger(path: str) -> bool:
return path in WORKFLOW_TRIGGER_PATHS or matches_any_prefix(path, WORKFLOW_TRIGGER_PREFIXES)
def is_structure_trigger(path: str, status: str) -> bool:
if "?" in status or "A" in status or "D" in status or "R" in status:
return matches_any_prefix(path, STRUCTURE_TRIGGER_PREFIXES)
return False
def classify_changed_files(items: list[tuple[str, str]]) -> dict[str, list[str]]:
workflow_hits: list[str] = []
structure_hits: list[str] = []
ignored_hits: list[str] = []
for status, path in items:
if should_ignore(path):
ignored_hits.append(path)
continue
if is_workflow_trigger(path):
workflow_hits.append(path)
if is_structure_trigger(path, status):
structure_hits.append(path)
return {
"workflow_hits": sorted(set(workflow_hits)),
"structure_hits": sorted(set(structure_hits)),
"ignored_hits": sorted(set(ignored_hits)),
}
def audit_consistency() -> list[str]:
issues: list[str] = []
for relative_path, snippets in CONSISTENCY_EXPECTATIONS.items():
content = read_text(relative_path)
for snippet in snippets:
if snippet not in content:
issues.append(f"{relative_path} 缺少关键片段: {snippet}")
return issues
def print_trigger_report(classification: dict[str, list[str]]) -> bool:
workflow_hits = classification["workflow_hits"]
structure_hits = classification["structure_hits"]
should_run = bool(workflow_hits or structure_hits)
print(f"should_run={str(should_run).lower()}")
if workflow_hits:
print("workflow_hits:")
for path in workflow_hits:
print(f" - {path}")
if structure_hits:
print("structure_hits:")
for path in structure_hits:
print(f" - {path}")
return should_run
def command_should_run() -> int:
classification = classify_changed_files(git_status_paths())
should_run = print_trigger_report(classification)
return 0 if should_run else 1
def command_audit() -> int:
issues = audit_consistency()
if issues:
print("audit_status=fail")
for issue in issues:
print(f"- {issue}")
return 1
print("audit_status=pass")
return 0
def command_auto() -> int:
classification = classify_changed_files(git_status_paths())
should_run = print_trigger_report(classification)
if not should_run:
print("audit_skipped=true")
return 0
issues = audit_consistency()
if issues:
print("audit_status=fail")
for issue in issues:
print(f"- {issue}")
return 1
print("audit_status=pass")
return 0
def main() -> int:
parser = argparse.ArgumentParser(description="流程一致性与文档滞后审计。")
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("should-run", help="根据当前 git 变更判断是否需要触发治理审计。")
subparsers.add_parser("audit", help="执行一致性与文档滞后检查。")
subparsers.add_parser("auto", help="自动判断是否触发,并在需要时执行审计。")
args = parser.parse_args()
if args.command == "should-run":
return command_should_run()
if args.command == "audit":
return command_audit()
if args.command == "auto":
return command_auto()
return 1
if __name__ == "__main__":
sys.exit(main())