"""检查 DevHarness 必需文件和任务归档的基本结构。""" from __future__ import annotations import argparse import re from pathlib import Path ROOT = Path(__file__).resolve().parents[1] REQUIRED_FILES = ( "AGENTS.md", "README.md", "docs/00-project-profile.md", "docs/01-workflow.md", "docs/templates/task-archive.md", ".gitea/issue_template/epic.md", ".gitea/issue_template/mvp.md", ".gitea/issue_template/task.md", ) ARCHIVE_HEADINGS = ( "## 背景与目标", "## 最终方案", "## 修改文件", "## 验收结果", "## 测试", "## 相关提交", ) def check_required_files(errors: list[str]) -> None: for relative_path in REQUIRED_FILES: if not (ROOT / relative_path).is_file(): errors.append(f"缺少必需文件:{relative_path}") def check_project_profile(errors: list[str], warnings: list[str], strict: bool) -> None: profile = ROOT / "docs" / "00-project-profile.md" if not profile.is_file(): return if "<填写" in profile.read_text(encoding="utf-8"): message = "项目档案仍有未填写内容" (errors if strict else warnings).append(message) def check_archives(errors: list[str]) -> None: task_dir = ROOT / "docs" / "task" for path in task_dir.glob("*.md"): if not re.match(r"^\d+-.+\.md$", path.name): errors.append(f"归档文件名不符合 <编号>-<标题>.md:{path.name}") content = path.read_text(encoding="utf-8") for heading in ARCHIVE_HEADINGS: if heading not in content: errors.append(f"{path.name} 缺少章节:{heading}") if "**未验证部分**:" not in content: errors.append(f"{path.name} 没有记录未验证部分") def main() -> int: parser = argparse.ArgumentParser(description="检查 DevHarness 项目结构") parser.add_argument( "--strict", action="store_true", help="项目档案有占位内容时返回失败", ) args = parser.parse_args() errors: list[str] = [] warnings: list[str] = [] check_required_files(errors) check_project_profile(errors, warnings, args.strict) check_archives(errors) for warning in warnings: print(f"警告:{warning}") for error in errors: print(f"错误:{error}") if errors: print(f"检查失败:{len(errors)} 个问题") return 1 print("DevHarness 检查通过") return 0 if __name__ == "__main__": raise SystemExit(main())