chore: initialize DevHarness template

This commit is contained in:
QiuSW
2026-08-07 23:11:53 +08:00
commit d6706f7c69
13 changed files with 588 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
## 背景
<!-- 为什么要做这个产品或长期能力。 -->
## 目标
- <!-- 填写 -->
## 非目标
- <!-- 填写 -->
## 总体方案
<!-- 只写稳定的架构和关键决策,不复制子任务细节。 -->
## 阶段路线
1. <!-- 填写 -->
## MVP 与任务索引
- [ ] # MVP 工单
## 依赖、风险和回退
- <!-- 填写 -->
## 最终验收标准
- [ ] <!-- 填写 -->
+27
View File
@@ -0,0 +1,27 @@
## 基本信息
- 所属 Epic:#
## MVP 目标
<!-- 这个版本交付后,用户能够完成什么。 -->
## 包含范围
- <!-- 填写 -->
## 排除范围
- <!-- 填写 -->
## 阶段与单元任务
- [ ] # 单元任务
## 集成风险和回退
- <!-- 填写 -->
## MVP 验收标准
- [ ] <!-- 填写 -->
+36
View File
@@ -0,0 +1,36 @@
## 基本信息
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 阶段:
## 要解决什么
<!-- 描述现状和目标。缺陷需要写清复现步骤、实际结果和期望结果。 -->
## 做什么 / 不做什么
- 做:
- 不做:
## 已确认方案
<!-- 写清修改范围、关键设计,以及是否影响接口、数据库和安全边界。 -->
预计修改文件:
- <!-- 填写 -->
## 验收标准
- [ ] <!-- 填写 -->
- [ ] <!-- 填写 -->
## 验证方式
<!-- 写出可复制的命令;需要真机、生产环境或人工检查时明确说明。 -->
## 风险和回退
<!-- 普通低风险任务可删除本节;涉及接口、迁移、安全或不可逆操作时必填。 -->
+20
View File
@@ -0,0 +1,20 @@
# 本地环境与凭据
.env
.env.*
!.env.example
*.local
# 编辑器与系统文件
.idea/
.vscode/
.DS_Store
Thumbs.db
# 常见构建、测试缓存
__pycache__/
.pytest_cache/
.coverage
coverage/
dist/
build/
node_modules/
+105
View File
@@ -0,0 +1,105 @@
# Agent 开发规则
本仓库采用 DevHarness 工作流:Gitea 工单是实施期间的事实来源,Git 是代码变更记录,`docs/task` 是完成后的最终归档。人负责确认方案与验收,Agent 负责检查、实现、测试和留下证据。
开始工作前先阅读 [项目档案](docs/00-project-profile.md) 和任务涉及目录中的 `AGENTS.md`。目录越深的规则越具体,但不得削弱上级安全规则。
## 1. 永久规则
- 不把密码、令牌、Cookie、私钥、个人数据或生产数据写入代码、日志、工单和文档。
- 不执行未经用户明确授权的发布、付款、删除数据、破坏性迁移或其他不可逆操作。
- 保留用户已有和任务无关的工作区改动,不擅自重置、覆盖或混入提交。
- 发现需求与安全规则、已确认方案或现有数据冲突时,先停止实施并说明影响。
- 测试结果必须真实;未执行或无法覆盖的验证必须明确记录。
项目专用红线写入本文件的“项目专用规则”或对应子目录的 `AGENTS.md`,不要散落在聊天记录中。
## 2. 哪些改动需要工单
新功能、缺陷修复、重构以及任何用户可感知或改变程序行为的修改,必须先建立单元任务工单。
以下小改动可以直接提交,不要求工单和任务归档:
- 只改错别字、注释或文档措辞;
- 只做格式化、导入排序或不跨文件的内部变量改名;
- 补充类型标注或文档字符串且不改变行为;
- 删除已经确认无人使用的死代码。
只要涉及接口、数据库、状态、权限、安全、并发、用户界面,或者无法确定是否改变行为,就必须建工单。
## 3. 需求到实施
1. 先复述目标,阅读相关代码、日志和文档,区分事实与假设。
2. 给出目标、非目标、方案、影响范围、风险、回退方式和验证方法。
3. 方案没有得到用户明确确认前,只做只读诊断和方案整理,不实施正式代码。
4. 方案确认后,先建立单元任务工单,再修改代码。
5. 开始实施前检查分支和工作区,明确哪些现有改动不属于本任务。
6. 严格按工单范围实现;新发现的问题先记录,不顺手混入当前任务。
7. 执行与风险相称的测试,把关键结果和未验证部分更新到工单。
Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明确授权,不得默认绕过建单。
## 4. 工单层级
```text
Epic:完整产品目标和长期路线
└── MVP:第一个可交付版本
└── 单元任务:唯一的实施单位
```
- Epic 维护总体目标、范围、路线、风险和所有任务索引。
- MVP 维护首个可交付范围、阶段、集成风险和任务清单。
- 单元任务记录具体方案、修改范围、验收标准、过程和测试结果。
- 父工单只维护 `- [ ] #编号` 或 `- [x] #编号` 的索引和汇总,不复制子工单全文。
- 新任务先建单元工单,再把编号同步到所属 MVP 和 Epic。
工单模板位于 `.gitea/issue_template/`。
## 5. 实施中的变化
- 范围、接口、数据结构、依赖、验收标准或风险变化时,先更新单元工单。
- 变化影响 MVP 或 Epic 时,同时更新父工单。
- 会改变用户已确认结果的变化,更新工单后必须再次等待用户确认。
- 阻塞、失败方案和新发现根因不能只留在聊天或代码注释中。
- 工单状态应使用:待确认、待实施、进行中、阻塞、待验收、已完成。
## 6. Git 与验证
- 提交只包含当前工单相关文件。
- 实现提交信息引用工单号,例如:`fix: 修复登录超时 (#123)`。
- 不为流程制造空提交。
- 优先运行项目档案中记录的格式检查、静态检查、单元测试和必要的集成测试。
- 不能验证的真机、生产、迁移或并发行为必须写入工单和归档。
## 7. 完成、验收和归档
1. 实现完成后逐项检查验收标准,并提交代码。
2. 更新单元工单:最终方案、方案差异、测试结果、提交哈希和遗留问题。
3. 工单保持“待验收”,用户没有明确验收通过前不得关闭。
4. 按 `docs/templates/task-archive.md` 创建 `docs/task/<编号>-<短标题>.md`。
5. 归档文档单独提交,例如:`docs: 归档任务 #123`。
6. 把归档路径和提交哈希回写工单。
7. 用户验收通过后关闭单元工单,并同步更新 MVP 和 Epic。
MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才能关闭 MVP。Epic 的全部范围完成后才能关闭 Epic。
## 8. 可维护性
- 优先使用直白、常见的实现;不要为了少写几行引入晦涩技巧。
- 类和函数保持单一职责,名称表达业务含义。
- 注释解释原因、边界和风险,不逐行翻译代码。
- 错误必须可定位,不静默吞掉失败。
- 文档先写结论和用途,再写步骤;示例命令应可直接复制。
- 面向初级维护者说明从哪里开始读、怎样运行和怎样验证。
## 9. 引导提交例外
从本模板创建全新仓库时,Gitea 远端和工单尚不存在,允许一次不带工单号的初始引导提交。该提交只能包含仓库骨架、Harness 规则和远端配置准备,不能包含产品功能。
远端建立并推送后,这个例外立即失效。
## 10. 项目专用规则
<!-- 在项目初始化时填写不可违反的技术、安全和业务约束。复杂子项目请在其目录中增加 AGENTS.md。 -->
- 尚未配置。开始产品开发前必须填写项目档案,并删除本行。
+55
View File
@@ -0,0 +1,55 @@
# DevHarness
DevHarness 是一个以 Gitea 工单为事实来源、以 Git 提交为变更记录、由人负责确认和验收的 AI 辅助开发模板。
它约束的是开发过程,不限制项目使用 Python、Go、JavaScript 或其他技术栈。
## 工作闭环
```text
讨论需求或缺陷
-> 阅读代码并提出方案
-> 人工确认方案
-> 创建 Epic / MVP / 单元任务工单
-> Agent 实现并测试
-> 提交代码并更新工单
-> 人工验收
-> 归档 docs/task
-> 关闭工单并更新父工单
```
## 快速开始
1. 复制或克隆本仓库,并修改仓库名称。
2. 填写 [项目档案](docs/00-project-profile.md),特别是 Gitea 地址、仓库名和验证命令。
3. 把项目不可违反的安全规则写入根目录或子项目的 `AGENTS.md`。
4. 创建 Gitea 远端仓库并推送当前引导提交。
5. 使用 `.gitea/issue_template/` 中的模板创建第一个 Epic、MVP 和单元任务。
6. 开始产品代码前运行:
```powershell
python scripts/check_harness.py --strict
```
新仓库在 Gitea 尚未建立前允许一次不关联工单的引导提交。远端和工单系统配置完成后,所有改变程序行为的工作都必须先有单元任务工单。
## 目录
```text
AGENTS.md Agent 的通用工作规则
.gitea/issue_template/ Epic、MVP、单元任务工单模板
docs/00-project-profile.md 每个项目需要填写的档案
docs/01-workflow.md 人和 Agent 都能阅读的流程说明
docs/templates/task-archive.md 完成后的本地归档模板
docs/task/ 已完成任务的最终记录
scripts/check_harness.py 模板和归档的最小自检
scripts/new_task_archive.py 创建任务归档文件
```
## 设计原则
- 人决定目标、范围和验收结果,Agent 负责检查、实现和验证。
- 工单记录实施过程,`docs/task` 只保存完成后的最终事实。
- 一个单元工单只解决一个可独立测试和回退的问题。
- 实现提交与归档提交分开,便于审查与追溯。
- 凭据、个人数据和生产数据不得进入代码、工单或归档。
+50
View File
@@ -0,0 +1,50 @@
# 项目档案
复制模板后先填写本页。这里保存不经常变化、所有维护者都需要知道的信息。
## 基本信息
| 项目 | 内容 |
|---|---|
| 项目名称 | `<填写>` |
| 一句话目标 | `<填写>` |
| Gitea 地址 | `<例如 https://gitea.example.com>` |
| 仓库 | `<owner/repository>` |
| 默认分支 | `main` |
| 主要维护者 | `<填写>` |
## 技术栈
| 部分 | 技术 | 规则文件 |
|---|---|---|
| `<子项目或服务>` | `<语言、框架、版本>` | `<路径/AGENTS.md>` |
## 常用命令
所有命令默认从仓库根目录执行。
| 用途 | 命令 | 预期结果 |
|---|---|---|
| 安装依赖 | `<填写>` | `<填写>` |
| 启动开发环境 | `<填写>` | `<填写>` |
| 格式检查 | `<填写>` | `<填写>` |
| 静态检查 | `<填写>` | `<填写>` |
| 单元测试 | `<填写>` | `<填写>` |
| 集成测试 | `<填写或写“不适用”>` | `<填写>` |
## 目录边界
| 目录 | 职责 | 不应放入 |
|---|---|---|
| `<路径>` | `<填写>` | `<填写>` |
## 环境与凭据
- 本地配置文件:`<填写>`
- 配置示例文件:`<填写>`
- 凭据保存位置:`<只写保存方式,不填写真实凭据>`
- 日志和构建产物位置:`<填写>`
## 项目专用验收要求
- `<填写>`
+72
View File
@@ -0,0 +1,72 @@
# 开发工作流
## 一次任务怎样完成
### 1. 讨论
用户描述需求或故障。Agent 先检查现状,再给出目标、非目标、方案、风险、回退和验证方法。存在不同实现方向时,说明取舍,让用户确认。
### 2. 建单
方案确认后,使用 `.gitea/issue_template/task.md` 创建单元任务工单。没有工单号之前不修改产品代码。
新产品或较大版本先建立 Epic,再建立 MVP:
```text
[Epic] 产品或长期目标
└── [MVP] 第一个可交付版本
├── #101 单元任务
├── #102 单元任务
└── #103 单元任务
```
每个单元任务都应目标单一,能够独立测试、提交和回退。
### 3. 实施
Agent 检查工作区,只修改工单范围内的文件。发现新问题时先记录到工单;如果它不影响当前验收,另建工单,不扩大当前任务。
重要进度应及时写回工单:
- 已确认的根因;
- 方案或范围变化;
- 测试结果;
- 阻塞和未验证内容;
- Git 提交哈希。
### 4. 待验收
实现和测试完成后,Agent 提交代码并将工单更新为待验收。用户验收前工单保持开启。
### 5. 归档和关闭
使用以下命令创建归档草稿:
```powershell
python scripts/new_task_archive.py 123 "修复登录超时"
```
填写实际结果后单独提交归档,再把路径和提交哈希写回工单。用户明确验收通过后,关闭单元工单并勾选父工单中的任务。
## 什么时候重新确认方案
以下变化必须先更新工单,再由用户确认:
- 交付结果或用户操作发生变化;
- 增加或删除接口、数据库字段或迁移;
- 安全边界、权限或不可逆操作发生变化;
- 原方案不可行,需要更换主要技术路线;
- 任务范围明显扩大。
普通内部实现细节不需要反复确认,但重要取舍应记录在工单中。
## 工单与文档分别写什么
| 信息 | Gitea 工单 | `docs/task` |
|---|---:|---:|
| 讨论过程和临时方案 | 是 | 否 |
| 实施进度和阻塞 | 是 | 否 |
| 最终实现方案 | 是 | 是 |
| 测试结果与未验证内容 | 是 | 是 |
| 提交哈希 | 是 | 是 |
| 长期有效的最终结论 | 可链接 | 是 |
+8
View File
@@ -0,0 +1,8 @@
# 文档索引
- [项目档案](00-project-profile.md):仓库、技术栈、命令和负责人等稳定信息。
- [开发工作流](01-workflow.md):从需求讨论到工单关闭的完整顺序。
- [任务归档模板](templates/task-archive.md):任务完成后的固定格式。
- `task/`:已经完成并与代码版本对应的任务记录。
临时进度、方案讨论和待办事项写入 Gitea 工单,不写进长期文档。
+1
View File
@@ -0,0 +1 @@
+40
View File
@@ -0,0 +1,40 @@
# <工单号> <标题>
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:YYYY-MM-DD
- Gitea 工单:<链接>
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
+87
View File
@@ -0,0 +1,87 @@
"""检查 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())
+56
View File
@@ -0,0 +1,56 @@
"""根据模板创建任务归档草稿。"""
from __future__ import annotations
import argparse
import re
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
TEMPLATE = ROOT / "docs" / "templates" / "task-archive.md"
TASK_DIR = ROOT / "docs" / "task"
def safe_title(title: str) -> str:
"""把标题转换为适合文件名的短文本。"""
cleaned = re.sub(r'[<>:"/\\|?*]', "-", title.strip())
cleaned = re.sub(r"\s+", "-", cleaned)
return cleaned.strip(".-")
def create_archive(issue_number: str, title: str) -> Path:
"""创建归档草稿;目标文件存在时拒绝覆盖。"""
short_title = safe_title(title)
if not issue_number.isdigit():
raise ValueError("工单号必须是数字")
if not short_title:
raise ValueError("标题不能为空")
target = TASK_DIR / f"{issue_number}-{short_title}.md"
if target.exists():
raise FileExistsError(f"文件已存在:{target}")
content = TEMPLATE.read_text(encoding="utf-8")
content = content.replace("<工单号>", issue_number, 1)
content = content.replace("<标题>", title.strip(), 1)
content = content.replace("YYYY-MM-DD", date.today().isoformat(), 1)
target.write_text(content, encoding="utf-8")
return target
def main() -> None:
parser = argparse.ArgumentParser(description="创建 docs/task 任务归档草稿")
parser.add_argument("issue_number", help="Gitea 工单号,例如 123")
parser.add_argument("title", help="简短任务标题")
args = parser.parse_args()
target = create_archive(args.issue_number, args.title)
print(f"已创建:{target.relative_to(ROOT)}")
if __name__ == "__main__":
main()