feat(coordination): add atomic Gitea task workflow (phase 2)
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Agent task
|
||||||
|
about: 创建与 docs/tasks/T-<编号>.md 一一对应的开发任务
|
||||||
|
title: "[T-XXX] "
|
||||||
|
ref: ""
|
||||||
|
labels:
|
||||||
|
- kind/task
|
||||||
|
---
|
||||||
|
|
||||||
|
## 任务映射
|
||||||
|
|
||||||
|
- task_id: `T-XXX`
|
||||||
|
- task_file: `docs/tasks/T-XXX.md`
|
||||||
|
- context_ref: `【领取时的默认分支提交 SHA】`
|
||||||
|
- deps: `【T-编号列表或无】`
|
||||||
|
- write_paths:
|
||||||
|
- `docs/tasks/T-XXX.md`
|
||||||
|
- `【允许修改的仓库相对路径】`
|
||||||
|
|
||||||
|
## 问题与方案
|
||||||
|
|
||||||
|
【链接任务文件对应章节;Issue 只写协调所需摘要,不复制整份规格。】
|
||||||
|
|
||||||
|
## 验收入口
|
||||||
|
|
||||||
|
【真实验证命令和可观察结果;长期证据回填到任务文件。】
|
||||||
|
|
||||||
|
## 协作状态
|
||||||
|
|
||||||
|
- expected_claim_branch: `claims/T-XXX`
|
||||||
|
- work_branch: `【领取后填写】`
|
||||||
|
- claimed_by: `【领取后填写非敏感 agent-id】`
|
||||||
|
- lease_until: `【领取后填写 RFC 3339 时间】`
|
||||||
|
|
||||||
|
领取必须遵循 `docs/gitea-collaboration.md` 的原子 claim 流程。不要在本 Issue 粘贴 Token、Authorization header 或私有配置。
|
||||||
|
创建后先把 Issue 编号回填任务文件并合入默认分支,再按主要变更选择唯一 `type/docs` 或 `type/code`、一个 `priority/*` 和 `status/todo`。映射提交完成前不可领取。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
## 任务映射
|
||||||
|
|
||||||
|
- Closes #【Issue 编号】
|
||||||
|
- task_file: `docs/tasks/T-XXX.md`
|
||||||
|
- context_ref: `【领取任务时的提交 SHA】`
|
||||||
|
- claim_branch: `claims/T-XXX`
|
||||||
|
- work_branch: `agent/【agent-id】/T-XXX`
|
||||||
|
- write_paths:
|
||||||
|
- `docs/tasks/T-XXX.md`
|
||||||
|
- `【本 PR 允许修改的仓库相对路径】`
|
||||||
|
|
||||||
|
## 变更摘要
|
||||||
|
|
||||||
|
【改了什么,以及为什么符合任务方案。】
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
| 命令 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| `【真实命令】` | 【通过 / 失败摘要】 |
|
||||||
|
|
||||||
|
## 风险与回滚
|
||||||
|
|
||||||
|
【已知风险、兼容性影响、回滚方法;没有则写“无”。】
|
||||||
|
|
||||||
|
## 检查清单
|
||||||
|
|
||||||
|
- [ ] 当前 PR 只对应一个任务 / Issue。
|
||||||
|
- [ ] 变更未超出 `write_paths`,没有夹带无关修改。
|
||||||
|
- [ ] 任务文件执行记录包含相同的验证证据。
|
||||||
|
- [ ] 合并前任务文件 frontmatter 已为 `DONE`;`Closes` 自动关闭 Issue 不会制造假完成。
|
||||||
|
- [ ] 未提交 Token、Authorization header、私有配置或实例地址。
|
||||||
|
- [ ] Issue 已切换到唯一 `status/review`;合并后才标记 `status/done`。
|
||||||
@@ -36,6 +36,8 @@ harness coding 需要的项目文档模板主要集中在 `docs/` 目录;根
|
|||||||
|
|
||||||
领取任务时记录默认分支头提交为 `context_ref`;同一会话中文件 SHA 未变化时可复用已读内容,ref 变化后重新读取清单和受影响文档。
|
领取任务时记录默认分支头提交为 `context_ref`;同一会话中文件 SHA 未变化时可复用已读内容,ref 变化后重新读取清单和受影响文档。
|
||||||
|
|
||||||
|
模板复制到启用 Gitea 协作的项目后,还应先读 `docs/gitea-collaboration.md`:dispatcher 串行检查写路径和分配任务,每个任务以唯一 claim 分支防重复领取;每个 worker 同时最多一个活跃任务,工作分支 / worktree 独立,活跃任务的 `write_paths` 不得重叠。
|
||||||
|
|
||||||
如果用户要求修改某类模板,优先阅读对应文件,不要只凭文件名猜内容。
|
如果用户要求修改某类模板,优先阅读对应文件,不要只凭文件名猜内容。
|
||||||
|
|
||||||
## 工作规则
|
## 工作规则
|
||||||
|
|||||||
@@ -33,7 +33,10 @@
|
|||||||
| [`docs/agent-context.schema.json`](docs/agent-context.schema.json) | 上下文清单结构契约 |
|
| [`docs/agent-context.schema.json`](docs/agent-context.schema.json) | 上下文清单结构契约 |
|
||||||
| [`docs/agent-context.md`](docs/agent-context.md) | 上下文清单的读取、缓存、权威来源和断连降级说明 |
|
| [`docs/agent-context.md`](docs/agent-context.md) | 上下文清单的读取、缓存、权威来源和断连降级说明 |
|
||||||
| [`docs/gitea-mcp.md`](docs/gitea-mcp.md) | 可选:Gitea MCP 共享文档与任务协调接入、安全和降级规则 |
|
| [`docs/gitea-mcp.md`](docs/gitea-mcp.md) | 可选:Gitea MCP 共享文档与任务协调接入、安全和降级规则 |
|
||||||
|
| [`docs/gitea-collaboration.md`](docs/gitea-collaboration.md) | 可选:Issue / 任务文件 / PR 映射、原子领取和写路径防撞协议 |
|
||||||
| [`scripts/validate_agent_context.py`](scripts/validate_agent_context.py) | 零第三方依赖校验上下文清单、Schema 和仓库相对路径 |
|
| [`scripts/validate_agent_context.py`](scripts/validate_agent_context.py) | 零第三方依赖校验上下文清单、Schema 和仓库相对路径 |
|
||||||
|
| [`scripts/setup_gitea_labels.py`](scripts/setup_gitea_labels.py) | 默认只读预览远端差异、显式 `--apply` 的 Gitea 协作标签初始化脚本 |
|
||||||
|
| [`.gitea/ISSUE_TEMPLATE/task.md`](.gitea/ISSUE_TEMPLATE/task.md) / [`.gitea/PULL_REQUEST_TEMPLATE.md`](.gitea/PULL_REQUEST_TEMPLATE.md) | Gitea 任务 Issue 与 PR 模板 |
|
||||||
| [`docs/method-map.md`](docs/method-map.md) | 失败模式 → 首要修复 → 工件的诊断 / 导航对照表 |
|
| [`docs/method-map.md`](docs/method-map.md) | 失败模式 → 首要修复 → 工件的诊断 / 导航对照表 |
|
||||||
| [`docs/evaluator-rubric.md`](docs/evaluator-rubric.md) | 单次会话输出的结构化评审评分表 |
|
| [`docs/evaluator-rubric.md`](docs/evaluator-rubric.md) | 单次会话输出的结构化评审评分表 |
|
||||||
| [`docs/quality-document.md`](docs/quality-document.md) | 代码库长期健康度追踪(产品域 × 架构层评级) |
|
| [`docs/quality-document.md`](docs/quality-document.md) | 代码库长期健康度追踪(产品域 × 架构层评级) |
|
||||||
@@ -76,6 +79,7 @@
|
|||||||
项目进入多轮长期开发后,再按需启用:
|
项目进入多轮长期开发后,再按需启用:
|
||||||
|
|
||||||
- `docs/gitea-mcp.md`:需要跨 agent 读取 Gitea 文档、Issue 和 PR 时启用。
|
- `docs/gitea-mcp.md`:需要跨 agent 读取 Gitea 文档、Issue 和 PR 时启用。
|
||||||
|
- `docs/gitea-collaboration.md`:需要多 agent 原子领取、独立分支 / worktree 和写路径防撞时启用。
|
||||||
- `docs/clean-state-checklist.md`:每轮结束前检查仓库是否可恢复。
|
- `docs/clean-state-checklist.md`:每轮结束前检查仓库是否可恢复。
|
||||||
- `docs/method-map.md`:遇到失败模式时定位该补哪个工件。
|
- `docs/method-map.md`:遇到失败模式时定位该补哪个工件。
|
||||||
- `docs/evaluator-rubric.md`:评审单次 agent 输出质量。
|
- `docs/evaluator-rubric.md`:评审单次 agent 输出质量。
|
||||||
|
|||||||
@@ -40,12 +40,12 @@
|
|||||||
读完上述文档后,每轮会话按这个机械顺序进入,恢复持久状态再动手:
|
读完上述文档后,每轮会话按这个机械顺序进入,恢复持久状态再动手:
|
||||||
|
|
||||||
1. `pwd`:确认在正确的仓库根目录。
|
1. `pwd`:确认在正确的仓库根目录。
|
||||||
2. 读 [`current-state.md`](current-state.md) 和 `docs/tasks/` 中 `status: DOING` 的任务文件:恢复已验证状态、下一步和当前 blocker。
|
2. 读 [`current-state.md`](current-state.md) 和 `docs/tasks/` 中当前 agent 的活跃任务;启用 Gitea 时同时读对应 Issue,恢复已验证状态、claim、下一步和当前 blocker。
|
||||||
3. `git log --oneline -5`:看清最近发生了什么。
|
3. `git log --oneline -5`:看清最近发生了什么。
|
||||||
4. 运行 `./init.sh`(Windows 原生 PowerShell 用 `./init.ps1`):统一安装、验证、打印启动命令;如果脚本提示命令未替换,先配置脚本顶部三个命令。
|
4. 运行 `./init.sh`(Windows 原生 PowerShell 用 `./init.ps1`):统一安装、验证、打印启动命令;如果脚本提示命令未替换,先配置脚本顶部三个命令。
|
||||||
5. 跑一条基础 smoke / 端到端路径,确认基线没坏。
|
5. 跑一条基础 smoke / 端到端路径,确认基线没坏。
|
||||||
6. **如果基线已坏,先修基线**,不要在坏的起点上叠新功能。
|
6. **如果基线已坏,先修基线**,不要在坏的起点上叠新功能。
|
||||||
7. 基线绿了,再从 `docs/tasks/` 领取唯一任务(路线图见 [`06-tasks.md`](06-tasks.md))。
|
7. 基线绿了,再从 `docs/tasks/` 为当前 agent 领取唯一任务(路线图见 [`06-tasks.md`](06-tasks.md));启用 Gitea 时由 dispatcher 串行分配并取得原子 claim。
|
||||||
|
|
||||||
## 当前阶段
|
## 当前阶段
|
||||||
|
|
||||||
@@ -63,16 +63,17 @@
|
|||||||
|
|
||||||
任务以「一任务一文件」存放在 `docs/tasks/`(约定见 [`tasks/README.md`](tasks/README.md)):
|
任务以「一任务一文件」存放在 `docs/tasks/`(约定见 [`tasks/README.md`](tasks/README.md)):
|
||||||
|
|
||||||
- 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的。
|
- 每个 agent 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的;项目可并行多个 `write_paths` 互不重叠的任务。
|
||||||
- 若 `docs/tasks/` 暂无可领任务,先按 [`06-tasks.md`](06-tasks.md) 路线图把下一个建议任务落成任务文件,再领取。
|
- 若 `docs/tasks/` 暂无可领任务,先按 [`06-tasks.md`](06-tasks.md) 路线图把下一个建议任务落成任务文件,再领取。
|
||||||
- 开始前把该文件 frontmatter 的 `status` 改为 `DOING`(同一时间最多 1 个)。
|
- 未启用 Gitea 时,开始前在独立分支 / worktree 把该文件 frontmatter 的 `status` 改为 `DOING`。
|
||||||
|
- 启用 Gitea 时,由 dispatcher 按 [`gitea-collaboration.md`](gitea-collaboration.md) 串行检查写路径并原子创建 `claims/T-<编号>`;worker 只接受已读回确认的分配。标签、assignee 和更新后读回不能代替同任务领取锁。
|
||||||
- 本轮只完成这一个任务;验收通过后改为 `DONE`。
|
- 本轮只完成这一个任务;验收通过后改为 `DONE`。
|
||||||
- **执行记录写进该任务文件的 `## 执行记录`**(改了什么、跑了什么验证、结果、决策)。
|
- **执行记录写进该任务文件的 `## 执行记录`**(改了什么、跑了什么验证、结果、决策)。
|
||||||
- 若项目现实发生变化(启动/验证路径、目录结构、blocker),覆盖更新 [`current-state.md`](current-state.md);任务状态以任务文件 frontmatter 为准,不逐任务改写快照。
|
- 若项目现实发生变化(启动/验证路径、目录结构、blocker),覆盖更新 [`current-state.md`](current-state.md);未启用 Gitea 时以任务文件 frontmatter 为状态权威,启用后以 Issue 为实时状态,不逐任务改写快照。
|
||||||
- 结束会话前过一遍 [`clean-state-checklist.md`](clean-state-checklist.md),确保下一轮无需人工修复即可开工。
|
- 结束会话前过一遍 [`clean-state-checklist.md`](clean-state-checklist.md),确保下一轮无需人工修复即可开工。
|
||||||
- 做完即停,汇报验证结果,等待下一步指令。
|
- 做完即停,汇报验证结果,等待下一步指令。
|
||||||
|
|
||||||
> 本约定单 agent 与多 agent 并发通用;并发时注意 [`tasks/README.md`](tasks/README.md) 的防撞号规则,只改自己领取的任务文件。
|
> 本约定单 agent 与多 agent 并发通用;并发时遵守 [`tasks/README.md`](tasks/README.md) 的编号和写路径防撞规则,每个 agent 使用独立工作分支与 worktree。
|
||||||
|
|
||||||
如果代码实际状态和任务文件冲突,先说明冲突,不要擅自跳步或重排。
|
如果代码实际状态和任务文件冲突,先说明冲突,不要擅自跳步或重排。
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
# 任务路线图(Roadmap)
|
# 任务路线图(Roadmap)
|
||||||
|
|
||||||
> 本文是**只读路线图**:维护阶段划分、里程碑、待办池和建议拆分清单,把 MVP 拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。
|
> 本文是**只读路线图**:维护阶段划分、里程碑、待办池和建议拆分清单,把 MVP 拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。
|
||||||
> 真实任务以「一任务一文件」存放在 [`tasks/`](tasks/README.md)(`docs/tasks/T-<编号>.md`),任务状态以各任务文件 frontmatter 为准;**本文不跟踪单任务状态**。
|
> 真实任务以「一任务一文件」存放在 [`tasks/`](tasks/README.md)(`docs/tasks/T-<编号>.md`)。未启用 Gitea 时以任务文件 frontmatter 为状态权威;启用后以 Issue 为实时状态、合并后的任务文件为长期事实。**本文不跟踪单任务状态**。
|
||||||
|
|
||||||
## 使用规则
|
## 使用规则
|
||||||
|
|
||||||
1. **开工先落文件**:从下方「建议拆分清单」把下一个任务按 [`tasks/README.md`](tasks/README.md) 落成 `docs/tasks/T-<编号>.md`(沿用建议编号),把验收要点展开成可执行、可观察的步骤,再开始实现。
|
1. **开工先落文件**:从下方「建议拆分清单」把下一个任务按 [`tasks/README.md`](tasks/README.md) 落成 `docs/tasks/T-<编号>.md`(沿用建议编号),把验收要点展开成可执行、可观察的步骤,再开始实现。
|
||||||
2. **一次只做一个任务**:领取、状态流转、执行记录、完成定义全部遵循 [`tasks/README.md`](tasks/README.md) 和 [编码规则](05-coding-rules.md);`DONE` 需要可运行证据。
|
2. **每个 agent 一次只做一个任务**:领取、状态流转、执行记录、完成定义全部遵循 [`tasks/README.md`](tasks/README.md) 和 [编码规则](05-coding-rules.md);`DONE` 需要可运行证据,多 agent 只并行写路径互不重叠的任务。
|
||||||
3. **不跳步**:依赖未完成的任务不能开工。
|
3. **不跳步**:依赖未完成的任务不能开工。
|
||||||
4. **本文只在规划变化时修改**:调整阶段划分、里程碑、增删建议任务或 Backlog 条目时才动本文;单个任务开工或完成**不**修改本文。
|
4. **本文只在规划变化时修改**:调整阶段划分、里程碑、增删建议任务或 Backlog 条目时才动本文;单个任务开工或完成**不**修改本文。
|
||||||
5. **动手前**先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。
|
5. **动手前**先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。
|
||||||
|
|||||||
+5
-2
@@ -23,19 +23,22 @@
|
|||||||
- [架构设计](04-architecture.md):系统结构、模块职责、数据模型、关键风险和开发顺序。
|
- [架构设计](04-architecture.md):系统结构、模块职责、数据模型、关键风险和开发顺序。
|
||||||
- [编码规则](05-coding-rules.md):AI 写代码前必须遵守的硬约束。
|
- [编码规则](05-coding-rules.md):AI 写代码前必须遵守的硬约束。
|
||||||
- [任务路线图](06-tasks.md):阶段划分、里程碑和待办池;只读,不跟踪单任务状态。
|
- [任务路线图](06-tasks.md):阶段划分、里程碑和待办池;只读,不跟踪单任务状态。
|
||||||
- [任务文件(默认)](tasks/README.md):一任务一文件 `docs/tasks/T-<编号>.md`,任务状态以 frontmatter 为准,单/多 agent 通用,agent 每轮只做一个。
|
- [任务文件(默认)](tasks/README.md):一任务一文件 `docs/tasks/T-<编号>.md`,单/多 agent 通用,每个 agent 同时只做一个;启用 Gitea 后由 Issue 承担实时状态。
|
||||||
- [已有项目接入清单](adoption-checklist.md):把本模板补进已有代码库时的迁移步骤和第一轮任务建议。
|
- [已有项目接入清单](adoption-checklist.md):把本模板补进已有代码库时的迁移步骤和第一轮任务建议。
|
||||||
- [API 合约](api.md):前后端接口形状、错误格式、鉴权约定。
|
- [API 合约](api.md):前后端接口形状、错误格式、鉴权约定。
|
||||||
- [路由与页面结构](routes.md):页面路由、页面职责、组件归属。
|
- [路由与页面结构](routes.md):页面路由、页面职责、组件归属。
|
||||||
- [当前实现状态](current-state.md):可覆盖的当前快照,记录仓库现实状态、可运行命令和下一步可做任务。
|
- [当前实现状态](current-state.md):可覆盖的当前快照,记录仓库现实状态、可运行命令和下一步可做任务。
|
||||||
- [Agent 上下文清单](agent-context.md) / [`agent-context.json`](agent-context.json) / [`Schema`](agent-context.schema.json):按任务类型选择文档、用提交 / 文件 SHA 避免重复读取。
|
- [Agent 上下文清单](agent-context.md) / [`agent-context.json`](agent-context.json) / [`Schema`](agent-context.schema.json):按任务类型选择文档、用提交 / 文件 SHA 避免重复读取。
|
||||||
- [Gitea MCP 接入](gitea-mcp.md):可选的共享文档、Issue / PR 协调、安全配置和断连降级规则。
|
- [Gitea MCP 接入](gitea-mcp.md):可选的共享文档、Issue / PR 协调、安全配置和断连降级规则。
|
||||||
|
- [Gitea 多 Agent 协作](gitea-collaboration.md):可选的任务映射、原子 claim、写路径防撞和 PR 状态协议。
|
||||||
- [收尾检查清单](clean-state-checklist.md):会话结束前逐项检查,保证下一轮无需人工修复即可继续。
|
- [收尾检查清单](clean-state-checklist.md):会话结束前逐项检查,保证下一轮无需人工修复即可继续。
|
||||||
- [方法对照表](method-map.md):失败模式 → 首要修复 → 工件;出问题先查这里对症补工件。
|
- [方法对照表](method-map.md):失败模式 → 首要修复 → 工件;出问题先查这里对症补工件。
|
||||||
- [评审评分表](evaluator-rubric.md):单次会话输出的结构化评审(6 维 0-2 分 + 校准说明)。
|
- [评审评分表](evaluator-rubric.md):单次会话输出的结构化评审(6 维 0-2 分 + 校准说明)。
|
||||||
- [质量文档](quality-document.md):代码库长期健康度追踪,区别于单次输出评审。
|
- [质量文档](quality-document.md):代码库长期健康度追踪,区别于单次输出评审。
|
||||||
- [`../init.sh`](../init.sh) / [`../init.ps1`](../init.ps1):标准启动与验证入口脚本(根目录),统一安装、验证和启动命令。按操作系统二选一:WSL / Git Bash / macOS / Linux 用 `init.sh`,Windows 原生 PowerShell 用 `init.ps1`;换技术栈只改脚本顶部三个命令变量;未替换前脚本会主动失败,避免把示例命令误当真实项目命令。
|
- [`../init.sh`](../init.sh) / [`../init.ps1`](../init.ps1):标准启动与验证入口脚本(根目录),统一安装、验证和启动命令。按操作系统二选一:WSL / Git Bash / macOS / Linux 用 `init.sh`,Windows 原生 PowerShell 用 `init.ps1`;换技术栈只改脚本顶部三个命令变量;未替换前脚本会主动失败,避免把示例命令误当真实项目命令。
|
||||||
- [`../scripts/validate_agent_context.py`](../scripts/validate_agent_context.py):零第三方依赖校验上下文清单、Schema 和仓库相对路径。
|
- [`../scripts/validate_agent_context.py`](../scripts/validate_agent_context.py):零第三方依赖校验上下文清单、Schema 和仓库相对路径。
|
||||||
|
- [`../scripts/setup_gitea_labels.py`](../scripts/setup_gitea_labels.py):默认只读预览远端差异、显式写入的 Gitea 协作标签初始化脚本。
|
||||||
|
- [Gitea Issue 模板](../.gitea/ISSUE_TEMPLATE/task.md) / [PR 模板](../.gitea/PULL_REQUEST_TEMPLATE.md):任务映射、写路径和验证证据字段。
|
||||||
|
|
||||||
## 任务 / 进度 / 当前状态
|
## 任务 / 进度 / 当前状态
|
||||||
|
|
||||||
@@ -49,6 +52,6 @@
|
|||||||
## 维护原则
|
## 维护原则
|
||||||
|
|
||||||
- 需求变化先改文档,再改代码。
|
- 需求变化先改文档,再改代码。
|
||||||
- 代码现实变化后同步 `current-state.md`;任务状态改在对应任务文件 frontmatter,执行过程写进该任务文件的 `## 执行记录`。
|
- 代码现实变化后同步 `current-state.md`;任务长期状态和执行证据写进对应任务文件,启用 Gitea 时实时状态同步到 Issue。
|
||||||
- API、数据模型、路由、技术栈一旦在文档中定稿,代码不得另起一套。
|
- API、数据模型、路由、技术栈一旦在文档中定稿,代码不得另起一套。
|
||||||
- agent 开始新任务前,必须从 `00-ai-start-here.md` 进入。
|
- agent 开始新任务前,必须从 `00-ai-start-here.md` 进入。
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
推荐随后补齐:`docs/01-vision.md`、`docs/02-requirements.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/clean-state-checklist.md`;`progress.md` 可选(历史归档 / 项目级大事记)。
|
推荐随后补齐:`docs/01-vision.md`、`docs/02-requirements.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/clean-state-checklist.md`;`progress.md` 可选(历史归档 / 项目级大事记)。
|
||||||
|
|
||||||
|
需要 Gitea 多 Agent 协作时,再复制 `docs/gitea-mcp.md`、`docs/gitea-collaboration.md`、`.gitea/` 模板和 `scripts/setup_gitea_labels.py`;先只读预览远端标签差异,再由维护者显式 `--apply`。
|
||||||
|
|
||||||
## 接入步骤
|
## 接入步骤
|
||||||
|
|
||||||
1. 确认仓库根目录,读取已有 `README`、运行脚本、测试配置和主要入口文件。
|
1. 确认仓库根目录,读取已有 `README`、运行脚本、测试配置和主要入口文件。
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
],
|
],
|
||||||
"gitea": [
|
"gitea": [
|
||||||
"docs/gitea-mcp.md",
|
"docs/gitea-mcp.md",
|
||||||
|
"docs/gitea-collaboration.md",
|
||||||
"docs/tasks/README.md",
|
"docs/tasks/README.md",
|
||||||
"docs/clean-state-checklist.md"
|
"docs/clean-state-checklist.md"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,5 +12,7 @@
|
|||||||
- [ ] [`current-state.md`](current-state.md) 的项目级快照与现实一致(启动/验证路径、目录要点、blocker)。
|
- [ ] [`current-state.md`](current-state.md) 的项目级快照与现实一致(启动/验证路径、目录要点、blocker)。
|
||||||
- [ ] 没有半成品改动处于未记录状态;如有,已写明 `BLOCKED` / `PARTIAL` 和原因。
|
- [ ] 没有半成品改动处于未记录状态;如有,已写明 `BLOCKED` / `PARTIAL` 和原因。
|
||||||
- [ ] 代码处于可安全恢复的状态(必要时已提交,提交信息清晰)。
|
- [ ] 代码处于可安全恢复的状态(必要时已提交,提交信息清晰)。
|
||||||
|
- [ ] 本轮实际修改没有超出任务 `write_paths`,也没有与其他活跃任务发生路径重叠。
|
||||||
|
- [ ] 启用 Gitea 时,Issue 的唯一 `status/*`、claim / 工作分支、PR 和任务状态彼此一致;未完成任务没有误删 claim。
|
||||||
|
|
||||||
任意一项不满足,就先补到满足,再结束会话。
|
任意一项不满足,就先补到满足,再结束会话。
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
## 任务状态
|
## 任务状态
|
||||||
|
|
||||||
任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准(可用脚本汇总成只读看板),历史执行记录见各任务文件的 `## 执行记录`。本节只写摘要:
|
未启用 Gitea 时,任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准;启用 Gitea 时,Issue 是实时状态权威,合并到默认分支的任务文件保存长期状态和执行证据。本节只写项目级摘要:
|
||||||
|
|
||||||
- 已完成:【列出 DONE 任务】。
|
- 已完成:【列出 DONE 任务】。
|
||||||
- 正在进行:【如有,列出 DOING 任务】。
|
- 正在进行:【如有,列出 DOING 任务】。
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
1. 读仓库级 agent 规则文件(如有)。
|
1. 读仓库级 agent 规则文件(如有)。
|
||||||
2. 读 `docs/00-ai-start-here.md`。
|
2. 读 `docs/00-ai-start-here.md`。
|
||||||
3. 读 `docs/05-coding-rules.md`。
|
3. 读 `docs/05-coding-rules.md`。
|
||||||
4. 在 `docs/tasks/` 取第一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件;暂无可领任务时,先按 `docs/06-tasks.md` 路线图落成任务文件。
|
4. 在 `docs/tasks/` 找到 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件;暂无任务时,先按 `docs/06-tasks.md` 路线图落成任务文件。
|
||||||
5. 将该任务文件 frontmatter 的 `status` 改为 `DOING`。
|
5. 未启用 Gitea 时,在独立分支 / worktree 把任务改为 `DOING`;启用 Gitea 时,先按 `gitea-collaboration.md` 由 dispatcher 完成原子 claim 和路径分配,worker 读回成功后再开始。
|
||||||
|
|
||||||
## 维护规则
|
## 维护规则
|
||||||
|
|
||||||
@@ -69,11 +69,11 @@
|
|||||||
|
|
||||||
- 新增或移动入口文件。
|
- 新增或移动入口文件。
|
||||||
- 初始化框架或模块。
|
- 初始化框架或模块。
|
||||||
- 任务从 `TODO` 进入 `DOING` 或 `DONE`。
|
|
||||||
- 新增可运行命令。
|
- 新增可运行命令。
|
||||||
- 发现文档和代码现实不一致。
|
- 发现文档和代码现实不一致。
|
||||||
|
- 阶段、项目级 blocker 或可领取任务摘要发生需要跨会话保留的变化。
|
||||||
|
|
||||||
同时注意:
|
同时注意:
|
||||||
|
|
||||||
- 任务状态变化改在对应任务文件的 frontmatter;每轮执行记录、验证命令、阻塞点和关键决策写进该任务文件的 `## 执行记录`。
|
- 任务长期状态改在对应任务文件的 frontmatter;启用 Gitea 时实时状态同步到 Issue。每轮执行记录、验证命令、阻塞点和关键决策写进该任务文件的 `## 执行记录`。
|
||||||
- 本文件只保留当前快照,不保留完整历史。
|
- 本文件只保留当前快照,不保留完整历史。
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Gitea 多 Agent 协作协议
|
||||||
|
|
||||||
|
> 本协议是可选增强。启用 Gitea 协作时,任务规格留在 Git,实时协调放在 Issue / PR;未启用时继续使用 [`tasks/README.md`](tasks/README.md) 的本地流程。
|
||||||
|
|
||||||
|
## 工件与权威来源
|
||||||
|
|
||||||
|
| 工件 | 保存什么 | 不保存什么 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `docs/tasks/T-<编号>.md` | 任务规格、依赖、允许写路径、验收标准、可审计执行证据 | Token、实例地址、临时聊天 |
|
||||||
|
| Gitea Issue | 实时状态、领取者、阻塞、结构化 claim / 续租记录 | 需求正文的唯一副本 |
|
||||||
|
| `claims/T-<编号>` 分支 | 唯一领取锁;分支存在表示任务已被领取 | 工作提交 |
|
||||||
|
| `agent/<agent-id>/T-<编号>` 分支 | 单个 agent 的任务提交 | 其他任务的顺手修改 |
|
||||||
|
| Pull Request | 评审、验证证据、合并决策 | 未进入 Git 的隐含上下文 |
|
||||||
|
|
||||||
|
同一个任务只能映射一个任务文件和一个主 Issue。Issue 标题、工作分支和 PR 标题都以 `[T-<编号>]` 开头;Issue 正文保存任务文件路径,PR 同时链接任务文件和 Issue。
|
||||||
|
|
||||||
|
## 状态与标签
|
||||||
|
|
||||||
|
推荐标签:
|
||||||
|
|
||||||
|
- 类型:`kind/task` 标识可执行任务,并从 `type/docs`、`type/code` 中选择一个主要变更类型。
|
||||||
|
- 状态:`status/todo`、`status/doing`、`status/blocked`、`status/review`、`status/done`。
|
||||||
|
- 优先级:`priority/p0`、`priority/p1`、`priority/p2`。
|
||||||
|
|
||||||
|
`status/*`、`type/*`、`priority/*` 分别使用 Gitea exclusive scoped labels,同一分组任一时刻最多一个;`kind/task` 为普通标签。
|
||||||
|
|
||||||
|
状态映射:
|
||||||
|
|
||||||
|
| 阶段 | 任务文件 | Issue | 分支 / PR |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 待领取 | `TODO` | open + `status/todo` | 无 claim 分支 |
|
||||||
|
| 开发中 | 工作分支上 `DOING` | open + `status/doing` | claim 与工作分支存在 |
|
||||||
|
| 阻塞 | `BLOCKED` | open + `status/blocked` | 默认保留 claim,避免误领 |
|
||||||
|
| 评审中 | 已写完整证据 | open + `status/review` | PR open |
|
||||||
|
| 已完成 | 合入默认分支的 `DONE` | closed + `status/done` | PR merged;claim 可清理 |
|
||||||
|
|
||||||
|
Issue 是实时状态权威;默认分支尚未合入工作提交时,其任务文件仍可能显示 `TODO`,这不是冲突。合并后,任务文件成为长期审计事实。
|
||||||
|
|
||||||
|
## 任务进入可领取队列
|
||||||
|
|
||||||
|
1. 先创建任务文件,写清规格、依赖和初始 `write_paths`,合入默认分支;此时 `issue`、`context_ref`、claim / 工作分支均为 `null`。
|
||||||
|
2. 用 Issue 模板创建唯一主 Issue。新 Issue 只有 `kind/task`,尚未带 `status/todo`。
|
||||||
|
3. 把 Issue 编号回填任务文件,并让 Issue 链接该文件;映射提交合入默认分支后,再选择 `type/*`、`priority/*` 和 `status/todo`。
|
||||||
|
|
||||||
|
因此 dispatcher 能从默认分支可靠定位任务 ↔ Issue;没有双向映射或没有 `status/todo` 的任务都不可领取。
|
||||||
|
|
||||||
|
## 串行分配与原子领取
|
||||||
|
|
||||||
|
仅修改 assignee / `status/doing` 再读回不是原子操作:两个 agent 可能先后覆盖并各自读到成功。MVP 指定一个 dispatcher(主 agent 或维护者)串行执行分配;worker 不并发自选任务。dispatcher 再使用 Gitea “同名分支只能创建一次”的约束,防止同一任务因重试或旁路操作被重复领取:
|
||||||
|
|
||||||
|
1. 读取默认分支任务文件和对应 Issue,确认双向映射、依赖均为 `DONE`、Issue 为 `status/todo`,且目标 worker 没有其他活跃任务。
|
||||||
|
2. 读取默认分支头提交 SHA,记为 `context_ref`。串行检查所有活跃预留的 `write_paths`,不得与本任务重叠。
|
||||||
|
3. 从精确的 `context_ref` 原子创建 `claims/T-<编号>`。创建成功者获得该任务锁;收到 `409` 或“分支已存在”即停止,不得仅因分支 SHA 相同就判定成功。
|
||||||
|
4. 创建 `agent/<agent-id>/T-<编号>` 工作分支,更新 Issue 为 `status/doing`,按项目规则设置 assignee,并追加结构化 claim 评论。
|
||||||
|
5. dispatcher 读回 Issue 和两个分支;不一致时先修复协调状态,不把任务交给 worker。
|
||||||
|
6. worker 在独立 worktree 读回分配结果,再把工作分支任务文件更新为 `DOING`,写入 `context_ref`、claim / 工作分支和已接受的 `write_paths`;提交只触碰允许路径。
|
||||||
|
|
||||||
|
不同任务的“扫描路径后分别创建各自 claim”本身不具备原子性,因此不得让多个 worker 并发执行步骤 1~5。若团队不使用单一 dispatcher,路径检查只能视为乐观预检,任务必须事先由维护者分配互不重叠的范围,不能宣称有强互斥。
|
||||||
|
|
||||||
|
结构化 claim 评论至少包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CLAIM
|
||||||
|
task: T-123
|
||||||
|
claimed_by: 【agent-id】
|
||||||
|
allocated_by: 【dispatcher-id】
|
||||||
|
context_ref: 【40 位提交 SHA】
|
||||||
|
claim_branch: claims/T-123
|
||||||
|
work_branch: agent/【agent-id】/T-123
|
||||||
|
write_paths: 【仓库相对路径列表】
|
||||||
|
claimed_at: 【RFC 3339 时间】
|
||||||
|
lease_until: 【RFC 3339 时间】
|
||||||
|
```
|
||||||
|
|
||||||
|
断线重连时,只有 Issue 最新有效 claim 的 `claimed_by`、工作分支和当前 agent 全部一致,才可把已有 claim 当作自己的恢复现场;仅比较 SHA 不足以证明所有权。
|
||||||
|
|
||||||
|
## 写路径防撞
|
||||||
|
|
||||||
|
- 默认分支任务文件定义初始 `write_paths`;领取后,最新被 dispatcher 接受的结构化 claim / scope 评论与工作分支任务文件共同定义活跃预留。二者不一致时暂停工作。
|
||||||
|
- `write_paths` 必须列出任务文件本身及预期修改的文件或目录;共享配置、锁文件、导航文件也要列入。
|
||||||
|
- 两条路径相同,或一条是另一条的目录前缀,视为重叠;活跃任务不得存在重叠路径。
|
||||||
|
- 发现必须修改范围外文件时,先停止并在 Issue 提议扩展范围;dispatcher 串行复查其他活跃预留,接受后追加结构化 scope 评论,worker 同步更新工作分支任务文件,二者都完成后才能继续。
|
||||||
|
- 活跃任务由 Issue 的 `status/doing`、`status/blocked`、`status/review` 判定。每个 agent 同时最多一个活跃任务;项目可以并行多个写路径互不重叠的任务。
|
||||||
|
|
||||||
|
建议 worktree 命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git fetch origin
|
||||||
|
git worktree add ../【项目】-T-123 -b agent/【agent-id】/T-123 origin/agent/【agent-id】/T-123
|
||||||
|
```
|
||||||
|
|
||||||
|
不要让多个 agent 共用同一 worktree,也不要在 claim 分支提交工作代码。
|
||||||
|
|
||||||
|
## PR 与完成
|
||||||
|
|
||||||
|
1. 在任务文件 `## 执行记录` 写入实际验证命令和结果,完成标准满足后更新状态。
|
||||||
|
2. PR 使用 `.gitea/PULL_REQUEST_TEMPLATE.md`,链接 `Closes #【Issue 编号】`、任务文件、`context_ref`、写路径和验证证据。
|
||||||
|
3. 创建 PR 后把 Issue 切到唯一 `status/review`;评审失败则把 Issue 和工作分支任务状态一起回到 doing / blocked。
|
||||||
|
4. PR 合并、默认分支任务文件为 `DONE` 后,Issue 才切到 `status/done` 并关闭。
|
||||||
|
5. MCP 当前没有删除分支工具。清理 claim 前先确认 PR 已合并、Issue 已完成且无恢复需要,再由维护者通过 Gitea UI 或受控 REST 操作删除。
|
||||||
|
|
||||||
|
## 过期 claim 与断连
|
||||||
|
|
||||||
|
- agent 应在 `lease_until` 前用新评论续租;续租不更换 claim 分支。
|
||||||
|
- claim 过期不等于可以自动抢占。维护者先检查 Issue 最后活动、工作分支新提交和 PR,再评论回收原因并人工删除 claim 分支。
|
||||||
|
- Gitea / MCP 断连时,只能继续已经确认归属自己的任务;不能领取新任务、释放锁或猜测远端状态。
|
||||||
|
|
||||||
|
## 初始化标签
|
||||||
|
|
||||||
|
先预览,再显式写入:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/setup_gitea_labels.py --repo 【owner/repo】
|
||||||
|
python scripts/setup_gitea_labels.py --repo 【owner/repo】 --apply
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本从环境变量读取 `GITEA_URL`、`GITEA_TOKEN`;HTTP 仍要求 `GITEA_ALLOW_INSECURE_HTTP=1`。默认命令会连接目标仓库做只读比较,显示 create / update / unchanged;`--apply` 会把同名标签的颜色、描述和 exclusive 属性校正为本模板值。MCP 没有创建标签工具,因此标签初始化使用 Gitea REST API 或由维护者在 UI 中完成。
|
||||||
|
|
||||||
|
## 并发验收
|
||||||
|
|
||||||
|
在专用测试任务上让两个进程同时创建同一个临时 claim 分支,验收结果必须是恰好一个 `201`、另一个 `409`。测试前后均需人工确认分支名,清理由受控 REST / UI 完成;不得在业务任务上试验。
|
||||||
@@ -88,3 +88,12 @@ tool_timeout_sec = 60
|
|||||||
4. 保存 `read_file` 返回的文件 SHA;同一会话内 SHA 未变化时复用内容。
|
4. 保存 `read_file` 返回的文件 SHA;同一会话内 SHA 未变化时复用内容。
|
||||||
|
|
||||||
Gitea 中的文件与本地 `docs/` 是同一 Git 工件的远端与 checkout,不要再创建第三份人工同步副本。
|
Gitea 中的文件与本地 `docs/` 是同一 Git 工件的远端与 checkout,不要再创建第三份人工同步副本。
|
||||||
|
|
||||||
|
## Issue / PR 协调
|
||||||
|
|
||||||
|
多 agent 协作时遵循 [`gitea-collaboration.md`](gitea-collaboration.md):
|
||||||
|
|
||||||
|
- 任务文件保存规格和长期证据,Issue 保存实时状态,PR 保存评审与合并决策。
|
||||||
|
- 领取互斥依赖唯一 `claims/T-<编号>` 分支;assignee、`status/doing` 和读回仅作状态确认。
|
||||||
|
- MVP 由单一 dispatcher 串行分配任务并检查 `write_paths`,每个 worker 使用 `agent/<agent-id>/T-<编号>` 和独立 worktree;每任务 claim 只解决同任务重复领取,不单独保证跨任务路径互斥。
|
||||||
|
- MCP 可创建 claim / 工作分支,但当前没有创建标签或删除分支工具。标签用 `python scripts/setup_gitea_labels.py --repo 【owner/repo】 --apply` 幂等初始化;过期 claim 由维护者通过 UI 或受控 REST 人工回收。
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
| 评审主观 | 质量判断靠个人记忆和感觉,agent 容易自我说服通过 | 用固定维度做评分 | [`evaluator-rubric.md`](evaluator-rubric.md) |
|
| 评审主观 | 质量判断靠个人记忆和感觉,agent 容易自我说服通过 | 用固定维度做评分 | [`evaluator-rubric.md`](evaluator-rubric.md) |
|
||||||
| 代码库悄悄退化 | 速度上去了,但几轮会话后代码越来越难审、边界越来越糊 | 定期给代码库健康度打分 | [`quality-document.md`](quality-document.md) |
|
| 代码库悄悄退化 | 速度上去了,但几轮会话后代码越来越难审、边界越来越糊 | 定期给代码库健康度打分 | [`quality-document.md`](quality-document.md) |
|
||||||
| 文档堆叠失控 | 入口文件越来越长,"每次失败加一句" | 渐进披露,入口保持薄 | [`00-ai-start-here.md`](00-ai-start-here.md) + 拆分到具体文档 |
|
| 文档堆叠失控 | 入口文件越来越长,"每次失败加一句" | 渐进披露,入口保持薄 | [`00-ai-start-here.md`](00-ai-start-here.md) + 拆分到具体文档 |
|
||||||
|
| 每轮全量重读 | 多 agent 反复拉取全部文档,慢且容易混入无关上下文 | 用任务路由和提交 / 文件 SHA 增量读取 | [`agent-context.md`](agent-context.md) + [`agent-context.json`](agent-context.json) |
|
||||||
| 多 agent 抢改任务文件 | 多个 agent 并发时抢改同一个看板/进度文件,出现"读到旧版本"、ID 撞号、合并冲突 | 一任务一文件(默认模式已内建),执行记录进任务文件,不逐任务改共享收尾文件 | [`tasks/README.md`](tasks/README.md) |
|
| 多 agent 抢改任务文件 | 多个 agent 并发时抢改同一个看板/进度文件,出现"读到旧版本"、ID 撞号、合并冲突 | 一任务一文件(默认模式已内建),执行记录进任务文件,不逐任务改共享收尾文件 | [`tasks/README.md`](tasks/README.md) |
|
||||||
|
| 多 agent 重复领取 | 两个 agent 同时把同一 Issue 改为 doing,读回后都以为成功 | 用唯一 claim 分支做原子锁,标签只展示状态 | [`gitea-collaboration.md`](gitea-collaboration.md) |
|
||||||
|
| 多 agent 写路径碰撞 | 不同任务同时修改同一目录或共享配置,合并时才发现冲突 | dispatcher 串行声明 / 比较 `write_paths`,worker 使用独立 worktree | [`gitea-collaboration.md`](gitea-collaboration.md) + [`tasks/README.md`](tasks/README.md) |
|
||||||
|
|
||||||
## 使用原则
|
## 使用原则
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -27,6 +27,13 @@ phase: 1 # 所属阶段,沿用路线图的 Phase 编号
|
|||||||
deps: [T-100] # 依赖的任务 ID
|
deps: [T-100] # 依赖的任务 ID
|
||||||
status: TODO # TODO | DOING | DONE | BLOCKED
|
status: TODO # TODO | DOING | DONE | BLOCKED
|
||||||
created: 【日期】
|
created: 【日期】
|
||||||
|
issue: null # Gitea Issue 编号;未启用 Gitea 时保持 null
|
||||||
|
context_ref: null # 领取时默认分支提交 SHA
|
||||||
|
claim_branch: null
|
||||||
|
work_branch: null
|
||||||
|
write_paths: # 允许修改的仓库相对路径
|
||||||
|
- docs/tasks/T-101.md
|
||||||
|
- 【path/to/module】
|
||||||
---
|
---
|
||||||
|
|
||||||
## 问题 / 背景
|
## 问题 / 背景
|
||||||
@@ -38,12 +45,25 @@ created: 【日期】
|
|||||||
|
|
||||||
## 领取 / 完成流程
|
## 领取 / 完成流程
|
||||||
|
|
||||||
- 状态:`TODO` · `DOING`(同一时间最多 1 个)· `DONE` · `BLOCKED`。
|
- 状态:`TODO` · `DOING` · `DONE` · `BLOCKED`。每个 agent 同时最多一个活跃任务;项目可以并行多个写路径互不重叠的任务。
|
||||||
- 一次只领一个 `status: TODO` 且依赖全 `DONE` 的任务,取编号最靠前的;若本目录暂无可领任务,先按路线图把下一个建议任务落成任务文件,再领取。
|
- 每个 agent 一次只领一个 `status: TODO` 且依赖全 `DONE` 的任务,取编号最靠前的;若本目录暂无可领任务,先按路线图把下一个建议任务落成任务文件,再领取。
|
||||||
|
- `write_paths` 必须在动手前写清。两个活跃任务路径相同,或一条是另一条的目录前缀,均视为冲突,不能并行。
|
||||||
- 做完自测、按「passing 需证据」把验证命令与结果写清、改 `status: DONE`。
|
- 做完自测、按「passing 需证据」把验证命令与结果写清、改 `status: DONE`。
|
||||||
- **执行记录写进本任务文件的 `## 执行记录` 一节**(改了什么、跑了什么验证、结果、决策)——不逐任务追加共享的 `progress.md`(可选历史归档)、也不逐任务覆盖 `current-state.md`(项目级快照,只在启动/验证路径、目录结构或 blocker 变化时更新)。
|
- **执行记录写进本任务文件的 `## 执行记录` 一节**(改了什么、跑了什么验证、结果、决策)——不逐任务追加共享的 `progress.md`(可选历史归档)、也不逐任务覆盖 `current-state.md`(项目级快照,只在启动/验证路径、目录结构或 blocker 变化时更新)。
|
||||||
- **只改自己那个任务文件**;不要编辑别人正在做的任务文件。
|
- **只改自己那个任务文件**;不要编辑别人正在做的任务文件。
|
||||||
|
|
||||||
|
未启用 Gitea 时,在独立分支 / worktree 中把任务文件从 `TODO` 改为 `DOING` 即可。启用 Gitea 时,必须先按 [`../gitea-collaboration.md`](../gitea-collaboration.md) 原子创建唯一 `claims/T-<编号>` 分支;assignee、标签和“更新后读回”都不能代替领取锁。
|
||||||
|
|
||||||
|
## 与 Gitea Issue / PR 的映射(可选)
|
||||||
|
|
||||||
|
- 一个任务文件对应一个主 Issue;Issue 负责实时领取、阻塞和评审状态,任务文件负责版本化规格和长期证据。
|
||||||
|
- 先把任务文件合入默认分支,再创建 Issue;随后把 Issue 编号回填任务文件并合入默认分支,最后才添加 `status/todo`。映射未完成的 Issue 不可领取。
|
||||||
|
- Issue、claim 分支、工作分支和 PR 都携带同一个 `T-<编号>`;不得用一个 PR 顺带完成多个任务。
|
||||||
|
- 工作分支命名为 `agent/<agent-id>/T-<编号>`,每个 agent 使用独立 worktree。
|
||||||
|
- MVP 由一个 dispatcher / 主 agent 串行分配任务。唯一 claim 分支保证同一任务不被重复领取,dispatcher 的串行检查保证不同任务的 `write_paths` 不冲突。
|
||||||
|
- 进入评审后 Issue 使用唯一 `status/review`;PR 合并且默认分支任务文件为 `DONE` 后,Issue 才能关闭并标记 `status/done`。
|
||||||
|
- 领取、结构化 claim 评论、过期锁回收和分支清理的完整规则见 [`../gitea-collaboration.md`](../gitea-collaboration.md)。
|
||||||
|
|
||||||
## 用户指令暗语(可选约定)
|
## 用户指令暗语(可选约定)
|
||||||
|
|
||||||
> 用户的工作流通常固定为:提 bug/需求 → 讨论定案 → 落成任务文件 → 提交 → 实现 → 提交。
|
> 用户的工作流通常固定为:提 bug/需求 → 讨论定案 → 落成任务文件 → 提交 → 实现 → 提交。
|
||||||
@@ -72,6 +92,7 @@ created: 【日期】
|
|||||||
|
|
||||||
## 与路线图和共享文件的关系
|
## 与路线图和共享文件的关系
|
||||||
|
|
||||||
- [`../06-tasks.md`](../06-tasks.md):只读路线图(Phase 划分、里程碑、Backlog、建议拆分清单);真实任务状态一律以本目录任务文件的 frontmatter 为准。
|
- [`../06-tasks.md`](../06-tasks.md):只读路线图(Phase 划分、里程碑、Backlog、建议拆分清单);未启用 Gitea 时以任务文件 frontmatter 为准,启用后以 Issue 为实时状态、合并后的任务文件为长期事实。
|
||||||
- `../../progress.md`:可选工件,用作历史归档或项目级大事记;执行记录写各任务文件,不逐任务追加。
|
- `../../progress.md`:可选工件,用作历史归档或项目级大事记;执行记录写各任务文件,不逐任务追加。
|
||||||
- [`../current-state.md`](../current-state.md):项目级快照(启动/验证路径、目录要点、blocker);任务状态不在此维护,可由脚本汇总 frontmatter 生成。
|
- [`../current-state.md`](../current-state.md):项目级快照(启动/验证路径、目录要点、blocker);任务状态不在此维护,可由脚本汇总 frontmatter 生成。
|
||||||
|
- [`../gitea-collaboration.md`](../gitea-collaboration.md):启用 Gitea 时的任务映射、原子领取、写路径防撞和 PR 状态协议。
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ phase: 1
|
|||||||
deps: []
|
deps: []
|
||||||
status: TODO
|
status: TODO
|
||||||
created: 【日期】
|
created: 【日期】
|
||||||
|
issue: null
|
||||||
|
context_ref: null
|
||||||
|
claim_branch: null
|
||||||
|
work_branch: null
|
||||||
|
write_paths:
|
||||||
|
- docs/tasks/T-XXX.md
|
||||||
|
- 【允许修改的仓库相对路径】
|
||||||
---
|
---
|
||||||
|
|
||||||
## 问题 / 背景
|
## 问题 / 背景
|
||||||
@@ -23,6 +30,10 @@ created: 【日期】
|
|||||||
|
|
||||||
(明确不碰的模块/流程)
|
(明确不碰的模块/流程)
|
||||||
|
|
||||||
|
## 协作约束
|
||||||
|
|
||||||
|
(启用 Gitea 时填写对应 Issue、领取时的 `context_ref`、claim / 工作分支;任何新增写路径先检查与其他活跃任务是否重叠。)
|
||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
(做完在此记录:改了哪些文件、跑的验证命令与结果、阻塞、关键决策。
|
(做完在此记录:改了哪些文件、跑的验证命令与结果、阻塞、关键决策。
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Preview or idempotently apply Harness Coding labels to one Gitea repo."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
LABELS: tuple[dict[str, Any], ...] = (
|
||||||
|
{
|
||||||
|
"name": "kind/task",
|
||||||
|
"color": "0052CC",
|
||||||
|
"description": "Harness Coding task",
|
||||||
|
"exclusive": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "type/docs",
|
||||||
|
"color": "5319E7",
|
||||||
|
"description": "Documentation change",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "type/code",
|
||||||
|
"color": "1D76DB",
|
||||||
|
"description": "Code or automation change",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status/todo",
|
||||||
|
"color": "C5DEF5",
|
||||||
|
"description": "Ready to claim",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status/doing",
|
||||||
|
"color": "FBCA04",
|
||||||
|
"description": "Claimed and in progress",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status/blocked",
|
||||||
|
"color": "D93F0B",
|
||||||
|
"description": "Blocked; claim retained",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status/review",
|
||||||
|
"color": "BFD4F2",
|
||||||
|
"description": "Pull request under review",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status/done",
|
||||||
|
"color": "0E8A16",
|
||||||
|
"description": "Merged and completed",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priority/p0",
|
||||||
|
"color": "B60205",
|
||||||
|
"description": "Highest priority",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priority/p1",
|
||||||
|
"color": "D93F0B",
|
||||||
|
"description": "High priority",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priority/p2",
|
||||||
|
"color": "FBCA04",
|
||||||
|
"description": "Normal priority",
|
||||||
|
"exclusive": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiError(RuntimeError):
|
||||||
|
def __init__(self, status: int, reason: str) -> None:
|
||||||
|
super().__init__(f"Gitea API 返回 HTTP {status}:{reason}")
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
|
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||||
|
"""Never forward the Authorization header to a redirected origin."""
|
||||||
|
|
||||||
|
def redirect_request(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="读取远端差异,并可幂等创建或校正 Harness Coding Gitea 标签。"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--repo",
|
||||||
|
default=os.environ.get("GITEA_REPOSITORY"),
|
||||||
|
help="目标 owner/repo;也可设置 GITEA_REPOSITORY。",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--apply",
|
||||||
|
action="store_true",
|
||||||
|
help="应用预览中的 create/update;省略时只读远端并打印差异。",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_config(url: str, token: str, repo: str) -> tuple[str, str, str]:
|
||||||
|
parsed = urllib.parse.urlsplit(url.strip())
|
||||||
|
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
||||||
|
raise ValueError("GITEA_URL 必须是 http(s) 实例根地址。")
|
||||||
|
if parsed.username or parsed.password or parsed.query or parsed.fragment:
|
||||||
|
raise ValueError("GITEA_URL 不得包含凭据、query 或 fragment。")
|
||||||
|
path = parsed.path.rstrip("/")
|
||||||
|
if path.lower().endswith("/api/v1"):
|
||||||
|
raise ValueError("GITEA_URL 不得包含 /api/v1。")
|
||||||
|
if parsed.scheme == "http" and os.environ.get("GITEA_ALLOW_INSECURE_HTTP") != "1":
|
||||||
|
raise ValueError("HTTP 需要显式设置 GITEA_ALLOW_INSECURE_HTTP=1。")
|
||||||
|
if not token:
|
||||||
|
raise ValueError("缺少 GITEA_TOKEN。")
|
||||||
|
parts = repo.split("/")
|
||||||
|
if len(parts) != 2 or not all(parts):
|
||||||
|
raise ValueError("--repo 必须使用 owner/repo 格式。")
|
||||||
|
root = urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, path, "", ""))
|
||||||
|
return root.rstrip("/"), parts[0], parts[1]
|
||||||
|
|
||||||
|
|
||||||
|
class GiteaClient:
|
||||||
|
def __init__(self, root: str, owner: str, repo: str, token: str) -> None:
|
||||||
|
owner_q = urllib.parse.quote(owner, safe="")
|
||||||
|
repo_q = urllib.parse.quote(repo, safe="")
|
||||||
|
self.base = f"{root}/api/v1/repos/{owner_q}/{repo_q}"
|
||||||
|
self.token = token
|
||||||
|
proxy_handler = (
|
||||||
|
urllib.request.ProxyHandler({})
|
||||||
|
if os.environ.get("GITEA_DIRECT") == "1"
|
||||||
|
else urllib.request.ProxyHandler()
|
||||||
|
)
|
||||||
|
self.opener = urllib.request.build_opener(proxy_handler, NoRedirect())
|
||||||
|
|
||||||
|
def request(
|
||||||
|
self, method: str, path: str, payload: dict[str, Any] | None = None
|
||||||
|
) -> Any:
|
||||||
|
data = None if payload is None else json.dumps(payload).encode("utf-8")
|
||||||
|
request = urllib.request.Request(
|
||||||
|
self.base + path,
|
||||||
|
data=data,
|
||||||
|
method=method,
|
||||||
|
headers={
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": f"token {self.token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with self.opener.open(request, timeout=30) as response:
|
||||||
|
body = response.read()
|
||||||
|
return json.loads(body.decode("utf-8")) if body else None
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
raise ApiError(exc.code, exc.reason) from None
|
||||||
|
except urllib.error.URLError as exc:
|
||||||
|
raise RuntimeError(f"连接 Gitea 失败:{exc.reason}") from None
|
||||||
|
|
||||||
|
def list_labels(self) -> dict[str, dict[str, Any]]:
|
||||||
|
result: dict[str, dict[str, Any]] = {}
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
labels = self.request("GET", f"/labels?limit=50&page={page}")
|
||||||
|
if not isinstance(labels, list):
|
||||||
|
raise RuntimeError("Gitea labels 响应格式异常。")
|
||||||
|
for label in labels:
|
||||||
|
if isinstance(label, dict) and isinstance(label.get("name"), str):
|
||||||
|
result[label["name"]] = label
|
||||||
|
if len(labels) < 50:
|
||||||
|
return result
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_color(value: Any) -> str:
|
||||||
|
return str(value or "").lstrip("#").upper()
|
||||||
|
|
||||||
|
|
||||||
|
def needs_update(current: dict[str, Any], desired: dict[str, Any]) -> bool:
|
||||||
|
return (
|
||||||
|
normalize_color(current.get("color")) != desired["color"]
|
||||||
|
or str(current.get("description") or "") != desired["description"]
|
||||||
|
or bool(current.get("exclusive")) != desired["exclusive"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_plan(
|
||||||
|
existing: dict[str, dict[str, Any]],
|
||||||
|
) -> list[tuple[str, dict[str, Any], dict[str, Any] | None]]:
|
||||||
|
plan = []
|
||||||
|
for desired in LABELS:
|
||||||
|
current = existing.get(desired["name"])
|
||||||
|
if current is None:
|
||||||
|
action = "create"
|
||||||
|
elif needs_update(current, desired):
|
||||||
|
action = "update"
|
||||||
|
else:
|
||||||
|
action = "unchanged"
|
||||||
|
plan.append((action, desired, current))
|
||||||
|
return plan
|
||||||
|
|
||||||
|
|
||||||
|
def show_plan(repo: str, plan: list[tuple[str, dict[str, Any], Any]]) -> None:
|
||||||
|
print(f"目标仓库:{repo}")
|
||||||
|
for action, desired, _ in plan:
|
||||||
|
scope = "exclusive" if desired["exclusive"] else "normal"
|
||||||
|
print(f"- {action:9} {desired['name']} #{desired['color']} {scope}")
|
||||||
|
counts = {name: sum(action == name for action, _, _ in plan) for name in (
|
||||||
|
"create",
|
||||||
|
"update",
|
||||||
|
"unchanged",
|
||||||
|
)}
|
||||||
|
print(
|
||||||
|
"计划汇总:"
|
||||||
|
f"创建 {counts['create']},更新 {counts['update']},未变化 {counts['unchanged']}。"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_plan(
|
||||||
|
client: GiteaClient,
|
||||||
|
plan: list[tuple[str, dict[str, Any], dict[str, Any] | None]],
|
||||||
|
) -> None:
|
||||||
|
created = updated = unchanged = 0
|
||||||
|
for action, desired, current in plan:
|
||||||
|
if action == "unchanged":
|
||||||
|
unchanged += 1
|
||||||
|
continue
|
||||||
|
if action == "update":
|
||||||
|
label_id = None if current is None else current.get("id")
|
||||||
|
if not isinstance(label_id, int):
|
||||||
|
raise RuntimeError(f"标签 {desired['name']} 缺少数字 id。")
|
||||||
|
client.request("PATCH", f"/labels/{label_id}", dict(desired))
|
||||||
|
updated += 1
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
client.request("POST", "/labels", dict(desired))
|
||||||
|
created += 1
|
||||||
|
except ApiError as exc:
|
||||||
|
if exc.status != 422:
|
||||||
|
raise
|
||||||
|
latest = client.list_labels().get(desired["name"])
|
||||||
|
if latest is None:
|
||||||
|
raise
|
||||||
|
if needs_update(latest, desired):
|
||||||
|
label_id = latest.get("id")
|
||||||
|
if not isinstance(label_id, int):
|
||||||
|
raise RuntimeError(f"标签 {desired['name']} 缺少数字 id。")
|
||||||
|
client.request("PATCH", f"/labels/{label_id}", dict(desired))
|
||||||
|
updated += 1
|
||||||
|
else:
|
||||||
|
unchanged += 1
|
||||||
|
print(f"标签同步完成:创建 {created},更新 {updated},未变化 {unchanged}。")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
if not args.repo:
|
||||||
|
print("ERROR: 需要 --repo owner/repo 或 GITEA_REPOSITORY。", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
url = os.environ.get("GITEA_URL", "")
|
||||||
|
token = os.environ.get("GITEA_TOKEN", "")
|
||||||
|
try:
|
||||||
|
root, owner, name = validate_config(url, token, args.repo)
|
||||||
|
client = GiteaClient(root, owner, name, token)
|
||||||
|
plan = build_plan(client.list_labels())
|
||||||
|
show_plan(args.repo, plan)
|
||||||
|
if not args.apply:
|
||||||
|
print("dry-run:未写入;追加 --apply 才会应用上述 create/update。")
|
||||||
|
return 0
|
||||||
|
apply_plan(client, plan)
|
||||||
|
return 0
|
||||||
|
except (ValueError, ApiError, RuntimeError) as exc:
|
||||||
|
print(f"ERROR: {exc}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -96,7 +96,7 @@ Get-ChildItem -Recurse -File
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| H-601 | Phase 0:建立 Gitea MCP 安全与连接基线 | H-409 | 固定 MCP 版本;私有配置不入库;HTTP 需显式确认风险;读写审批和断连降级规则清楚 | DONE |
|
| H-601 | Phase 0:建立 Gitea MCP 安全与连接基线 | H-409 | 固定 MCP 版本;私有配置不入库;HTTP 需显式确认风险;读写审批和断连降级规则清楚 | DONE |
|
||||||
| H-602 | Phase 1:增加上下文清单与按需读取流程 | H-601 | 有机器可读清单和无第三方依赖验证;agent 按任务类型读取;同一 SHA 不重复加载 | DONE |
|
| H-602 | Phase 1:增加上下文清单与按需读取流程 | H-601 | 有机器可读清单和无第三方依赖验证;agent 按任务类型读取;同一 SHA 不重复加载 | DONE |
|
||||||
| H-603 | Phase 2:建立 Issue / 任务文件 / PR 多 Agent 协调协议 | H-602 | 任务映射、领取读回校验、分支 / worktree 和写路径防撞规则完整 | TODO |
|
| H-603 | Phase 2:建立 Issue / 任务文件 / PR 多 Agent 协调协议 | H-602 | 任务映射、领取读回校验、分支 / worktree 和写路径防撞规则完整 | DONE |
|
||||||
| H-604 | Phase 3:增加自动化治理与一致性检查 | H-603 | 导航、清单、任务元数据、敏感信息和 Gitea Actions 检查可运行 | TODO |
|
| H-604 | Phase 3:增加自动化治理与一致性检查 | H-603 | 导航、清单、任务元数据、敏感信息和 Gitea Actions 检查可运行 | TODO |
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|||||||
Reference in New Issue
Block a user