chore: initialize DevHarness template
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
## 背景
|
||||
|
||||
<!-- 为什么要做这个产品或长期能力。 -->
|
||||
|
||||
## 目标
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 非目标
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 总体方案
|
||||
|
||||
<!-- 只写稳定的架构和关键决策,不复制子任务细节。 -->
|
||||
|
||||
## 阶段路线
|
||||
|
||||
1. <!-- 填写 -->
|
||||
|
||||
## MVP 与任务索引
|
||||
|
||||
- [ ] # MVP 工单
|
||||
|
||||
## 依赖、风险和回退
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 最终验收标准
|
||||
|
||||
- [ ] <!-- 填写 -->
|
||||
@@ -0,0 +1,27 @@
|
||||
## 基本信息
|
||||
|
||||
- 所属 Epic:#
|
||||
|
||||
## MVP 目标
|
||||
|
||||
<!-- 这个版本交付后,用户能够完成什么。 -->
|
||||
|
||||
## 包含范围
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 排除范围
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 阶段与单元任务
|
||||
|
||||
- [ ] # 单元任务
|
||||
|
||||
## 集成风险和回退
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## MVP 验收标准
|
||||
|
||||
- [ ] <!-- 填写 -->
|
||||
@@ -0,0 +1,36 @@
|
||||
## 基本信息
|
||||
|
||||
- 类型:需求 / 缺陷 / 重构
|
||||
- 所属 Epic:#
|
||||
- 所属 MVP / 版本:#
|
||||
- 阶段:
|
||||
|
||||
## 要解决什么
|
||||
|
||||
<!-- 描述现状和目标。缺陷需要写清复现步骤、实际结果和期望结果。 -->
|
||||
|
||||
## 做什么 / 不做什么
|
||||
|
||||
- 做:
|
||||
- 不做:
|
||||
|
||||
## 已确认方案
|
||||
|
||||
<!-- 写清修改范围、关键设计,以及是否影响接口、数据库和安全边界。 -->
|
||||
|
||||
预计修改文件:
|
||||
|
||||
- <!-- 填写 -->
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] <!-- 填写 -->
|
||||
- [ ] <!-- 填写 -->
|
||||
|
||||
## 验证方式
|
||||
|
||||
<!-- 写出可复制的命令;需要真机、生产环境或人工检查时明确说明。 -->
|
||||
|
||||
## 风险和回退
|
||||
|
||||
<!-- 普通低风险任务可删除本节;涉及接口、迁移、安全或不可逆操作时必填。 -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# 本地环境与凭据
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.local
|
||||
|
||||
# 编辑器与系统文件
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 常见构建、测试缓存
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
coverage/
|
||||
dist/
|
||||
build/
|
||||
node_modules/
|
||||
@@ -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。 -->
|
||||
|
||||
- 尚未配置。开始产品开发前必须填写项目档案,并删除本行。
|
||||
@@ -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` 只保存完成后的最终事实。
|
||||
- 一个单元工单只解决一个可独立测试和回退的问题。
|
||||
- 实现提交与归档提交分开,便于审查与追溯。
|
||||
- 凭据、个人数据和生产数据不得进入代码、工单或归档。
|
||||
@@ -0,0 +1,50 @@
|
||||
# 项目档案
|
||||
|
||||
复制模板后先填写本页。这里保存不经常变化、所有维护者都需要知道的信息。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|---|---|
|
||||
| 项目名称 | `<填写>` |
|
||||
| 一句话目标 | `<填写>` |
|
||||
| Gitea 地址 | `<例如 https://gitea.example.com>` |
|
||||
| 仓库 | `<owner/repository>` |
|
||||
| 默认分支 | `main` |
|
||||
| 主要维护者 | `<填写>` |
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 部分 | 技术 | 规则文件 |
|
||||
|---|---|---|
|
||||
| `<子项目或服务>` | `<语言、框架、版本>` | `<路径/AGENTS.md>` |
|
||||
|
||||
## 常用命令
|
||||
|
||||
所有命令默认从仓库根目录执行。
|
||||
|
||||
| 用途 | 命令 | 预期结果 |
|
||||
|---|---|---|
|
||||
| 安装依赖 | `<填写>` | `<填写>` |
|
||||
| 启动开发环境 | `<填写>` | `<填写>` |
|
||||
| 格式检查 | `<填写>` | `<填写>` |
|
||||
| 静态检查 | `<填写>` | `<填写>` |
|
||||
| 单元测试 | `<填写>` | `<填写>` |
|
||||
| 集成测试 | `<填写或写“不适用”>` | `<填写>` |
|
||||
|
||||
## 目录边界
|
||||
|
||||
| 目录 | 职责 | 不应放入 |
|
||||
|---|---|---|
|
||||
| `<路径>` | `<填写>` | `<填写>` |
|
||||
|
||||
## 环境与凭据
|
||||
|
||||
- 本地配置文件:`<填写>`
|
||||
- 配置示例文件:`<填写>`
|
||||
- 凭据保存位置:`<只写保存方式,不填写真实凭据>`
|
||||
- 日志和构建产物位置:`<填写>`
|
||||
|
||||
## 项目专用验收要求
|
||||
|
||||
- `<填写>`
|
||||
@@ -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` |
|
||||
|---|---:|---:|
|
||||
| 讨论过程和临时方案 | 是 | 否 |
|
||||
| 实施进度和阻塞 | 是 | 否 |
|
||||
| 最终实现方案 | 是 | 是 |
|
||||
| 测试结果与未验证内容 | 是 | 是 |
|
||||
| 提交哈希 | 是 | 是 |
|
||||
| 长期有效的最终结论 | 可链接 | 是 |
|
||||
@@ -0,0 +1,8 @@
|
||||
# 文档索引
|
||||
|
||||
- [项目档案](00-project-profile.md):仓库、技术栈、命令和负责人等稳定信息。
|
||||
- [开发工作流](01-workflow.md):从需求讨论到工单关闭的完整顺序。
|
||||
- [任务归档模板](templates/task-archive.md):任务完成后的固定格式。
|
||||
- `task/`:已经完成并与代码版本对应的任务记录。
|
||||
|
||||
临时进度、方案讨论和待办事项写入 Gitea 工单,不写进长期文档。
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
# <工单号> <标题>
|
||||
|
||||
- 类型:需求 / 缺陷 / 重构
|
||||
- 所属 Epic:#
|
||||
- 所属 MVP / 版本:#
|
||||
- 状态:待验收 / 已完成
|
||||
- 日期:YYYY-MM-DD
|
||||
- Gitea 工单:<链接>
|
||||
|
||||
## 背景与目标
|
||||
|
||||
<!-- 原来有什么问题,这次达到什么结果。 -->
|
||||
|
||||
## 最终方案
|
||||
|
||||
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
|
||||
|
||||
## 修改文件
|
||||
|
||||
- `<文件>`:<改动说明>
|
||||
|
||||
## 验收结果
|
||||
|
||||
| 验收标准 | 结果 |
|
||||
|---|---|
|
||||
| | 通过 / 未通过 |
|
||||
|
||||
## 测试
|
||||
|
||||
- 执行命令:`<命令>`
|
||||
- 结果:
|
||||
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
|
||||
|
||||
## 遗留问题
|
||||
|
||||
<!-- 没有就删除本节。 -->
|
||||
|
||||
## 相关提交
|
||||
|
||||
- `<提交哈希>` <提交说明>
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user