2026-08-08 09:00:44 +08:00
|
|
|
|
"""检查 DevHarness 必需文件、核心文档和任务归档的基本结构。"""
|
2026-08-07 23:11:25 +08:00
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
import re
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
2026-08-07 23:57:13 +08:00
|
|
|
|
from wiki_docs import WikiDocsError, load_config, parse_mirror
|
|
|
|
|
|
|
2026-08-07 23:11:25 +08:00
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
2026-08-08 09:00:44 +08:00
|
|
|
|
CORE_PAGE_PATHS = {
|
|
|
|
|
|
"Home": "docs/README.md",
|
|
|
|
|
|
"Project-Profile": "docs/00-project-profile.md",
|
|
|
|
|
|
"Development-Workflow": "docs/01-workflow.md",
|
|
|
|
|
|
"Architecture-and-Code-Map": "docs/02-architecture-and-code-map.md",
|
|
|
|
|
|
"Business-Rules-and-Glossary": "docs/03-business-rules-and-glossary.md",
|
|
|
|
|
|
"Local-Development-and-Verification": (
|
|
|
|
|
|
"docs/04-local-development-and-verification.md"
|
|
|
|
|
|
),
|
|
|
|
|
|
"Common-Changes": "docs/05-common-changes.md",
|
|
|
|
|
|
"Troubleshooting": "docs/06-troubleshooting.md",
|
|
|
|
|
|
"New-Project-Documentation-Setup": (
|
|
|
|
|
|
"docs/07-new-project-documentation-setup.md"
|
|
|
|
|
|
),
|
2026-08-10 15:01:15 +08:00
|
|
|
|
"Existing-Project-Adoption-Guide": (
|
|
|
|
|
|
"docs/08-existing-project-adoption.md"
|
|
|
|
|
|
),
|
2026-08-10 14:25:34 +08:00
|
|
|
|
"Delivery-Documentation-Guide": "docs/delivery/README.md",
|
|
|
|
|
|
"Audience-Document-Template": (
|
|
|
|
|
|
"docs/delivery/audience-document-template.md"
|
|
|
|
|
|
),
|
2026-08-08 09:00:44 +08:00
|
|
|
|
"Task-Archive-Template": "docs/templates/task-archive.md",
|
|
|
|
|
|
}
|
|
|
|
|
|
CORE_DOCUMENT_REQUIREMENTS = {
|
|
|
|
|
|
"docs/README.md": (
|
|
|
|
|
|
"## 第一次阅读",
|
|
|
|
|
|
"## 五分钟开始",
|
|
|
|
|
|
"## 简单修改从哪里开始",
|
|
|
|
|
|
"## 事实来源",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/00-project-profile.md": (
|
|
|
|
|
|
"## 基本信息",
|
2026-08-10 19:05:07 +08:00
|
|
|
|
"## 子项目与交付单元",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
"## 技术栈与运行环境",
|
|
|
|
|
|
"## 阅读入口",
|
|
|
|
|
|
"## 常用命令",
|
|
|
|
|
|
"## 环境、配置与凭据",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/01-workflow.md": (
|
|
|
|
|
|
"## 面向初级维护者的修改边界",
|
|
|
|
|
|
"## 每个任务的文档影响",
|
2026-08-10 11:53:38 +08:00
|
|
|
|
"## 需求记录与流转",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
"## 稳定文档与任务归档",
|
2026-08-10 01:09:13 +08:00
|
|
|
|
"## 自然语言快捷指令",
|
2026-08-08 09:38:12 +08:00
|
|
|
|
"## 效率与范围控制",
|
|
|
|
|
|
"### 严格控制范围",
|
|
|
|
|
|
"### 渐进执行和修复",
|
|
|
|
|
|
"### 复用已验证事实",
|
|
|
|
|
|
"### 明确停止条件",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
),
|
|
|
|
|
|
"docs/02-architecture-and-code-map.md": (
|
|
|
|
|
|
"## 项目定位",
|
|
|
|
|
|
"## 代码地图",
|
|
|
|
|
|
"## 两条主要执行路径",
|
|
|
|
|
|
"## 不可破坏的边界",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/03-business-rules-and-glossary.md": (
|
|
|
|
|
|
"## 核心术语",
|
|
|
|
|
|
"## 工单状态",
|
|
|
|
|
|
"## 稳定业务规则",
|
|
|
|
|
|
"## 新项目需要补充什么",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/04-local-development-and-verification.md": (
|
|
|
|
|
|
"## 环境要求",
|
|
|
|
|
|
"## 第一次运行",
|
|
|
|
|
|
"## 常用调试方式",
|
|
|
|
|
|
"## 完成修改前",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/05-common-changes.md": (
|
|
|
|
|
|
"## 风险分级",
|
|
|
|
|
|
"## 修改 Wiki 文案",
|
|
|
|
|
|
"## 调整 Harness 检查",
|
|
|
|
|
|
"## 看懂 Agent 的修改",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/06-troubleshooting.md": (
|
|
|
|
|
|
"## 排查顺序",
|
|
|
|
|
|
"## 必须停止的情况",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/07-new-project-documentation-setup.md": (
|
|
|
|
|
|
"## 初始化顺序",
|
2026-08-16 19:30:22 +08:00
|
|
|
|
"### 2. 选择建设基线",
|
|
|
|
|
|
"### 3. 识别子项目与交付单元",
|
|
|
|
|
|
"### 7. 确定交付对象和文档",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
"## 完成标准",
|
|
|
|
|
|
),
|
2026-08-10 15:01:15 +08:00
|
|
|
|
"docs/08-existing-project-adoption.md": (
|
|
|
|
|
|
"## 与新项目初始化的区别",
|
|
|
|
|
|
"## 接入前只读盘点",
|
|
|
|
|
|
"## 已有内容保护原则",
|
2026-08-10 19:05:07 +08:00
|
|
|
|
"## 多应用单仓库判断",
|
|
|
|
|
|
"### 适合继续单仓库",
|
|
|
|
|
|
"### 可以考虑拆仓",
|
|
|
|
|
|
"### 保持单仓库时的最小规则",
|
2026-08-10 15:01:15 +08:00
|
|
|
|
"## 增量接入顺序",
|
|
|
|
|
|
"## 冲突处理和停止条件",
|
|
|
|
|
|
"## 可复制 Agent 指令",
|
|
|
|
|
|
"### 只分析",
|
|
|
|
|
|
"### 方案确认后实施",
|
|
|
|
|
|
"## 最小验收清单",
|
|
|
|
|
|
"## 回退原则",
|
|
|
|
|
|
),
|
2026-08-10 14:25:34 +08:00
|
|
|
|
"docs/delivery/README.md": (
|
|
|
|
|
|
"## 什么时候需要交付文档",
|
|
|
|
|
|
"## 受众与文档选择",
|
|
|
|
|
|
"## 内部文档与交付文档边界",
|
|
|
|
|
|
"## 编写和维护流程",
|
|
|
|
|
|
"## 最小验收清单",
|
|
|
|
|
|
),
|
|
|
|
|
|
"docs/delivery/audience-document-template.md": (
|
|
|
|
|
|
"## 文档信息",
|
|
|
|
|
|
"## 目的与适用范围",
|
|
|
|
|
|
"## 前置条件",
|
|
|
|
|
|
"## 操作步骤",
|
|
|
|
|
|
"## 常见错误与恢复",
|
|
|
|
|
|
"## 安全与权限",
|
|
|
|
|
|
"## 已知限制",
|
|
|
|
|
|
"## 支持与升级处理",
|
|
|
|
|
|
"## 版本记录",
|
|
|
|
|
|
"## 交付前检查",
|
|
|
|
|
|
),
|
2026-08-08 09:00:44 +08:00
|
|
|
|
}
|
2026-08-07 23:11:25 +08:00
|
|
|
|
REQUIRED_FILES = (
|
|
|
|
|
|
"AGENTS.md",
|
2026-08-08 10:15:24 +08:00
|
|
|
|
"CLAUDE.md",
|
2026-08-07 23:11:25 +08:00
|
|
|
|
"README.md",
|
|
|
|
|
|
"docs/00-project-profile.md",
|
|
|
|
|
|
"docs/01-workflow.md",
|
|
|
|
|
|
"docs/templates/task-archive.md",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
*CORE_DOCUMENT_REQUIREMENTS,
|
2026-08-07 23:57:13 +08:00
|
|
|
|
"wiki-docs.json",
|
2026-08-08 09:18:09 +08:00
|
|
|
|
"dev_scripts/wiki_docs.py",
|
|
|
|
|
|
"dev_scripts/sync_wiki_docs.py",
|
2026-08-16 19:21:36 +08:00
|
|
|
|
"dev_scripts/export_task_archives.py",
|
2026-08-07 23:11:25 +08:00
|
|
|
|
".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")
|
2026-08-16 19:21:36 +08:00
|
|
|
|
try:
|
|
|
|
|
|
metadata, _ = parse_mirror(content)
|
|
|
|
|
|
except WikiDocsError as exc:
|
|
|
|
|
|
errors.append(f"{path.name} 的任务镜像无效:{exc}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
if re.fullmatch(r"Task-\d+-.+", metadata.get("wiki_page", "")) is None:
|
|
|
|
|
|
errors.append(f"{path.name} 的 wiki_page 不是任务归档页面")
|
|
|
|
|
|
if re.fullmatch(r"[0-9a-f]{40,64}", metadata.get("wiki_revision", "")) is None:
|
|
|
|
|
|
errors.append(f"{path.name} 的 wiki_revision 无效")
|
2026-08-07 23:11:25 +08:00
|
|
|
|
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} 没有记录未验证部分")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 09:00:44 +08:00
|
|
|
|
def missing_sections(content: str, required: tuple[str, ...]) -> list[str]:
|
|
|
|
|
|
return [section for section in required if section not in content]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_core_documents(errors: list[str], root: Path = ROOT) -> None:
|
|
|
|
|
|
"""检查初级维护者所需主题页的固定结构。"""
|
|
|
|
|
|
|
|
|
|
|
|
for relative_path, required in CORE_DOCUMENT_REQUIREMENTS.items():
|
|
|
|
|
|
path = root / relative_path
|
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
_, body = parse_mirror(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
except (OSError, UnicodeDecodeError, WikiDocsError):
|
|
|
|
|
|
continue
|
|
|
|
|
|
for section in missing_sections(body, required):
|
|
|
|
|
|
errors.append(f"{relative_path} 缺少核心章节:{section}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_task_template(errors: list[str], root: Path = ROOT) -> None:
|
|
|
|
|
|
path = root / ".gitea" / "issue_template" / "task.md"
|
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
|
return
|
|
|
|
|
|
content = path.read_text(encoding="utf-8")
|
|
|
|
|
|
required = (
|
2026-08-10 00:30:31 +08:00
|
|
|
|
"## 依赖与并行",
|
|
|
|
|
|
"- 前置工单:无 / #编号",
|
|
|
|
|
|
"- 是否允许与前置工单并行:是 / 否",
|
|
|
|
|
|
"- 原因:",
|
2026-08-10 19:05:07 +08:00
|
|
|
|
"## 子项目影响",
|
|
|
|
|
|
"- 仅影响的子项目 / 交付单元:",
|
|
|
|
|
|
"- 是否跨子项目:是 / 否",
|
|
|
|
|
|
"- 是否修改共享接口或契约:是 / 否;唯一事实来源:",
|
|
|
|
|
|
"- 各子项目需要执行的验证:",
|
2026-08-10 11:53:38 +08:00
|
|
|
|
"## 原始需求",
|
|
|
|
|
|
"- 来源:用户对话 / Gitea / 其他",
|
|
|
|
|
|
"- 提出时间:",
|
|
|
|
|
|
"- 关键原话或脱敏摘要:",
|
|
|
|
|
|
"## 需求变化记录",
|
|
|
|
|
|
"| 日期 | 变化内容 | 原因 | 用户确认 |",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
"## 文档影响",
|
|
|
|
|
|
"- [ ] 不影响长期文档,原因:",
|
|
|
|
|
|
"- [ ] 更新架构与代码地图",
|
|
|
|
|
|
"- [ ] 更新业务规则与术语",
|
|
|
|
|
|
"- [ ] 更新常见修改或故障排查",
|
2026-08-10 14:25:34 +08:00
|
|
|
|
"## 交付文档影响",
|
|
|
|
|
|
"- [ ] 无交付文档影响,原因:",
|
|
|
|
|
|
"- [ ] 更新已有交付文档,受众与页面:",
|
|
|
|
|
|
"- [ ] 新增交付文档,受众与页面:",
|
|
|
|
|
|
"- [ ] 需要目标岗位或客户代表验证:是 / 否;验证方式:",
|
2026-08-08 09:00:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
for section in missing_sections(content, required):
|
|
|
|
|
|
errors.append(f"单元任务模板缺少:{section}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 09:38:12 +08:00
|
|
|
|
def check_agent_efficiency_rules(errors: list[str], root: Path = ROOT) -> None:
|
|
|
|
|
|
path = root / "AGENTS.md"
|
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
|
return
|
|
|
|
|
|
content = path.read_text(encoding="utf-8")
|
|
|
|
|
|
required = (
|
|
|
|
|
|
"### 效率与范围控制",
|
|
|
|
|
|
"#### 严格控制范围",
|
|
|
|
|
|
"#### 渐进执行和修复",
|
|
|
|
|
|
"#### 复用已验证事实",
|
|
|
|
|
|
"#### 明确停止条件",
|
2026-08-08 10:03:38 +08:00
|
|
|
|
"单元任务是唯一正式实施单位",
|
|
|
|
|
|
"高风险修改必须停止",
|
|
|
|
|
|
"用户没有明确验收通过前不得关闭",
|
2026-08-16 19:21:36 +08:00
|
|
|
|
"长期核心文档必须先修改 Wiki",
|
2026-08-08 10:03:38 +08:00
|
|
|
|
"提交只包含当前工单相关文件",
|
2026-08-10 01:09:13 +08:00
|
|
|
|
"### 自然语言快捷指令",
|
|
|
|
|
|
"`只分析`",
|
|
|
|
|
|
"`建工单`",
|
|
|
|
|
|
"`执行工单 #N`",
|
|
|
|
|
|
"`建工单并做`",
|
|
|
|
|
|
"`继续工单 #N`",
|
|
|
|
|
|
"`检查工单 #N`",
|
|
|
|
|
|
"`同步文档`",
|
2026-08-16 19:21:36 +08:00
|
|
|
|
"`导出任务归档`",
|
|
|
|
|
|
"`导出全部任务归档`",
|
2026-08-10 01:09:13 +08:00
|
|
|
|
"`#N 验收通过`",
|
2026-08-10 11:53:38 +08:00
|
|
|
|
"### 需求记录与流转",
|
|
|
|
|
|
"不得臆造用户原话",
|
|
|
|
|
|
"不复制完整聊天",
|
|
|
|
|
|
"Gitea 工单全文不导出到仓库",
|
2026-08-08 09:38:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
for section in missing_sections(content, required):
|
|
|
|
|
|
errors.append(f"AGENTS.md 缺少:{section}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 10:15:24 +08:00
|
|
|
|
def check_claude_code_entry(errors: list[str], root: Path = ROOT) -> None:
|
|
|
|
|
|
"""检查 Claude Code 入口直接复用共同 Agent 规则。"""
|
|
|
|
|
|
|
|
|
|
|
|
path = root / "CLAUDE.md"
|
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
|
return
|
|
|
|
|
|
content = path.read_text(encoding="utf-8")
|
|
|
|
|
|
lines = {line.strip() for line in content.splitlines()}
|
|
|
|
|
|
if "@AGENTS.md" not in lines:
|
|
|
|
|
|
errors.append("CLAUDE.md 缺少独立的 @AGENTS.md 导入")
|
|
|
|
|
|
required = (
|
|
|
|
|
|
"共同规则事实来源",
|
|
|
|
|
|
"只记录 Claude Code 特有",
|
|
|
|
|
|
"只修改 `AGENTS.md`",
|
2026-08-08 11:18:06 +08:00
|
|
|
|
"## 模型路由",
|
|
|
|
|
|
"## Agent 交接",
|
|
|
|
|
|
"## Haiku 只读约束",
|
|
|
|
|
|
"当前模型足以完成任务时不升级模型",
|
|
|
|
|
|
"Opus 输出方案后必须等待用户确认",
|
|
|
|
|
|
"不让 Haiku 决定最终根因",
|
|
|
|
|
|
"只读必须通过子 Agent 工具权限实现",
|
2026-08-08 10:15:24 +08:00
|
|
|
|
)
|
|
|
|
|
|
for section in missing_sections(content, required):
|
|
|
|
|
|
errors.append(f"CLAUDE.md 缺少:{section}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 09:02:48 +08:00
|
|
|
|
def core_mapping_errors(configured_mappings: dict[str, str]) -> list[str]:
|
|
|
|
|
|
errors: list[str] = []
|
|
|
|
|
|
for page, expected_path in CORE_PAGE_PATHS.items():
|
|
|
|
|
|
if configured_mappings.get(page) != expected_path:
|
|
|
|
|
|
errors.append(
|
|
|
|
|
|
f"核心 Wiki 页面映射缺失或路径错误:{page} -> {expected_path}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return errors
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-07 23:57:13 +08:00
|
|
|
|
def check_wiki_mirrors(errors: list[str]) -> None:
|
2026-08-16 19:21:36 +08:00
|
|
|
|
"""检查核心映射与镜像头;任务快照由 check_archives 单独检查。"""
|
2026-08-07 23:57:13 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
config = load_config()
|
|
|
|
|
|
except WikiDocsError as exc:
|
|
|
|
|
|
errors.append(str(exc))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-08-08 09:00:44 +08:00
|
|
|
|
configured_mappings = {mapping.page: mapping.path for mapping in config.mappings}
|
2026-08-08 09:02:48 +08:00
|
|
|
|
errors.extend(core_mapping_errors(configured_mappings))
|
2026-08-08 09:00:44 +08:00
|
|
|
|
|
2026-08-07 23:57:13 +08:00
|
|
|
|
mapped_paths = {mapping.path for mapping in config.mappings}
|
|
|
|
|
|
actual_paths = {
|
|
|
|
|
|
path.relative_to(ROOT).as_posix() for path in (ROOT / "docs").rglob("*.md")
|
2026-08-16 19:21:36 +08:00
|
|
|
|
if path.parent != ROOT / "docs" / "task"
|
2026-08-07 23:57:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
for path in sorted(actual_paths - mapped_paths):
|
|
|
|
|
|
errors.append(f"docs 中存在未登记的 Wiki 镜像:{path}")
|
|
|
|
|
|
|
|
|
|
|
|
for mapping in config.mappings:
|
|
|
|
|
|
path = ROOT / mapping.path
|
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
|
errors.append(f"缺少 Wiki 镜像:{mapping.path}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
metadata, _ = parse_mirror(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
except (OSError, UnicodeDecodeError, WikiDocsError) as exc:
|
|
|
|
|
|
errors.append(f"Wiki 镜像无效 {mapping.path}:{exc}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
if metadata.get("generated") != "true (请先修改 Gitea Wiki,禁止直接编辑本文件)":
|
|
|
|
|
|
errors.append(f"{mapping.path} 没有只读镜像标记")
|
|
|
|
|
|
if metadata.get("wiki_page") != mapping.page:
|
|
|
|
|
|
errors.append(f"{mapping.path} 的 wiki_page 与映射不一致")
|
|
|
|
|
|
revision = metadata.get("wiki_revision", "")
|
|
|
|
|
|
if re.fullmatch(r"[0-9a-f]{40,64}", revision) is None:
|
|
|
|
|
|
errors.append(f"{mapping.path} 的 wiki_revision 无效")
|
|
|
|
|
|
if not metadata.get("synchronized_at"):
|
|
|
|
|
|
errors.append(f"{mapping.path} 缺少 synchronized_at")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-07 23:11:25 +08:00
|
|
|
|
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)
|
2026-08-07 23:57:13 +08:00
|
|
|
|
check_wiki_mirrors(errors)
|
2026-08-08 09:00:44 +08:00
|
|
|
|
check_core_documents(errors)
|
|
|
|
|
|
check_task_template(errors)
|
2026-08-08 09:38:12 +08:00
|
|
|
|
check_agent_efficiency_rules(errors)
|
2026-08-08 10:15:24 +08:00
|
|
|
|
check_claude_code_entry(errors)
|
2026-08-07 23:11:25 +08:00
|
|
|
|
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())
|