Add harness coding docs for SoftBox (Go + Gio dual-build)
Harness governance / validate (push) Has been cancelled

Initialize the full harness coding document set from the
harness_coding_docs template, customized for the SoftBox project:

- Vision, requirements, tech stack (modern Go 1.25 + Gio v0.10.1;
  Win7 legacy Go 1.20.14 + Gio v0.6.0), architecture, coding rules
- Protocol contracts (signed catalog, package protocol v1, Ed25519
  license, events, CLI) and Gio view structure
- Roadmap Phase 0-6 with 20 suggested tasks; T-001 (monorepo
  skeleton) filed and ready to claim
- Agent entry points (AGENTS.md, docs/00-ai-start-here.md),
  context manifest, governance scripts and tests
- Merge Go gitignore with harness rules; keep go.work tracked

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-16 14:33:54 +08:00
co-authored by Claude Fable 5
parent 03bd4ab62a
commit 0184595ccc
43 changed files with 5010 additions and 10 deletions
+154
View File
@@ -0,0 +1,154 @@
# AI 开发入口
> 给 AI coding agent 的项目入口。这里负责导航和流程,硬性编码规则见 [`05-coding-rules.md`](05-coding-rules.md)。
## 一句话定位
SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端,为自家软件产品家族提供「发现 → 下载 → 安装 → 更新 → 启动 → 授权」的一站式安全闭环,并以独立遗留版兼容 Win7 SP1 用户。
第一版 MVP 只做:签名清单、列表/搜索、下载队列、ZIP 安全安装、更新与回滚、启动与运行检测、盒子自更新、机器绑定许可证、现代版 + Win7 版双构建。
## 上下文读取
首次接入、[`agent-context.json`](agent-context.json) 缺失或校验失败时,按这个顺序建立完整上下文:
1. [`01-vision.md`](01-vision.md):为什么做、为谁做、什么不做。
2. [`02-requirements.md`](02-requirements.md):MVP 要什么、怎么算达成。
3. [`03-tech-stack.md`](03-tech-stack.md):既定技术选型与双工具链矩阵。
4. [`04-architecture.md`](04-architecture.md):分层结构、目录、数据模型和关键安全流程。
5. [`05-coding-rules.md`](05-coding-rules.md):写代码前必须遵守的规则。
6. [`06-tasks.md`](06-tasks.md):阶段路线图、里程碑和待办池。
7. [`tasks/README.md`](tasks/README.md):任务文件约定(一任务一文件);本轮任务从 `docs/tasks/` 领取。
8. [`current-state.md`](current-state.md):当前代码现实、可运行命令、下一步任务。
日常会话不需要机械重读全部文档:
1. 读取仓库级规则(`AGENTS.md`)和 [`agent-context.json`](agent-context.json)。
2. 读取 `bootstrap.always_read`。
3. 读取本轮任务文件。
4. 按任务类型读取 `routes` 中的文档;一个文件命中多个路由时只读一次。
5. 记录默认分支头提交为 `context_ref`;同一会话中文件 SHA 未变化时复用已读内容。
清单的使用、缓存和断连降级规则见 [`agent-context.md`](agent-context.md)。
## 固定开工流程
读完上述文档后,每轮会话按这个机械顺序进入,恢复持久状态再动手:
1. `pwd`:确认在正确的仓库根目录。
2. 读 [`current-state.md`](current-state.md) 和 `docs/tasks/` 中当前 agent 的活跃任务,恢复已验证状态、下一步和当前 blocker。
3. `git log --oneline -5`:看清最近发生了什么。
4. 运行 `./init.sh`(Windows 原生 PowerShell 用 `./init.ps1`):统一安装、验证、打印启动命令;T-001 完成前脚本会提示命令未替换,属预期。
5. 跑一条基础 smoke 路径(core 测试 + 双目标编译),确认基线没坏。
6. **如果基线已坏,先修基线**,不要在坏的起点上叠新功能。
7. 基线绿了,再从 `docs/tasks/` 领取唯一任务(路线图见 [`06-tasks.md`](06-tasks.md))。
## 当前阶段
当前项目处于:**MVP 起步(Phase 0 工程骨架尚未建立,仓库只有文档)**。
优先路径:
1. Phase 0:monorepo 骨架 + 双目标编译 + 空 Gio 窗口。
2. Phase 1:三大高风险原型(清单验签、ZIP 安全解压、原子切换回滚)。
3. Phase 2-3:清单/列表 → 下载/安装。
4. Phase 4-5:启动/更新/自更新 → 授权。
5. Phase 6:Win7 加固与双通道发布。
## 领取任务规则
任务以「一任务一文件」存放在 `docs/tasks/`(约定见 [`tasks/README.md`](tasks/README.md)):
- 每个 agent 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的;项目可并行多个 `write_paths` 互不重叠的任务。
- 若 `docs/tasks/` 暂无可领任务,先按 [`06-tasks.md`](06-tasks.md) 路线图把下一个建议任务落成任务文件,再领取。
- 开始前在独立分支 / worktree 把该文件 frontmatter 的 `status` 改为 `DOING`。
- 本轮只完成这一个任务;验收通过后改为 `DONE`。
- **执行记录写进该任务文件的 `## 执行记录`**(改了什么、跑了什么验证、结果、决策)。
- 若项目现实发生变化(启动/验证路径、目录结构、blocker),覆盖更新 [`current-state.md`](current-state.md)。
- 结束会话前过一遍 [`clean-state-checklist.md`](clean-state-checklist.md),确保下一轮无需人工修复即可开工。
- 做完即停,汇报验证结果,等待下一步指令。
如果代码实际状态和任务文件冲突,先说明冲突,不要擅自跳步或重排。
## MVP 边界
MVP 只做:
- 签名清单获取、缓存与过滤;列表、分类、搜索。
- 下载队列(并发/暂停/续传/恢复)与 ZIP 安全安装(staging/backup/回滚)。
- 版本检测、子软件更新、软件启动与进程检测。
- SoftBoxUpdater 盒子自更新。
- Ed25519 机器绑定许可证(导入、离线验证、试用)。
- 现代版(Win10/11 x64)+ Win7 遗留版(SP1 x64)双构建、双更新通道。
MVP 不做:
- 评论/评分/社区、在线支付、多账号云同步。
- 皮肤市场、插件脚本执行、驱动安装。
- files.json 修复、命名管道优雅退出、便携模式、beta 通道、Win7 x86(见 Backlog)。
## 事实来源
项目事实只信:
- [`02-requirements.md`](02-requirements.md):功能范围与验收标准。
- [`api.md`](api.md) 与 `schemas/`:清单、软件包、许可证、事件、CLI 的协议合约。
- [`04-architecture.md`](04-architecture.md):分层边界、目录布局、状态机、安全流程。
- [`03-tech-stack.md`](03-tech-stack.md):工具链矩阵与依赖纪律。
- 现有代码中的 `core/domain` 与 `core/application`(骨架建立后)。
不要把以下内容当事实来源:
- 对旧商用盒子的逆向分析笔记(那是设计输入,不是本仓库协议)。
- 历史备份文件、旧导出文档、临时实验目录。
- 未被任务或需求引用的草稿。
## 常见任务该看哪里
做 Gio 页面 / UI:
- 先看 `02-requirements.md` 的对应验收标准。
- 再看 `routes.md` 的视图职责与交互硬约束。
- 最后看 `04-architecture.md` 的 UI 交互模式(事件驱动、无 IO Layout)。
做 core 业务模块(catalog/downloader/installer/licensing/updater):
- 先看 `api.md` 的协议合约与事件合约。
- 再看 `04-architecture.md` 的分层约束、状态机和安全流程。
做协议 / 数据结构:
- 先看 `api.md` 和 `schemas/`。
- 如果结构变化,必须同步更新 `api.md`、`04-architecture.md`、`schemas/`、`current-state.md` 和相关任务验收。
做平台层(platform/windows):
- 先看 `04-architecture.md` 的平台层约束(接口注入、非 Windows stub、动态加载降级)。
- 再看 `03-tech-stack.md` 的 Win7 API 兼容要求。
做构建 / 发布:
- 先看 `03-tech-stack.md` 的工具链矩阵和命令。
- 再看 `current-state.md` 的当前真实命令。
## 验证命令
统一启动与验证入口收敛到根目录 `./init.sh` / `./init.ps1`;T-001 完成前脚本处于未配置状态。骨架建立后的标准命令:
```bash
# core 测试(Linux 无头可跑)
cd core && go vet ./... && go test -count=1 ./...
# 现代版构建
cd app-modern && GOOS=windows GOARCH=amd64 go build ./cmd/softbox
# Win7 版构建(强制 Go 1.20)
cd app-win7 && GOTOOLCHAIN=go1.20.14 GOOS=windows GOARCH=amd64 go build ./cmd/softbox
```
说明:
- 改 core 后跑:core 测试 + 两个构建目标(防止 Go 1.20 兼容性破坏)。
- 改 UI 后跑:对应 app 构建 + UI 事件测试(fake submitter + ApplyEvent)。
- 改协议 / 数据结构后跑:core 测试 + `schemas/` 校验 + 同步文档。
- 如果命令当前不可运行(骨架未建成),必须在回复里如实说明原因。
+46
View File
@@ -0,0 +1,46 @@
# 项目愿景
## 一、核心目标
SoftBox 软件盒子要解决:用户获取和维护自家系列软件时,需要手动访问下载页、选择安装包、解压、找 EXE、手动升级、手动输注册码的低效和易错问题。
> 让自家软件的用户能够在一个桌面客户端里完成「发现软件 → 下载/更新 → 启动 → 授权」的完整闭环,并获得安全、可回滚的安装体验。
它不是 MSI 安装器、应用商店服务或系统级包管理器,而是一个为「自家软件产品家族的分发、更新和授权」服务的桌面工具:远程软件目录 + 安全下载器 + 便携安装器 + 更新器 + 启动器 + 授权入口。
## 二、目标用户
- **Win10/Win11 主流用户**:一站式获取、更新和启动系列软件,不关心安装细节。
- **Win7 SP1 遗留用户**(少量存量):在老系统上继续可用的遗留版盒子,能下载兼容其系统的软件版本。
- **软件发布者(自己)**:通过签名 Catalog 集中发布、更新和撤销软件,通过许可证系统管理授权与绑定电脑。
## 三、产品原则
遇到取舍时,以这些原则为准:
- **业务逻辑优先**:清单、下载、校验、更新、授权的正确性优先于视觉效果。
- **安全更新**:HTTPS + 签名清单 + SHA-256 + staging/backup/回滚;未验证内容绝不执行。
- **Gio 仅限 UI**:业务核心不感知 UI;Windows 能力隔离在平台层。
- **现代版和 Win7 版共享核心**:一个主分支、共享 core、两个构建模块、两个更新通道;不维护两个长期开发分支。
- **小步交付**:每一步都能运行、能验证、能回退。
- **可维护**:分层边界清晰,让后续 agent 能继续接手。
## 四、核心价值主张
| 价值点 | 说明 |
| --- | --- |
| 一站式软件管理 | 发现、下载、更新、启动在一个列表中完成,不再手动找包解压 |
| 安全可回滚 | 签名清单 + 哈希校验 + 原子切换,更新失败自动恢复旧版本 |
| Win7 兼容 | 遗留用户获得独立的 Legacy 版本和更新通道,不被现代版工具链抛弃 |
| 离线授权 | Ed25519 签名许可证 + 机器绑定,离线可验证,支持换绑与恢复 |
## 五、不做什么(非目标)
- 不做用户评论、评分和社区功能。
- 不做在线支付和多账号云同步。
- 不做复杂皮肤市场、插件脚本执行。
- 不做系统级驱动安装和强依赖管理员权限的软件部署。
- 不做通用第三方软件商店;只分发自家 Catalog 中签名发布的软件。
- 不把所有版本长期锁在 Win7 工具链;Win7 版定位 Legacy,限制新增功能。
> MVP 的具体功能范围与验收标准,见 [需求](02-requirements.md)。
+91
View File
@@ -0,0 +1,91 @@
# 需求
> 本文只描述**要什么**与**怎么算达成**,用产品 / 用户语言表达,**不涉及技术实现**。
> 技术方案、数据结构、字段定义见 [架构设计](04-architecture.md) 与 [协议合约](api.md)。
## 一、业务现状
| 项 | 状态 |
| --- | --- |
| 用户 | 自家系列软件的用户,目前手动下载、解压、升级、输注册码,流程易错 |
| 数据 | 已有系列软件的安装包和授权体系(旧盒子);新 Catalog、许可证格式待按新协议建立 |
| 现有系统 | 参考过一个商用软件盒子的逻辑(下载、更新、注册绑定电脑);本项目为全新自研,不复用其代码和旧注册码算法 |
| 约束 | 需兼容少量 Win7 SP1 用户;下载与更新必须防篡改;不依赖管理员权限 |
## 二、用户角色
- **普通用户**:浏览软件列表、下载、更新、启动软件、导入许可证、试用。
- **Win7 遗留用户**:使用 Legacy 版盒子,只能看到并下载兼容自己系统的软件包。
- **发布者(自己,不在客户端内)**:通过 Catalog 发布签名清单和软件包、签发许可证、维护撤销名单。
## 三、功能清单
### 第一版 MVP(最小闭环)
| 功能 | 用户能做什么 | 优先级 |
| --- | --- | --- |
| 远程软件清单 | 打开盒子看到签名清单中的软件;断网时看到最后一次验证成功的缓存 | P0 |
| 分类、标签和搜索 | 按名称/标签搜索,按分类和「全部/已安装/可更新」视图筛选 | P0 |
| 本地安装状态识别 | 每款软件显示未安装/已安装/可更新/下载中/运行中等状态 | P0 |
| 下载队列与进度 | 排队、开始、暂停、取消、重试;看到速度、进度和剩余时间;重启后恢复 | P0 |
| ZIP 安全解压与安装 | 下载校验后一键安装;失败不破坏已安装版本 | P0 |
| 版本检测和更新 | 有新版本时提示并可一键更新;更新失败自动回滚 | P0 |
| 软件启动与运行检测 | 一键启动;运行中的软件不被更新覆盖 | P0 |
| 盒子自身更新 | 盒子提示自身新版本并安全自更新,失败可恢复 | P0 |
| 机器绑定许可证 | 导入许可证,离线验证,区分正式版/试用版/授权错误 | P0 |
| 双版本 | 现代版(Win10/11 x64)和 Win7 遗留版各自可用,更新通道不交叉 | P0 |
| 日志与失败恢复 | 关键操作有日志;下载/安装失败有明确提示和重试路径 | P0 |
### 后续迭代
| 功能 | 描述 | 阶段 |
| --- | --- | --- |
| files.json 复核与修复 | 按文件清单发现缺失/被改文件并修复安装 | V1.1 |
| 命名管道优雅退出 | 更新前通知子软件保存并退出(prepare_update/ready) | V1.1 |
| --softbox-info / --softbox-health | 子软件标准自检参数接入健康检查 | V1.1 |
| Win7 x86 构建 | 有真实用户需求时提供 32 位遗留版 | V2 |
| 便携模式 | portable.flag 触发相对目录存储 | V2 |
| beta 通道 | 用户可选参与 beta 清单 | V2 |
| SQLite 存储 | 历史记录、全文搜索需求出现后再评估 | V2+ |
## 四、核心用户故事(MVP)
1. 作为普通用户,我打开盒子后能看到软件列表(断网也能看到缓存的列表),并能搜索和按分类筛选。
2. 我可以点击「下载」,看到进度和速度;下载完成后自动校验、安装,状态变为「已安装」;我点击「启动」即可使用软件。
3. 当软件有新版本时,列表中显示「可更新」;我点击更新,旧版本被安全替换;若更新中途失败,软件仍能以旧版本启动。
4. 我导入许可证文件后,盒子显示已授权的软件;子软件独立启动时同样能识别授权;把许可证复制到别的电脑无法通过验证。
5. 当清单签名被篡改、下载包哈希不符、磁盘不足或断网时,盒子给出明确提示,不执行任何未验证内容,也不破坏已安装的软件。
6. 作为 Win7 用户,我使用遗留版盒子,只看到并下载 Win7 兼容的软件包,不会被推送无法启动的现代版。
## 五、验收标准(MVP)
- **清单**:断网启动可读取最后验证成功的缓存;伪造或篡改的清单被拒绝且不回退到未验证内容;下架软件通过 status 隐藏而不是改名。
- **下载**:并发默认 2;暂停/取消/重试可用;中断后重启盒子能续传;速度、进度、剩余时间实时刷新。
- **安装/更新**:哈希不符、路径穿越、磁盘不足、目标程序运行中——每种情况都有确定的失败结果且不破坏 current;更新失败后旧版本仍可启动。
- **启动**:启动前检查文件存在、系统兼容和授权;运行中软件在列表显示「运行中」;运行状态通过进程快照检测。
- **盒子自更新**:更新过程中断电/失败,下次启动可恢复旧版;新版启动后写入健康状态。
- **授权**:许可证离线可验证;machine_hash 不匹配即拒绝;更新任何软件都不覆盖许可证与用户数据;试用与正式状态在盒子和子软件中一致。
- **双版本**:两个 EXE 分别在 Win11 和 Win7 SP1 真机可打开并完成「清单→下载→安装→启动」;Win7 版清单不出现现代版软件包。
- **性能**:数百个软件项列表滚动流畅;界面每帧不做磁盘/网络 IO。
## 六、范围边界与决策
| 问题 | 决策 |
| --- | --- |
| 第一版平台 | Windows 桌面(现代版 x64 + Win7 遗留版 x64;x86 按需) |
| 是否需要账号 | 否;授权基于许可证文件 + 机器绑定,无在线账号体系 |
| 第一版范围 | 上表 P0 的最小闭环 |
| 软件包格式 | ZIP(标准软件包协议 v1),不再依赖 UnRAR.dll |
| 暂不支持 | 评论/评分/社区、支付、云同步、皮肤市场、插件脚本、驱动安装 |
| Win7 定位 | Legacy:仅兼容与严重修复,有用户占比门槛和停止维护日期 |
## 七、待确认 / 风险点
- **Catalog 服务端**:清单签名发布器和对象存储的落地方式(本仓库只消费签名清单,发布侧在 softbox-catalog 仓库);首期可用本地/静态文件模拟,需确认正式域名和存储。
- **签名密钥管理**:Ed25519 私钥的保管和签发流程,谁持有、如何轮换;私钥绝不进入本仓库。
- **Win7 支持门槛**:真实 Win7 用户占比未量化;需确认是否提供 x86 和 Legacy 停止维护日期。
- **授权迁移**:旧盒子存量用户的注册码如何换发为新许可证,人工流程谁负责。
- **第三方平台风险**:下载走自有对象存储/CDN,需确认带宽成本和 URL 防盗链策略。
- **自动化边界**:盒子不强杀用户进程;更新前等待用户关闭软件,超时取消更新。
- **隐私**:只上送/记录 machine_hash,不保存原始序列号和 MAC;日志不含注册码和敏感查询参数。
- **Authenticode 证书**:EXE 代码签名证书的采购与签名流水线接入时间点。
+63
View File
@@ -0,0 +1,63 @@
# 技术栈(Tech Stack)
> "用什么"的统一速查表。选型与理由在此集中维护;"怎么把它们搭起来"见 [架构设计](04-architecture.md)。
> 未定项必须标为待定,不要让 agent 在代码里自行决定。
## 一、技术栈一览
| 维度 | 选型 | 状态 | 理由 / 说明 |
| --- | --- | --- | --- |
| 语言 | Go | 已定 | 网络、并发、校验、单二进制交付、可测试性强 |
| UI 框架 | Gio(纯 Go 立即模式 GUI) | 已定 | 无 CGO、单 EXE、跨版本可锁定;仅限 UI 层 |
| 现代版工具链 | Go 1.25(锁定已验证版本) + Gio v0.10.1 | 已定 | 当前受支持工具链;支持 Win10/Win11 x64 |
| Win7 版工具链 | Go 1.20.14(最终补丁) + Gio v0.6.0 | 已定 | Go 1.20 是最后支持 Win7 的版本;Gio v0.6.0 可由 Go 1.20 构建 |
| 模块结构 | go.work + 三个 go.mod(core / app-modern / app-win7) | 已定 | 共享核心,双构建互不污染工具链要求 |
| 依赖管理 | 版本锁定 + vendor(Gio 及相关依赖) | 已定 | 防止 Win7 构建被隐式升级到要求 Go 1.21+ 的依赖 |
| 存储 | JSON 文件 + 临时文件原子替换 | 已定 | 数百款软件规模足够;SQLite 仅在历史/搜索/事务需求出现后评估 |
| 软件包格式 | ZIP(标准软件包协议 v1) | 已定 | 避免 UnRAR.dll;协议见 [api.md](api.md) |
| 清单/许可证签名 | Ed25519(客户端只内置公钥) | 已定 | 离线可验证;私钥不进客户端与仓库 |
| 包完整性 | SHA-256 | 已定 | 下载后、执行前强制校验 |
| 传输 | HTTPS | 已定 | 清单与软件包一律 HTTPS |
| Windows API | golang.org/x/sys/windows + 动态加载(LoadLibrary) | 已定 | Win10 专属 API 动态加载并降级,不进导入表 |
| EXE 签名 | Authenticode | 已定 | 主程序与 Updater 均签名;证书采购待确认 |
| 测试 | go test(core 无头可测) + fake/stub 注入 | 已定 | domain/application 不依赖 Gio 与 Windows API |
| CI | 待定(Gitea Actions 或本地脚本) | 待定 | 需同时编译 modern 与 win7 双目标 |
| 静态检查 | go vet(+ 待定 golangci-lint) | 部分已定 | vet 必跑;lint 工具后续确认 |
## 二、决策记录与演进
- **双版本策略**:不维护两个长期开发分支;一个 main 主分支 + 共享 core + 两个 go.mod + 两个更新通道。仅当 Win7 版冻结、只接受严重修复时,才考虑建立 `release/win7-legacy` 维护分支。
- **core 兼容基线**:core 保持 Go 1.20 语法和依赖兼容,使两个版本复用同一业务实现;现代版可用新 Gio 和现代 Windows 能力,但这些依赖不得反向进入 core。
- **存储**:现在选择 JSON + 原子写入,避免早期引入 SQLite 的复杂度;在出现历史记录、全文搜索、大量事务需求时再评估。
- **软件包**:现在只支持 ZIP;不在 v1 开放任意安装脚本(install.bat/PowerShell 钩子),未来如需运行库安装,走受审核的 prerequisites 声明。
- **授权**:不复用旧盒子的 MD5/SHA-1 隐藏常量注册码设计;采用服务端 Ed25519 签名许可证 + machine_hash 绑定。
- **Win7 x86**:默认不提供;仅在真实用户占比支撑时增加 win7-386 构建目标。
## 三、构建与运行命令
> 工程骨架由 T-001 建立;骨架完成前以下命令不可运行,`init.sh`/`init.ps1` 会主动失败提示。骨架完成后,T-001 必须把真实命令回填到本表、`00-ai-start-here.md`、`current-state.md` 和 init 脚本顶部三个变量。
| 用途 | 命令(计划) |
| --- | --- |
| 同步工作区依赖 | `go work sync` |
| core 测试 | `cd core && go test -count=1 ./...` |
| core 静态检查 | `cd core && go vet ./...` |
| 现代版构建 | `cd app-modern && GOOS=windows GOARCH=amd64 go build -o ../dist/SoftBox.exe ./cmd/softbox` |
| Win7 版构建 | `cd app-win7 && GOTOOLCHAIN=go1.20.14 GOOS=windows GOARCH=amd64 go build -o ../dist/SoftBox-win7.exe ./cmd/softbox` |
| 本地运行(开发) | Windows 下直接 `go run ./cmd/softbox`(WSL 中构建 EXE 后到 Windows 侧运行) |
工具链矩阵:
| 构建目标 | 工具链 | Gio | 支持系统 | 维护策略 |
| --- | --- | --- | --- | --- |
| modern-amd64 | Go 1.25(锁定) | v0.10.1 | Win10/Win11 x64 | 正常新增功能 |
| win7-amd64 | Go 1.20.14 | v0.6.0 | Win7 SP1 x64 及更高 | 兼容与严重修复 |
| win7-386 | Go 1.20.14 | v0.6.0 | Win7 SP1 x86 及更高 | 仅真实需求时提供 |
## 四、依赖纪律
- 新增第三方依赖前,先说明用途、替代方案和维护成本,并确认其 go.mod 的 `go` 指令不超过对应模块的工具链基线(core 与 app-win7 为 1.20)。
- core 禁止依赖 Gio、SQLite 驱动、Windows 专属包;平台能力一律接口注入。
- Gio 及 UI 相关依赖锁定版本并 vendor;升级 Gio 只允许在 app-modern,且需单独任务评估。
- 不确定的技术选型先更新本文,再进入代码。
- 不允许同一职责并存两套实现(如两套下载器、两套配置读写)。
+182
View File
@@ -0,0 +1,182 @@
# 架构设计
> 本文讲"怎么把技术栈搭起来":系统结构、职责划分、数据模型、技术难点、开发顺序。
> 具体用了哪些框架 / 库 / 工具链,见 [技术栈](03-tech-stack.md);清单、软件包、许可证等协议字段见 [协议合约](api.md)。
## 一、系统结构
```text
用户
|
v
SoftBox.exe(Gio 桌面客户端,现代版 / Win7 遗留版)
|
├─> core 业务核心(清单、下载、安装、更新、授权、存储)
| |
| ├─> HTTPS:签名 Catalog 清单 + ZIP 软件包(对象存储 / CDN)
| └─> 本地文件:SoftBoxData(apps / data / licenses / cache / downloads / staging / backups / logs)
|
├─> platform/windows(进程、版本、图标、机器指纹、托盘、目录选择)
|
└─> SoftBoxUpdater.exe(盒子自更新助手)
```
真实组件:
- 客户端 UI:Gio,入口 `app-modern/cmd/softbox` 与 `app-win7/cmd/softbox`。
- 业务核心:`core/`(独立 go.mod,Go 1.20 兼容,无 UI、无 Windows 强依赖)。
- 外部服务:仅静态的签名清单与软件包下载(HTTPS);无自建在线 API、无账号服务。
- 存储:JSON 文件 + 原子替换;无数据库。
## 二、分层与职责划分
```text
domain 纯模型、状态和规则
↓
application 用例、任务调度、事件总线
↓
infrastructure 网络、存储、清单、下载、安装、授权
↓
platform/windows 进程、版本、图标、机器信息、更新助手
↓
ui/gio Gio 控件、布局和交互
↓
cmd/softbox 依赖装配和程序入口
```
硬约束:
- domain 不依赖 Gio、SQLite 或 Windows API。
- application 只依赖接口(下载、存储、时间、进程、系统信息均注入)。
- infrastructure 不操作 UI。
- platform/windows 通过接口暴露能力,并提供非 Windows stub,保证 core 可在 CI 无头测试;仅 Win10 存在的 API 必须动态加载、失败降级,不进 EXE 导入表。
- ui/gio 只提交用例并消费事件;Layout 中不读磁盘、不访问网络、不算哈希。
- cmd 负责现代版和 Win7 版的具体装配。
UI 固定交互模式:
1. 每帧先 drain 点击事件 → 2. 向 application runtime 提交任务 → 3. 保存 RequestID 与软件 ID → 4. 后台 goroutine 执行 → 5. runtime 发布 application.Event → 6. UI 的 ApplyEvent 更新 ViewModel → 7. `Window.Invalidate` 请求重绘。
控件状态按**软件 ID**保存,不按列表序号;列表用惰性 `layout.List`;图标走内存 + 磁盘缓存。
## 三、仓库目录结构
```text
soft_quay/
├─ core/ # 共享业务核心(go.mod,Go 1.20 兼容)
│ ├─ domain/
│ ├─ application/
│ ├─ catalog/ # 清单获取、验签、缓存、过滤
│ ├─ downloader/ # 下载队列、断点续传、任务持久化
│ ├─ installer/ # ZIP 校验、安全解压、staging/backup/回滚
│ ├─ licensing/ # Ed25519 许可证验证、machine_hash
│ ├─ storage/ # JSON 原子读写、目录布局
│ └─ updater/ # 盒子自更新编排
├─ app-modern/ # 现代版(go.mod,Go 1.25 + Gio v0.10.1)
│ ├─ cmd/softbox/
│ ├─ ui/gio/
│ └─ platform/windows/
├─ app-win7/ # Win7 遗留版(go.mod,Go 1.20 + Gio v0.6.0)
│ ├─ cmd/softbox/
│ ├─ ui/gio/
│ └─ platform/windows/
├─ schemas/ # manifest / app.json / files.json / license JSON Schema
├─ scripts/ # 构建、打包、签名、校验脚本
├─ testdata/ # 测试用清单、ZIP、许可证样例(全部为假数据)
├─ docs/ # 本文档集
└─ go.work
```
依赖方向:`app-modern` → `core` ← `app-win7`,不得反向。
## 四、数据模型
### 4.1 远端只读数据(签名后消费)
| 数据 | 来源 | 说明 |
| --- | --- | --- |
| Catalog 清单 | `manifest-modern.json` / `manifest-win7.json`(HTTPS) | 软件列表、版本、packages(url/size/sha256)、Ed25519 签名;字段见 [api.md](api.md) |
| ZIP 软件包 | 清单中的 `packages[arch].url` | 内含 `app.json` + `files.json` + `payload/`;协议见 [api.md](api.md) |
| 撤销名单 | 签名的 revocation 列表 | 网络失败时使用缓存并保留宽限期 |
关键事实:
- 软件主键是永久稳定的 `id`(小写英文/数字/短横线),不用名称;下架用 `status`,不用名称前缀。
- 清单验签失败时**拒绝**,回退到最后一次验证成功的缓存,绝不接受未验证的新内容。
- channel 分 `modern` / `win7`,更新器必须校验 channel + min_os,禁止交叉升级。
### 4.2 本地动态数据(JSON + 原子写入)
安装模式根目录 `%LOCALAPPDATA%/OwnSoftBox/`(便携模式为程序目录 `.softbox/`,V2):
```text
├─ app/ # 盒子自身程序
├─ apps/<id>/ # current/ staging/ backup/ installed-app.json
├─ data/<id>/ # 子软件用户数据(更新永不覆盖)
├─ licenses/ # 许可证(更新永不覆盖)
├─ cache/ # 清单缓存、图标缓存
├─ downloads/ # 下载临时文件 + 任务元数据
├─ staging/ # 盒子自更新暂存
├─ backups/ # 盒子自更新备份
└─ logs/
```
软件本地状态机(domain 层枚举):
`not_installed → queued → downloading → verifying → extracting → installing → installed`,
另有 `update_available`、`running`、`failed`、`rollback_pending`、`incompatible`。
每次状态写入使用临时文件 + 原子替换;崩溃后可识别 staging、backup 和未完成事务。
### 4.3 事件模型
后台任务只发布事件(`DownloadStarted / DownloadProgress / DownloadPaused / DownloadCompleted / DownloadFailed` 等),UI 按 RequestID 和软件 ID 回填,见 [api.md](api.md) 事件合约。
## 五、关键安全流程
安装/更新一款软件的强制顺序:
```text
读取已签名 Catalog → 选择 OS/架构匹配的 Package → 下载到 downloads
→ 校验 size 与 SHA-256 → 安全读取 app.json → 比对 ID/版本/通道/系统/架构
→ 检查 ZIP 路径与解压上限 → 解压 payload 到 staging → 校验 entry_exe
→ 确认目标软件已退出 → current 改名 backup → staging 原子切换为 current
→ 健康检查 → 成功延迟清理 backup / 失败恢复 backup
```
必须防止:绝对路径、`../` 穿越、符号链接逃逸、写入其他软件目录、覆盖 data 与 licenses、运行中强替换 EXE、未验证包被执行、解压数量/体积/压缩比无上限、包内自动执行脚本。
盒子自更新由独立 `SoftBoxUpdater.exe` 完成(传入 PID、暂存目录、目标目录;等待退出→备份→切换→启动新版→失败恢复)。
授权:平台层采集多个稳定硬件标识 → 清洗生成 machine_hash(不保存原始序列号/MAC)→ 服务端 Ed25519 私钥签发许可证 → 客户端内置公钥离线验签;许可证与程序文件、用户配置分开保存;子软件必须独立再次验证,不能只信盒子。
## 六、关键技术难点
| 难点 | 说明 | 应对 |
| --- | --- | --- |
| 清单签名与缓存回退 | 验签、缓存、拒绝逻辑错了等于远程代码执行 | Phase 1 先做最小原型 + 恶意样例测试(testdata 伪造清单) |
| ZIP 安全解压 | 路径穿越/符号链接/zip bomb | 独立模块 + 攻击样例表驱动测试 |
| 原子切换与回滚 | Windows 文件锁、断电恢复 | staging/backup 状态机 + 崩溃恢复测试;更新前确认进程退出 |
| 双 Gio 版本 API 差异 | v0.6.0 与 v0.10.1 控件 API 不同 | 共享 ViewModel 与交互语义,Gio 适配层各自实现 |
| Win7 API 兼容 | 新 API 进导入表则 Win7 无法启动 | 动态加载 + 降级;Toolhelp32/GetFileVersionInfo 等 Win7 已有 API 优先 |
| 断点续传与任务恢复 | 重启后恢复未完成任务 | 任务元数据持久化 + HTTP Range;超时指数退避 |
| 机器指纹稳定性 | 硬件变动导致正版误判 | 多标识加权 + 容错 + 换绑申诉流程 |
高风险模块先做最小原型(Phase 1),不要等整个系统搭完才验证。
## 七、推荐开发顺序
1. 工程骨架:core / app-modern / app-win7 + go.work + 空 Gio 窗口 + 平台 stub + CI 双目标编译。
2. 最高风险原型:签名清单验签与缓存回退、ZIP 安全解压、staging/backup 原子切换。
3. 核心用户流程:清单 → 列表/搜索 → 下载队列 → 安装 → 启动。
4. 更新与自更新:子软件更新、SoftBoxUpdater、健康检查与回滚。
5. 授权:机器指纹、许可证验证、试用/换绑/导入。
6. Win7 加固与发布:API 降级、真机矩阵、双通道发布流水线。
## 八、架构纪律
- 业务事实和协议变化必须同步更新本文与 [api.md](api.md)。
- 不在代码里发明文档没有的接口、字段和状态。
- 清单缓存、用户数据、许可证、下载临时文件分目录存放,不混在一个模型里。
- 高风险模块先单独验证,再接入完整页面或流程。
- core 的 Go 1.20 兼容性由 CI 用 Go 1.20 工具链编译检查,不靠口头约定。
+85
View File
@@ -0,0 +1,85 @@
# 编码规则(Coding Rules)
> 每次写代码前先读完本文件。这是让 AI 不跑偏、代码质量稳定的硬约束。
> 与技术细节冲突时,以 [技术栈](03-tech-stack.md) / [架构设计](04-architecture.md) 的事实为准;与"该不该做"冲突时,以 [需求](02-requirements.md) 为准;协议字段以 [api.md](api.md) 为准。
## 0. 黄金法则
1. **不臆造**:数据字段、文件、接口、依赖,不确定就查证或询问。
2. **守范围**:只做当前任务要求的事,不顺手加后续功能。
3. **照架构**:使用既定技术栈和模块边界,不擅自引入新框架。
4. **小步改**:一次只解决一个问题,不夹带无关重构。
5. **可验证**:改完必须能构建、能测试、对得上验收标准。
## 1. 动手前
- 按链路确认:`vision` → `requirements` → `tech-stack` → `architecture` → `tasks`。
- 找到本任务对应的验收标准,写之前就知道"怎么算做对"。
- 先找现有函数、组件、工具和测试,复用优先。
- 如果需求含糊,或改动会偏离原则 / 架构,先问。
## 2. 分层与工具链纪律(本项目最容易翻车的地方)
- `core/` 的 domain 与 application **禁止 import Gio、SQLite、Windows API**;下载、存储、时间、进程、系统信息一律接口注入。
- 依赖方向只允许 `app-modern`/`app-win7` → `core`;任何反向 import 都是返工。
- `core/` 与 `app-win7/` 只使用 **Go 1.20 可编译**的语法与依赖;新增依赖前检查其 go.mod 的 `go` 指令。泛型可用(1.18+),但 1.21+ 的标准库函数(如 `slices`、`maps`、`min/max` 内建)不得进入这两个模块。
- Gio 代码只出现在 `ui/gio/`;Windows 调用只出现在 `platform/windows/`,且必须有非 Windows stub,保证 `go test ./...` 在 Linux CI 可跑。
- 仅 Win10+ 存在的 Windows API 必须 LoadLibrary 动态加载、失败降级,不得成为 EXE 导入表强依赖。
- Gio Layout 每帧禁止 IO(磁盘/网络/哈希);后台任务只发布 application.Event,不直接改控件;控件状态按软件 ID 保存。
## 3. 安全纪律(违反即安全事故)
- 任何下载内容未通过 SHA-256 + 签名验证,**不得解压执行**;清单验签失败拒绝,不回退到未验证内容。
- ZIP 处理必须拒绝:绝对路径、`../` 穿越、符号链接逃逸、entrypoint 指向 payload 外、无上限解压(文件数/体积/压缩比)。
- 安装/更新只走 `staging → current → backup` 原子流程;任何写 `current/` 的捷径都不允许。
- 程序更新不得触碰 `data/` 与 `licenses/`。
- 不强杀用户进程;更新前等待正常退出,超时取消。
- 私钥、真实注册码、真实机器标识不进代码、测试数据和文档;`testdata/` 只放假数据和专用测试密钥对。
- 日志不记录注册码、令牌、原始硬件标识和敏感查询参数。
## 4. 事实来源纪律
- 协议字段只信 `docs/api.md` 与 `schemas/`;架构边界只信 `docs/04-architecture.md`;当前现实只信 `docs/current-state.md` 和代码。
- 不从旧盒子的逆向笔记、备份或草稿推断当前实现;那些是设计输入,不是本仓库事实。
- 不虚构字段、事件、错误码、配置项。
- 数据结构变化必须同步更新 `04-architecture.md`、`api.md`、`schemas/` 和相关任务。
## 5. 范围纪律
- MVP 只做 `02-requirements.md` 中列为 P0 的功能;V1.1/V2 功能只记录,不实现。
- 需求明确排除的非目标(社区、支付、云同步、插件、驱动)不得实现。
- 不为"将来可能用到"提前抽象;SDK 拆分(softbox-sdk)是后续独立仓库的事,本仓库不预建。
## 6. 代码规范
- 标识符使用英文;错误码用稳定英文枚举,UI 负责中文文案。
- 错误必须处理,不吞错;每个失败路径要能落到用户可见的状态或日志。
- 注释解释"为什么",不复述"做了什么"。
- 使用 `gofmt`(必须)与 `go vet`(必须);不手工制造风格分裂。
- 状态写入一律临时文件 + 原子替换。
## 7. 测试与验证
完成前至少检查:
- [ ] `cd core && go vet ./... && go test -count=1 ./...` 通过(Linux 无头环境)。
- [ ] 现代版可交叉编译:`cd app-modern && GOOS=windows GOARCH=amd64 go build ./cmd/softbox`。
- [ ] Win7 版可用 Go 1.20 编译:`cd app-win7 && GOTOOLCHAIN=go1.20.14 GOOS=windows GOARCH=amd64 go build ./cmd/softbox`。
- [ ] 状态机改动覆盖:成功、失败、取消、断电恢复、回滚。
- [ ] UI 事件改动使用 fake submitter + ApplyEvent 测试。
- [ ] 对得上需求验收标准;没有夹带无关改动。
- [ ] 涉及文档事实变化时,文档已同步。
- [ ] 已在当前任务文件(`docs/tasks/T-<编号>.md`)的 `## 执行记录` 记录跑过的命令和结果作为证据,不靠"代码已写"判定完成。
- [ ] 回复里如实说明跑了什么命令、结果如何。
## 8. 绝不
- 绝不把密钥、token、密码、私钥写进代码或文档样例的真实值里。
- 绝不为了让测试通过而删除断言、降低验收标准。
- 绝不擅自删除用户已有文件或重置工作区。
- 绝不在没说明的情况下改公共协议(manifest/app.json/许可证)、迁移数据结构或升级 Gio/Go 工具链。
- 绝不绕过签名验证、哈希校验或回滚机制"先跑起来再说"。
## 9. 拿不准就问
问题要具体,说明你卡在哪里、有哪些选项、倾向哪个选项以及原因。
+95
View File
@@ -0,0 +1,95 @@
# 任务路线图(Roadmap)
> 本文是**只读路线图**:维护阶段划分、里程碑、待办池和建议拆分清单,把 MVP 拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。
> 真实任务以「一任务一文件」存放在 [`tasks/`](tasks/README.md)(`docs/tasks/T-<编号>.md`)。未启用 Gitea 时以任务文件 frontmatter 为状态权威;启用后以 Issue 为实时状态、合并后的任务文件为长期事实。**本文不跟踪单任务状态**。
## 使用规则
1. **开工先落文件**:从下方「建议拆分清单」把下一个任务按 [`tasks/README.md`](tasks/README.md) 落成 `docs/tasks/T-<编号>.md`(沿用建议编号),把验收要点展开成可执行、可观察的步骤,再开始实现。
2. **每个 agent 一次只做一个任务**:领取、状态流转、执行记录、完成定义全部遵循 [`tasks/README.md`](tasks/README.md) 和 [编码规则](05-coding-rules.md);`DONE` 需要可运行证据,多 agent 只并行写路径互不重叠的任务。
3. **不跳步**:依赖未完成的任务不能开工。
4. **本文只在规划变化时修改**:调整阶段划分、里程碑、增删建议任务或 Backlog 条目时才动本文;单个任务开工或完成**不**修改本文。
5. **动手前**先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。
## 建议拆分清单
以下是按阶段列出的建议任务;`T-编号` 为建议编号,落成任务文件时沿用。
### Phase 0 · 工程骨架
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-001 | 初始化 monorepo 骨架 | - | `go.work` + core/app-modern/app-win7 三模块;core `go test ./...` 在 Linux 通过;两个 EXE 可交叉编译(modern 用锁定 Go,win7 强制 Go 1.20);用真实命令替换 `init.sh`/`init.ps1` 顶部三个变量,并同步 `00-ai-start-here.md`、`03-tech-stack.md`、`05-coding-rules.md`、`current-state.md` |
| T-002 | 建立 domain 状态模型与事件总线 | T-001 | 软件状态机枚举与迁移规则、application.Event 定义与 runtime 骨架;状态机表驱动测试覆盖非法迁移 |
| T-003 | Gio 空窗口 + 平台层 stub | T-001 | 两个 app 打开空主窗口(AppShell 骨架);platform/windows 接口 + 非 Windows stub;core 不 import Gio 的检查脚本 |
| T-004 | CI 双目标编译闸门 | T-001 | CI(或本地脚本)执行:core vet+test、modern 构建、win7 用 Go 1.20 构建、依赖 go 版本检查 |
### Phase 1 · 最高风险原型
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-101 | 签名清单验签与缓存回退原型 | T-002 | Ed25519 验签;伪造/篡改清单被拒绝(testdata 恶意样例);断网回退最后验证成功缓存;结论写入文档 |
| T-102 | ZIP 安全解压原型 | T-002 | 表驱动攻击样例全部被拒:绝对路径、`../`、符号链接、zip bomb(数量/体积/压缩比上限)、entrypoint 外逃 |
| T-103 | staging/backup 原子切换与回滚原型 | T-102 | 切换成功、切换中断(模拟崩溃)、回滚三条路径测试通过;状态可从磁盘恢复 |
### Phase 2 · 清单与软件列表
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-201 | catalog 模块正式接入 | T-101 | 清单获取、验签、缓存、min_os/架构过滤、status 处理;app.json/manifest Schema 落入 `schemas/` |
| T-202 | 本地安装状态识别 | T-201, T-103 | installed-app.json 读写;12 种状态正确呈现;版本比较用 SemVer |
| T-203 | 主界面软件列表 | T-202, T-003 | 虚拟列表 + 搜索/分类/视图切换;控件状态按软件 ID;数百项滚动流畅;Layout 无 IO |
| T-204 | 软件详情与图标缓存 | T-203 | 详情弹层;图标内存+磁盘缓存(按 DPI);断网可显示缓存图标 |
### Phase 3 · 下载与安装
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-301 | 下载队列 | T-202 | 并发默认 2;暂停/取消/重试;Range 续传;任务元数据持久化,重启恢复;事件驱动进度 |
| T-302 | 安装流程整合 | T-301, T-102, T-103 | 完整链:下载→SHA-256→app.json 比对→安全解压→staging→切换→健康检查→回滚;`04-architecture.md` 关键安全流程顺序逐步可观察 |
| T-303 | 失败处理与磁盘预检查 | T-302 | 哈希不符、磁盘不足、包损坏、程序占用各有确定结果与错误码;不破坏旧版本 |
### Phase 4 · 启动、运行与更新
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-401 | 进程检测与软件启动 | T-302 | Toolhelp 快照检测运行状态;启动前检查文件/兼容/占用;WorkingDirectory 正确 |
| T-402 | 子软件更新流程 | T-401 | 可更新识别→确认退出→更新→失败回滚仍可启动旧版;更新不触碰 data/licenses |
| T-403 | SoftBoxUpdater 盒子自更新 | T-402 | 独立 Updater 按 CLI 合约工作;更新中断可恢复;新版健康状态写入 |
### Phase 5 · 授权
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-501 | 机器指纹(machine_hash) | T-001 | 平台层采集多标识→清洗生成 hash;不落原始序列号/MAC;非 Windows stub 可测 |
| T-502 | Ed25519 许可证验证 | T-501 | licensing 模块离线验签;复制到不匹配机器被拒;测试用专用密钥对 |
| T-503 | 授权界面与试用/导入/换绑 | T-502, T-204 | 导入、授权列表、试用状态展示;撤销名单验签与宽限期 |
### Phase 6 · Win7 加固与发布
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-601 | Win7 API 降级与真机冒烟 | T-403 | 动态加载检查(导入表无 Win10+ API);Win7 SP1 真机/VM 完成清单→下载→安装→启动 |
| T-602 | Win7 兼容矩阵验证 | T-601 | DPI(100/125/150)、中文 IME、经典主题/Aero、远程桌面、非管理员、旧证书按矩阵记录结果 |
| T-603 | 发布流水线 | T-403, T-503 | 构建→注入版本→签名→打包 ZIP→哈希→生成签名 manifest;双通道互不交叉;干净机器完成下载/更新/回滚/授权测试 |
## 里程碑
- M1:工程骨架完成,双目标可编译,core 无头可测(Phase 0)。
- M2:三大高风险原型(验签、解压、原子切换)全部验证(Phase 1)。
- M3:「清单→列表→下载→安装→启动」核心闭环跑通(Phase 2-4 前半)。
- M4:自更新 + 授权完成,MVP 验收(Phase 4-5)。
- M5:Win7 遗留版可交付,双通道发布(Phase 6)。
## 待办池(Backlog)
- files.json 复核与修复功能(V1.1)。
- 命名管道 prepare_update/ready 优雅退出协议(V1.1)。
- --softbox-info / --softbox-health 健康检查接入(V1.1)。
- Win7 x86(386)构建目标(按真实用户占比)。
- 便携模式(portable.flag + `.softbox/` 相对目录)。
- beta 通道支持。
- 托盘图标与最小化到托盘。
- 拼音搜索。
- SBOM 生成与发布归档。
- softbox-sdk 拆分(独立仓库,待协议稳定后)。
+50
View File
@@ -0,0 +1,50 @@
# 项目文档导航
> SoftBox 软件盒子的 harness coding 文档集。agent 开始编程时以 [`00-ai-start-here.md`](00-ai-start-here.md) 为工作入口。
## 一句话定位
SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端,为自家软件产品家族提供「发现 → 下载 → 安装 → 更新 → 启动 → 授权」的一站式安全闭环;第一版先跑通签名清单 → 下载安装 → 启动更新 → 机器绑定授权的 MVP,并提供现代版与 Win7 遗留版双构建。
## 文档导航
- [`../AGENTS.md`](../AGENTS.md):AI coding agent 的仓库级入口(含硬性架构边界)。
- [`../CLAUDE.md`](../CLAUDE.md):Claude Code 的薄入口,具体规则以 `AGENTS.md` 为准。
- [`../progress.md`](../progress.md):可选历史归档 / 项目级大事记;执行记录默认写各任务文件的 `## 执行记录`。
- [AI 开发入口](00-ai-start-here.md):agent 每次开始工作的入口、阅读顺序和任务领取规则。
- [项目愿景](01-vision.md):为什么做、为谁做、产品原则、非目标。
- [需求](02-requirements.md):要什么、用户故事、验收标准,不写技术实现。
- [技术栈](03-tech-stack.md):Go + Gio 双工具链矩阵、构建命令、依赖纪律。
- [架构设计](04-architecture.md):分层结构、monorepo 目录、数据模型、安全流程和开发顺序。
- [编码规则](05-coding-rules.md):AI 写代码前必须遵守的硬约束(分层、工具链、安全纪律)。
- [任务路线图](06-tasks.md):Phase 0-6 阶段划分、里程碑和待办池;只读,不跟踪单任务状态。
- [任务文件(默认)](tasks/README.md):一任务一文件 `docs/tasks/T-<编号>.md`,单/多 agent 通用,每个 agent 同时只做一个。
- [已有项目接入清单](adoption-checklist.md):把本模板补进已有代码库时的迁移步骤(本项目从零开始,备查)。
- [协议合约](api.md):Catalog 清单、标准软件包协议 v1、许可证、事件合约、CLI 参数。
- [页面与视图结构](routes.md):Gio 视图划分、组件归属、交互硬约束。
- [当前实现状态](current-state.md):可覆盖的当前快照,记录仓库现实状态、可运行命令和下一步可做任务。
- [Agent 上下文清单](agent-context.md) / [`agent-context.json`](agent-context.json) / [`Schema`](agent-context.schema.json):按任务类型选择文档、用提交 / 文件 SHA 避免重复读取。
- [Gitea MCP 接入](gitea-mcp.md):可选的共享文档、Issue / PR 协调、安全配置和断连降级规则(当前未启用)。
- [Gitea 多 Agent 协作](gitea-collaboration.md):可选的任务映射、串行分配、防重复 claim 和 PR 状态协议(当前未启用)。
- [收尾检查清单](clean-state-checklist.md):会话结束前逐项检查,保证下一轮无需人工修复即可继续。
- [方法对照表](method-map.md):失败模式 → 首要修复 → 工件;出问题先查这里对症补工件。
- [评审评分表](evaluator-rubric.md):单次会话输出的结构化评审。
- [质量文档](quality-document.md):代码库长期健康度追踪。
- [`../init.sh`](../init.sh) / [`../init.ps1`](../init.ps1):标准启动与验证入口脚本(根目录),统一安装、验证和启动命令。按操作系统二选一;T-001 完成前顶部三个命令为占位符,运行会主动失败。
- [`../scripts/validate_agent_context.py`](../scripts/validate_agent_context.py):零第三方依赖校验上下文清单、Schema 和仓库相对路径。
- [`../scripts/validate_harness_governance.py`](../scripts/validate_harness_governance.py):离线检查导航、链接、任务、模板和敏感信息。
- [`../tests/test_governance.py`](../tests/test_governance.py):标准库治理回归测试。
## 任务 / 进度 / 当前状态
- `tasks/`(`docs/tasks/T-<编号>.md`)维护任务:规格、依赖、状态(frontmatter)和执行记录,一任务一文件。
- `06-tasks.md` 维护路线图:阶段划分、里程碑和待办池,不跟踪单任务状态。
- `current-state.md` 维护当前快照:当前目录、当前可运行命令、任务摘要和下一个可领取任务。
- `../progress.md` 可选:历史归档或项目级大事记,不逐任务追加。
## 维护原则
- 需求变化先改文档,再改代码。
- 代码现实变化后同步 `current-state.md`;任务长期状态和执行证据写进对应任务文件。
- 协议(manifest / app.json / 许可证)、数据模型、技术栈一旦在文档中定稿,代码不得另起一套。
- agent 开始新任务前,必须从 `00-ai-start-here.md` 进入。
+70
View File
@@ -0,0 +1,70 @@
# 已有项目接入清单
> 用于把本模板补进一个已经存在的项目。目标是先建立 agent 可读的事实来源,再继续开发新功能。
## 适用场景
- 项目已经有代码,但缺少清晰的 agent 入口、任务文件、当前状态和验证路径。
- 项目被多轮 AI 修改过,文档、代码和真实可运行状态已经不一致。
- 想从“靠聊天记录推进”切换到“靠仓库内工件推进”。
如果是全新空项目,优先按根目录 `README.md` 的“空项目接入”流程复制模板。
## 最小接入文件
先复制或建立这些文件:
| 文件 | 作用 |
| --- | --- |
| `AGENTS.md` | 仓库级 agent 入口和总规则 |
| `CLAUDE.md` | Claude Code 薄入口,指向 `AGENTS.md` |
| `docs/00-ai-start-here.md` | 每轮开工流程 |
| `docs/agent-context.json` | 按任务类型选择本轮上下文 |
| `docs/agent-context.schema.json` | 上下文清单结构契约 |
| `docs/agent-context.md` | 清单读取、缓存和断连降级规则 |
| `docs/05-coding-rules.md` | 编码纪律和验证底线 |
| `docs/06-tasks.md` | 任务路线图(阶段、里程碑、待办池) |
| `docs/tasks/` | 默认任务管理:一任务一文件(`README.md` + `_template.md`) |
| `docs/current-state.md` | 当前实现状态快照 |
| `init.sh` 或 `init.ps1` | 标准启动与验证入口,按操作系统二选一 |
| `scripts/validate_agent_context.py` | 零第三方依赖校验清单和引用路径 |
推荐随后补齐:`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`、`scripts/audit_gitea_coordination.py`、离线治理脚本和测试;先只读预览远端标签差异,再由维护者显式 `--apply`。Actions 工作流仅在仓库已启用 Actions 且 runner 可用时生效。
## 接入步骤
1. 确认仓库根目录,读取已有 `README`、运行脚本、测试配置和主要入口文件。
2. 复制最小接入文件,把所有 `【占位符】` 替换成当前项目事实,并按项目任务类型调整 `agent-context.json` 的路由。
3. 在 `docs/current-state.md` 写清真实目录、真实启动命令、真实验证命令、当前 blocker。
4. 在 `docs/03-tech-stack.md` 固定已经实际使用的技术栈,不确定项标为“待定”,不要让 agent 自行选择。
5. 在 `docs/04-architecture.md` 记录当前代码的真实模块边界;不清楚的地方标为“待确认”。
6. 在 `docs/06-tasks.md` 只放下一阶段能小步交付的建议任务,不要把历史愿望清单全部搬进去;第一轮任务按 `docs/tasks/README.md` 落成 `docs/tasks/T-<编号>.md`。
7. 配置 `init.sh` 或 `init.ps1` 顶部三个命令,让它能安装依赖、运行基础验证、打印启动命令。
8. 运行 `python scripts/validate_agent_context.py` 和项目标准验证;如果失败,第一轮任务应先修基线,不做新功能。
9. 把接入过程、验证结果写进第一轮任务文件的 `## 执行记录`,遗留 blocker 同步到 `docs/current-state.md`。
## 第一轮 agent 任务建议
已有项目接入后的第一轮,不建议直接做新功能。推荐任务是:
| ID | 任务 | 验收要点 |
| --- | --- | --- |
| T-000 | 建立当前状态基线 | `current-state.md` 写清真实状态;`init` 脚本已配置;基础验证结果已记录到本任务文件的 `## 执行记录` |
| T-001 | 修复启动 / 验证基线 | 标准启动路径和标准验证路径可运行;失败原因已消除或记录为 blocker |
| T-002 | 对齐任务路线图 | `06-tasks.md` 只保留可小步交付的建议任务;第一个待落地任务依赖清楚、验收可观察 |
## 代码现实与文档冲突时
- 以当前可运行代码和真实验证结果为事实起点。
- 文档描述旧功能但代码不存在时,先把差异记录到 `current-state.md`,不要直接补实现。
- 代码已有行为但文档没写时,先补 `02-requirements.md`、`04-architecture.md` 或 `api.md`,再继续修改代码。
- 命令不可运行时,不要标记任务完成;在当前任务文件的 `## 执行记录` 记录失败命令和错误摘要。
## 不建议做的事
- 不要一次性把所有模板都填满;先让入口、当前状态、任务和验证路径可用。
- 不要把聊天记录当事实来源。
- 不要为了让验证通过而降低测试或验收标准。
- 不要在接入 harness 的同一轮顺手重构业务代码,除非是修复启动或验证基线所必需。
+67
View File
@@ -0,0 +1,67 @@
{
"schema": "docs/agent-context.schema.json",
"schema_version": 1,
"authority": {
"bootstrap": "local_checkout",
"framework_templates": "current_repository",
"project_facts": "current_project_repository",
"coordination": "gitea_issues_and_pull_requests"
},
"bootstrap": {
"always_read": [
"AGENTS.md",
"docs/00-ai-start-here.md",
"docs/05-coding-rules.md",
"docs/current-state.md"
]
},
"routes": {
"documentation": [
"README.md",
"docs/README.md",
"docs/01-vision.md",
"docs/02-requirements.md"
],
"ui": [
"docs/02-requirements.md",
"docs/routes.md",
"docs/04-architecture.md"
],
"api": [
"docs/api.md",
"docs/04-architecture.md",
"docs/05-coding-rules.md"
],
"data": [
"docs/02-requirements.md",
"docs/04-architecture.md",
"docs/api.md"
],
"deploy": [
"docs/03-tech-stack.md",
"docs/current-state.md"
],
"gitea": [
"docs/gitea-mcp.md",
"docs/gitea-collaboration.md",
"docs/tasks/README.md",
"docs/clean-state-checklist.md"
]
},
"tasks": {
"roadmap": "docs/06-tasks.md",
"directory": "docs/tasks/",
"template": "docs/tasks/_template.md"
},
"refresh": {
"context_ref": "default_branch_head_sha",
"cache_key": "file_sha",
"unchanged_file": "reuse_within_current_session",
"changed_ref": "reread_manifest_and_routed_documents"
},
"degraded_mode": {
"continue_claimed_task": true,
"claim_new_task": false,
"write_remote_state": false
}
}
+70
View File
@@ -0,0 +1,70 @@
# Agent 上下文清单
> [`agent-context.json`](agent-context.json) 是机器可读的文档路由,[`agent-context.schema.json`](agent-context.schema.json) 定义结构契约;本文解释 agent 应如何使用它。清单只保存路径和刷新规则,不复制文档正文。
## 解决什么问题
项目文档仍存放在项目 Git 仓库的 `docs/` 中。本地 checkout 与 Gitea 远端是同一批 Git 工件,不是两套人工同步的文档。
上下文清单解决的是“本轮该读什么”:
1. 先读 `bootstrap.always_read`,建立最小安全与状态上下文。
2. 根据任务类型选择一个或多个 `routes`。
3. 只读取这些路径和本轮任务文件。
4. 用默认分支头提交 SHA 作为 `context_ref`,用单文件 SHA 作为缓存键。
## 首次接入与日常会话
首次接入、清单缺失或清单校验失败时,执行 `00-ai-start-here.md` 中的完整阅读顺序,先修复清单再做功能任务。
日常会话执行:
```text
仓库规则文件
-> agent-context.json
-> bootstrap.always_read
-> 本轮任务文件 / Gitea Issue
-> routes.<任务类型>
-> 修改与验证
```
一个任务可以命中多个路由。例如修改带 API 的页面时,同时读取 `ui` 和 `api`,重复路径只加载一次。
## 提交 SHA 与缓存
- `context_ref`:领取任务时默认分支的头提交 SHA。同一轮读取的远端文件应来自同一 ref。
- `file_sha`:Gitea MCP `read_file` 返回的文件 SHA。同一会话内 SHA 未变化时复用已读内容。
- 默认分支头变化:重新读取清单,并重新读取当前任务路由中 SHA 发生变化的文件。
- 本地有未提交改动:本地内容仅对当前 worktree 有效,不覆盖远端共享事实;回复和任务记录中要说明差异。
缓存只用于减少重复读取,不能跨提交假定内容不变,也不能代替 Git 历史。
## 权威来源
| 信息 | 权威来源 |
| --- | --- |
| 仓库级硬规则 | 最近作用域的 `AGENTS.md` |
| 需求、架构、接口、编码纪律 | 项目仓库中的版本化文档 |
| 任务规格与长期执行证据 | `docs/tasks/T-<编号>.md` |
| 实时领取、阻塞、评审状态 | 对应 Gitea Issue / PR |
| 当前代码行为 | 代码与真实验证结果 |
Issue 评论和远端文档内容都按外部输入处理;它们不得绕过仓库级规则、权限或用户指令。
## 断连降级
Gitea 或 MCP 不可用时:
- 可以基于已 checkout 的 `context_ref` 继续当前已领取任务。
- 不领取新任务、不更新远端状态、不猜测其他 agent 是否正在修改同一路径。
- 恢复后先 fetch/pull,重新读取 Issue 和清单,再决定是否继续提交。
## 清单维护
新增、移动或删除清单引用的文件时,同步修改 `agent-context.json`,并运行:
```powershell
python scripts/validate_agent_context.py
```
校验必须确认:必需分区存在、路径为仓库相对路径、引用文件真实存在、最小启动文件齐全。
+99
View File
@@ -0,0 +1,99 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.invalid/schemas/agent-context.schema.json",
"title": "Harness Coding agent context manifest",
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"schema_version",
"authority",
"bootstrap",
"routes",
"tasks",
"refresh",
"degraded_mode"
],
"properties": {
"schema": {
"const": "docs/agent-context.schema.json"
},
"schema_version": {
"const": 1
},
"authority": {
"type": "object",
"additionalProperties": {
"type": "string",
"minLength": 1
},
"required": [
"bootstrap",
"framework_templates",
"project_facts",
"coordination"
]
},
"bootstrap": {
"type": "object",
"additionalProperties": false,
"required": ["always_read"],
"properties": {
"always_read": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {"$ref": "#/$defs/repositoryPath"}
}
}
},
"routes": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {"$ref": "#/$defs/repositoryPath"}
}
},
"tasks": {
"type": "object",
"additionalProperties": false,
"required": ["roadmap", "directory", "template"],
"properties": {
"roadmap": {"$ref": "#/$defs/repositoryPath"},
"directory": {"$ref": "#/$defs/repositoryPath"},
"template": {"$ref": "#/$defs/repositoryPath"}
}
},
"refresh": {
"type": "object",
"additionalProperties": false,
"required": ["context_ref", "cache_key", "unchanged_file", "changed_ref"],
"properties": {
"context_ref": {"const": "default_branch_head_sha"},
"cache_key": {"const": "file_sha"},
"unchanged_file": {"const": "reuse_within_current_session"},
"changed_ref": {"const": "reread_manifest_and_routed_documents"}
}
},
"degraded_mode": {
"type": "object",
"additionalProperties": false,
"required": ["continue_claimed_task", "claim_new_task", "write_remote_state"],
"properties": {
"continue_claimed_task": {"type": "boolean"},
"claim_new_task": {"type": "boolean"},
"write_remote_state": {"type": "boolean"}
}
}
},
"$defs": {
"repositoryPath": {
"type": "string",
"minLength": 1,
"pattern": "^(?!/)(?!.*\\\\)(?!.*(^|/)\\.\\.(/|$))(?![A-Za-z][A-Za-z0-9+.-]*:).+$"
}
}
}
+197
View File
@@ -0,0 +1,197 @@
# 协议合约(Catalog / 软件包 / 许可证 / 事件 / CLI)
> 本项目没有自建在线 API;盒子只消费静态签名文件。本文定义所有跨边界数据结构和交互合约的目标形状。
> 实现前可细化,但不要在代码里另起一套不兼容结构。字段变化必须同步更新本文、[04-architecture.md](04-architecture.md) 和 `schemas/` 下的 JSON Schema。
## 通用约定
- 传输:HTTPS;所有 JSON 使用 UTF-8。
- 时间格式:ISO 8601(UTC)。
- 签名:Ed25519;客户端只内置公钥;示例中的 `"signature": "..."` 均为占位符。
- 版本号:语义化版本(SemVer)。
- 软件 ID:`^[a-z0-9-]+$`,永久稳定,发布后不得更改。
## 1. Catalog 清单(远端 → 盒子)
现代版与 Win7 版使用不同 channel 文件:`manifest-modern.json` / `manifest-win7.json`。更新器必须校验 channel,禁止 Win7 版下载现代版包。
```json
{
"schema_version": 1,
"channel": "modern",
"generated_at": "2026-07-16T00:00:00Z",
"min_box_version": "1.0.0",
"apps": [
{
"id": "json-parser",
"name": "JSON解析工具",
"description": "示例简介",
"version": "1.2.0",
"channel": "stable",
"status": "active",
"tags": ["工具", "JSON"],
"icon": "sha256:...",
"homepage": "https://example.com",
"tutorial": "https://example.com/tutorial",
"min_os": "windows-7-sp1",
"architectures": ["386", "amd64"],
"entry_exe": "JsonParser.exe",
"requires_admin": false,
"packages": {
"amd64": {
"url": "https://download.example.com/json-parser-1.2.0-amd64.zip",
"size": 12345678,
"sha256": "...",
"signature": "..."
}
}
}
],
"signature": "..."
}
```
行为要求:
- 网络成功:验签通过才替换本地缓存;验签失败**拒绝**,继续用最后一次验证成功的缓存。
- 网络失败:用缓存;清单过期给提示,但保留已安装软件的启动能力。
- 下架:显式 `status: deprecated | hidden`,不用名称前缀。
- 过滤:按 `min_os` 与 `architectures` 过滤;不兼容软件可见说明但不可下载。
## 2. 标准软件包协议 v1(ZIP)
```text
json-parser_1.4.2_windows_amd64.zip
├─ app.json # 身份、版本、入口、兼容性
├─ files.json # 解压后文件清单(v1.1 强制,v1 推荐)
└─ payload/ # 实际安装到 current/ 的程序文件
```
### 2.1 app.json
```json
{
"schema_version": 1,
"id": "json-parser",
"name": "JSON解析工具",
"vendor": "MyCompany",
"version": "1.4.2",
"channel": "stable",
"min_os": "windows-7-sp1",
"architecture": "amd64",
"entrypoint": "JsonParser.exe",
"working_directory": ".",
"product_id": "product-json-parser",
"supports_trial": true,
"requires_admin": false,
"data_policy": "local-app-data",
"update_policy": "managed-by-softbox"
}
```
校验规则:`entrypoint`/`working_directory` 必须是 payload 内安全相对路径;`id`、`version`、`channel`、`architecture` 必须与 Catalog 记录一致;`schema_version` 高于盒子支持范围时拒绝安装并提示升级盒子(盒子始终支持当前与前一个 Schema)。
### 2.2 files.json
```json
{
"schema_version": 1,
"files": [
{ "path": "JsonParser.exe", "size": 3456789, "sha256": "..." }
]
}
```
完整 ZIP 的 SHA-256 由签名 Catalog 保存(不写入包内部);files.json 用于解压后复核关键文件与修复功能。
### 2.3 安全限制(必须拒绝)
绝对路径;`../` 穿越;符号链接/重解析点逃出 staging;写入其他软件或盒子目录;覆盖 `data/` 与 `licenses/`;包内自动执行脚本(install.bat/PowerShell 钩子);未验证 SHA-256/签名的包被执行;解压文件数、总体积或压缩比无上限;entrypoint 指向 payload 之外。
### 2.4 安装记录 installed-app.json(本地)
记录实际安装的软件 ID、版本、架构、channel 和文件清单;与 `current/`、`staging/`、`backup/` 同级存放于 `apps/<id>/`。
## 3. 许可证(服务端签发 → 本地离线验证)
```json
{
"schema_version": 1,
"license_id": "lic-...",
"machine_hash": "...",
"products": ["product-json-parser"],
"issued_at": "2026-07-16T00:00:00Z",
"perpetual": true,
"update_policy": "updates-until-2027-12-31",
"rebind_policy": "self-service-1-per-90d",
"signature": "..."
}
```
- machine_hash 由平台层多个稳定硬件标识清洗生成;许可证中**不保存**原始序列号和 MAC。
- 客户端用内置 Ed25519 公钥离线验签;许可证保存于 `licenses/`,与程序文件、用户配置分离;更新不得覆盖。
- 撤销名单同样签名并缓存,网络失败保留宽限期。
- 盒子负责导入/展示/管理;**子软件必须用 sdk 的 licensing 逻辑独立再验证**(签名 + machine_hash + product_id),决定正式版/试用版/授权错误。
## 4. application 事件合约(core → UI)
后台任务不直接修改 Gio 控件,只发布事件;UI 按 `request_id` + `app_id` 更新 ViewModel 并 `Invalidate`。
| 事件 | 触发时机 | 负载 | 结果 |
| --- | --- | --- | --- |
| CatalogRefreshed | 清单验签并缓存成功 | catalog 摘要、generated_at | 列表刷新 |
| CatalogRejected | 清单验签失败 | 原因码 | 提示 + 继续用缓存 |
| DownloadStarted | 下载任务开始 | request_id, app_id | 状态 → downloading |
| DownloadProgress | 进度更新 | request_id, app_id, done, total, speed | 进度条刷新 |
| DownloadPaused | 用户暂停 | request_id, app_id | 状态 → queued(暂停态) |
| DownloadCompleted | 下载并校验通过 | request_id, app_id | 状态 → verifying/extracting |
| DownloadFailed | 失败(网络/哈希/磁盘) | request_id, app_id, error_code | 状态 → failed + 可重试 |
| InstallCompleted | 原子切换成功 + 健康检查通过 | app_id, version | 状态 → installed |
| InstallRolledBack | 切换失败恢复 backup | app_id, error_code | 状态 → rollback 完成提示 |
| AppStarted / AppExited | 进程启动/退出检测 | app_id, pid | 状态 → running / installed |
| LicenseChanged | 许可证导入/撤销 | products | 授权视图刷新 |
错误码为稳定英文枚举(如 `hash_mismatch`, `zip_path_escape`, `disk_full`, `app_running`, `signature_invalid`),UI 负责本地化文案。
## 5. CLI 参数合约
### 5.1 SoftBox.exe
```bash
SoftBox.exe # 正常启动
SoftBox.exe --open-app <id> # 打开并定位到指定软件
SoftBox.exe --update-app <id> # 触发指定软件更新流程
SoftBox.exe --repair-app <id> # 按 files.json 修复安装(V1.1)
```
子软件的「检查更新」按钮调用以上参数,不自建下载器;盒子未运行时子软件可启动它。
### 5.2 SoftBoxUpdater.exe(盒子自更新助手)
```bash
SoftBoxUpdater.exe --pid <主程序PID> --staging <暂存目录> --target <目标目录>
```
等待主进程退出 → 备份旧版 → 切换新版 → 启动新版 SoftBox → 失败时恢复备份。退出码:0 成功;非 0 失败并写日志。
### 5.3 子软件推荐参数(v1 推荐,非强制)
```bash
<App>.exe --softbox-info # 输出软件 ID、版本、架构、协议版本 JSON,退出码 0
<App>.exe --softbox-health # 基本环境自检,成功退出码 0
```
产品自定义参数不得占用 `--softbox-` 前缀。
## 6. 进程退出协议(更新前)
v1:进程快照判断运行 → 提示用户保存关闭 → 等待正常退出 → 超时取消更新,**不默认强杀**。
V1.1:命名管道 `\\.\pipe\softbox.<app-id>`,盒子发送 `{"command": "prepare_update", "request_id": "..."}`,子软件保存数据回复 `ready` 后自行退出。
## 待实现时确认
- 清单签名封装格式(签名域、密钥轮换字段)定稿后同步 `schemas/`。
- 错误码完整枚举表。
- 图标资源的分发方式(内嵌哈希 vs 独立 URL)。
- 撤销名单的结构与宽限期时长。
- machine_hash 的标识来源清单与加权算法(平台层内部文档)。
+19
View File
@@ -0,0 +1,19 @@
# 干净收尾检查清单
> 每轮会话结束前逐项过一遍,确保仓库处于"下一轮无需人工修复即可直接开工"的状态。
> 这是把"提前宣告完成"挡在门外的最后一道关卡,配合 [`05-coding-rules.md`](05-coding-rules.md) 的验证清单使用。
收尾前确认:
- [ ] 标准启动路径仍可用(`./init.sh` 或本项目等价命令能跑通)。
- [ ] 标准验证 / smoke 仍可运行,结果如实。
- [ ] 本轮执行记录已写进当前任务文件(`docs/tasks/T-<编号>.md`)的 `## 执行记录`(含跑过的命令和结果作为证据)。
- [ ] 任务文件 frontmatter 的 `status` 真实反映 `DONE` 与未验证的边界,没有"假 DONE"。
- [ ] [`current-state.md`](current-state.md) 的项目级快照与现实一致(启动/验证路径、目录要点、blocker)。
- [ ] 没有半成品改动处于未记录状态;如有,已写明 `BLOCKED` / `PARTIAL` 和原因。
- [ ] 代码处于可安全恢复的状态(必要时已提交,提交信息清晰)。
- [ ] 本轮实际修改没有超出任务 `write_paths`,也没有与其他活跃任务发生路径重叠。
- [ ] 启用 Gitea 时,Issue 的唯一 `status/*`、claim / 工作分支、PR 和任务状态彼此一致;未完成任务没有误删 claim。
- [ ] 已运行 `python scripts/validate_harness_governance.py`;启用且可连接 Gitea 时,还运行了只读 `python scripts/audit_gitea_coordination.py --repo 【owner/repo】 --dispatcher 【Gitea登录名】`。
任意一项不满足,就先补到满足,再结束会话。
+80
View File
@@ -0,0 +1,80 @@
# 当前实现状态
> 本文是可覆盖的**项目级快照**,记录代码与任务的现实状态,帮助 AI coding agent 避免只看计划而忽略仓库现状。
> 执行记录写进各任务文件(`docs/tasks/T-<编号>.md`)的 `## 执行记录`,不要在本文重复维护完整执行日志。
## 职责边界
- [`tasks/`](tasks/README.md)(`docs/tasks/T-<编号>.md`):任务规格、依赖、状态(frontmatter)和执行记录,一任务一文件。
- [`06-tasks.md`](06-tasks.md):只读路线图,维护阶段划分、里程碑和待办池。
- [`../progress.md`](../progress.md):可选历史归档 / 项目级大事记,不逐任务追加。
- `current-state.md`:当前快照,可覆盖更新当前目录、当前命令、已完成摘要和下一步。
## 当前快照
- 日期:2026-07-16
- 阶段:MVP 起步(Phase 0 未开始,仓库只有 harness 文档,无任何 Go 代码)
- 技术栈:已定稿于 `03-tech-stack.md`(Go + Gio,双工具链矩阵),尚未落地
- 生产代码:无;`core/`、`app-modern/`、`app-win7/`、`schemas/`、`testdata/` 均待 T-001 创建
- 测试:无
- 数据:无;Catalog 清单与测试样例待建
- 标准启动路径:`./init.sh` / `./init.ps1`(**尚未配置**,顶部三个命令仍为占位符,运行会主动失败提示——T-001 负责替换)
- 标准验证路径:同上,未配置
- 版本管理:git 已初始化,main 分支,远端 origin 为 Gitea `opc/soft_quay`;harness 文档已提交
- 当前 blocker:无;下一步即领取 T-001
## 当前目录要点
| 路径 | 状态 | 说明 |
| --- | --- | --- |
| `docs/` | 已有 | harness coding 文档集(本次初始化完成) |
| `docs/tasks/` | 已有 | 任务目录;T-001 已落成任务文件待领取 |
| `scripts/` | 已有 | harness 治理脚本(validate_agent_context 等);构建脚本待建 |
| `core/` | 待建 | 共享业务核心(T-001) |
| `app-modern/` | 待建 | 现代版模块(T-001) |
| `app-win7/` | 待建 | Win7 遗留版模块(T-001) |
| `schemas/` | 待建 | 协议 JSON Schema(T-201) |
| `testdata/` | 待建 | 假数据测试样例(T-101 起) |
## 任务状态
任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准。本节只写项目级摘要:
- 已完成:无。
- 正在进行:无。
- 下一个可领取任务:`T-001 初始化 monorepo 骨架`(`docs/tasks/T-001.md`)。
## 当前可运行内容
```bash
# 目前仅文档治理检查可运行:
python3 scripts/validate_agent_context.py
python3 scripts/validate_harness_governance.py
# Go 构建 / 测试:骨架未建立,暂不可运行(见 T-001)
```
## 开始编码前检查
开始任意任务前:
1. 读仓库级 agent 规则文件 `AGENTS.md`。
2. 读 `docs/00-ai-start-here.md`。
3. 读 `docs/05-coding-rules.md`。
4. 在 `docs/tasks/` 找到 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件;暂无任务时,先按 `docs/06-tasks.md` 路线图落成任务文件。
5. 在独立分支 / worktree 把任务改为 `DOING`。
## 维护规则
当实际代码状态发生变化时,同步更新本文件:
- 新增或移动入口文件。
- 初始化框架或模块。
- 新增可运行命令。
- 发现文档和代码现实不一致。
- 阶段、项目级 blocker 或可领取任务摘要发生需要跨会话保留的变化。
同时注意:
- 任务长期状态改在对应任务文件的 frontmatter;每轮执行记录、验证命令、阻塞点和关键决策写进该任务文件的 `## 执行记录`。
- 本文件只保留当前快照,不保留完整历史。
+43
View File
@@ -0,0 +1,43 @@
# 评审评分表
> 在一轮(或几轮)会话实现完成后、正式验收前,用这张表做一次结构化评审,回答"这轮 agent 做得好不好"。
> 它评的是**单次输出质量**;代码库长期健康度见 [`quality-document.md`](quality-document.md)。
## 评分维度
六个维度,每个 0-2 分(0 不满足 · 1 部分满足 · 2 满足)。
| 维度 | 问题 | 分数 (0-2) | 备注 |
| --- | --- | --- | --- |
| 正确性 | 实现出来的行为是否符合目标功能 / 验收标准? | | |
| 验证 | 要求的检查是否真的跑过,并在对应任务文件(`docs/tasks/T-<编号>.md`)的 `## 执行记录` 留下证据? | | |
| 范围纪律 | 这一轮是否基本保持在选定的单个任务范围内? | | |
| 可靠性 | 结果是否能在重启或重跑后继续工作? | | |
| 可维护性 | 代码和文档是否清楚到足以交给下一轮会话? | | |
| 交接准备度 | 新会话是否能只靠仓库内文件继续推进? | | |
## 结论
从下面三选一:
- **Accept** — 达标,可验收。
- **Revise** — 需要修补才能接受(列出必须补的修复)。
- **Block** — 有根本性问题,需要先解决(列出阻塞项)。
## 后续动作
- 缺失的证据:
- 必须补的修复:
- 下次复审触发条件:
## 关于校准(重要)
开箱即用的 agent 做评审很弱——它会发现问题,然后把自己说服到通过。所以这张表的通过/失败标准需要反复校准到和人工判断一致:
1. 用本表给一个已完成的任务打分。
2. 把它的分数和你自己的人工判断对比。
3. 有分歧的地方,把对应维度的"什么算 2 分 / 什么算 0 分"写得更具体,落到本项目的真实验收标准上。
4. 对同一个输出重新打分,看是否对齐。
5. 重复直到评审判断和人工评审基本一致。
预计需要 3-5 轮校准。每轮把改了什么、为什么改记入 `../progress.md` 的项目级大事记(跨任务的校准决策适合记在那里)。
+140
View File
@@ -0,0 +1,140 @@
# Gitea 多 Agent 协作协议
> 本协议是可选增强。启用 Gitea 协作时,任务规格留在 Git,实时协调放在 Issue / PR;未启用时继续使用 [`tasks/README.md`](tasks/README.md) 的本地流程。
## 工件与权威来源
| 工件 | 保存什么 | 不保存什么 |
| --- | --- | --- |
| `docs/tasks/T-<编号>.md` | 任务规格、依赖、允许写路径、验收标准、可审计执行证据 | Token、实例地址、临时聊天 |
| Gitea Issue | 实时状态、领取者、阻塞、结构化 claim / 续租记录 | 需求正文的唯一副本 |
| `claims/T-<编号>` 分支 | 防御性领取标记;分支存在表示 dispatcher 已分配任务 | 工作提交、跨 dispatcher 的线性化锁 |
| `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 不并发自选任务。claim 分支用于识别已分配任务并拦截顺序重试 / 常见旁路,不把 Gitea 的普通 create-branch API 当作线性化锁:
1. 读取默认分支任务文件和对应 Issue,确认双向映射、依赖均为 `DONE`、Issue 为 `status/todo`,且目标 worker 没有其他活跃任务。
2. 读取默认分支头提交 SHA,记为 `context_ref`。串行检查所有活跃预留的 `write_paths`,不得与本任务重叠。
3. 从精确的 `context_ref` 创建 `claims/T-<编号>` 防御性标记。若已存在、返回非成功或状态不确定就停止并人工核查;并发冲突在不同版本中可能表现为 `409` 或 `5xx`,不得自动无限重试,也不得仅因分支 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 的 Gitea 登录名】
context_ref: 【40 位提交 SHA】
claim_branch: claims/T-123
work_branch: agent/【agent-id】/T-123
write_paths:
- docs/tasks/T-123.md
- 【其他仓库相对路径】
claimed_at: 【RFC 3339 时间】
lease_until: 【RFC 3339 时间】
```
断线重连时,只有 Issue 最新有效 claim 的 `claimed_by`、工作分支和当前 agent 全部一致,才可把已有 claim 当作自己的恢复现场;仅比较 SHA 不足以证明所有权。
## 写路径防撞
- 默认分支任务文件定义初始 `write_paths`;领取后,由配置的 dispatcher Gitea 身份发布、且 `allocated_by` 与评论作者一致的最新完整 CLAIM / CLAIM RENEWAL,与工作分支任务文件共同定义活跃预留。二者不一致时暂停工作。
- `write_paths` 必须列出任务文件本身及预期修改的文件或目录;共享配置、锁文件、导航文件也要列入。
- 两条路径相同,或一条是另一条的目录前缀,视为重叠;活跃任务不得存在重叠路径。
- 发现必须修改范围外文件时,先停止并在 Issue 提议扩展范围;dispatcher 串行复查其他活跃预留,接受后追加包含全部字段和新路径的 `CLAIM RENEWAL`,worker 同步更新工作分支任务文件,二者都完成后才能继续。审计始终以最后一个完整 CLAIM 块为准。
- 活跃任务由 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 与断连
- worker 应在 `lease_until` 前请求续租;dispatcher 串行复查后,由自己的 Gitea 身份发布包含全部字段的 `CLAIM RENEWAL`。单次租期最长 24 小时,续租不得更换 `task`、`claimed_by`、`allocated_by`、`context_ref`、claim / 工作分支;审计以最后一个由配置 dispatcher 发布的有效块为准。
- claim 过期不等于可以自动抢占。维护者先检查 Issue 最后活动、工作分支新提交和 PR,再评论回收原因并人工删除 claim 分支。
- Gitea / MCP 断连时,只能继续已经确认归属自己的任务;不能领取新任务、释放锁或猜测远端状态。
只读检测命令:
```powershell
python scripts/audit_gitea_coordination.py --repo 【owner/repo】 --dispatcher 【Gitea登录名】
```
从默认分支的 clean checkout 运行审计。`--dispatcher`(或非敏感环境变量 `GITEA_DISPATCHER_LOGIN`)指定唯一可信的 dispatcher Gitea 登录名;审计只接受该账号发布且 `allocated_by` 一致的 CLAIM。它会核对标签、任务依赖、任务 ↔ Issue、claim / 工作分支、PR、活跃写路径和 `lease_until`,不会写远端。发现过期 claim 后不自动删除:维护者先查 Issue 最后活动、分支新提交和 PR,再评论回收原因,确认无人继续工作后才通过 UI 或受控 REST 删除。
## 初始化标签
先预览,再显式写入:
```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 中完成。
## 并发验收
可用 `python scripts/test_gitea_claim_race.py --repo 【owner/repo】 --apply` 在唯一 `claims/__probe__/race-*` 临时分支做兼容性 smoke,期望恰好一个 `201`、一个 `409`。脚本只在名称前缀和 SHA 都符合预期时清理并复查 404。一次 smoke 结果不能证明 create-branch 线性化;无论结果如何,MVP 仍依赖 dispatcher 串行分配。不得在业务任务分支上试验。
## 自动化与升级阈值
- `python scripts/validate_harness_governance.py` 完全离线检查上下文清单、导航、本地链接、任务 frontmatter / 依赖 / 写路径、Gitea 模板和已跟踪文本中的敏感值。
- `.gitea/workflows/harness-governance.yml` 在 push / PR 运行标准库测试和离线检查,不注入本机长期 PAT,也不运行远端审计。平台仍会提供 job token,工作流用 `permissions: read-all` 和 `persist-credentials: false` 收窄权限与留存。
- Actions 模板只有合入默认分支、仓库启用 Actions 且带 Python 3.10+ 的 `ubuntu-latest` runner 可用时才会真正执行;内网 runner 还要能取得 `actions/checkout@v4`。没有 runner 时,以相同本地命令作为验收证据,不宣称 CI 已跑绿。
- 退出码统一:`0` 通过,`1` 发现一致性问题,`2` 配置、网络或运行前提缺失。敏感信息检查只输出规则、文件和行号,不回显命中正文。
MVP 不实现 webhook、协调服务或独立 dashboard。只有出现以下任一信号才重新评估:单项目约 20 个以上并发任务、dispatcher 成为持续瓶颈、跨仓库聚合成为刚需、重复出现路径分配竞态,或审计 / 合规要求集中查询。届时优先增加原子 allocation 服务和 webhook 索引,再评估只读 dashboard;不把前端看板当作并发控制器。
+119
View File
@@ -0,0 +1,119 @@
# Gitea MCP 接入
> 可选增强:让 agent 通过 Gitea 读取共享文档、Issue、分支和 PR。Git checkout 仍是本地编辑与离线降级入口,MCP 不取代 Git。
## 适用边界
- Gitea Git 仓库保存版本化文档和代码。
- Gitea Issue / PR 保存实时协调状态。
- Gitea MCP 提供受控的远端读取和写入工具。
- `AGENTS.md`、`docs/00-ai-start-here.md` 等最小启动文件仍保留在项目 checkout 中。
## 私有配置
从根目录 [`gitea.env.example`](../gitea.env.example) 复制一份到 `$HOME/.codex/gitea.env`,替换示例值。也可用 `GITEA_ENV_FILE` 指向其他本机私有路径:
```text
GITEA_URL=【Gitea 实例根地址,不含 /api/v1】
GITEA_TOKEN=【最小权限 Personal Access Token】
# 启用远端协调审计时设置;不是秘密:
GITEA_DISPATCHER_LOGIN=【唯一 dispatcher 的 Gitea 登录名】
# 仅当团队明确接受 HTTP 下 Token 明文传输风险时设置:
GITEA_ALLOW_INSECURE_HTTP=1
# 仅当该实例必须绕过本机代理直连时设置:
GITEA_DIRECT=1
```
规则:
- 不把 `gitea.env`、Token、Authorization header、私有实例地址提交到仓库或粘贴到 Issue。
- Token 一旦出现在聊天、日志或提交历史中,立即撤销并轮换。
- 推荐 HTTPS;如果项目长期使用 HTTP,必须在项目安全决策中记录风险接受人、网络边界和轮换策略。
- `GITEA_URL` 填实例根地址;`gitea-mcp` 会自动追加 `/api/v1`。
## Codex 配置
复制 [`../scripts/gitea-mcp.ps1`](../scripts/gitea-mcp.ps1) 到稳定的本机路径,然后在全局 `~/.codex/config.toml` 或可信项目的 `.codex/config.toml` 注册:
```toml
[mcp_servers.gitea]
command = "pwsh.exe"
args = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "【gitea-mcp.ps1 的绝对路径】"]
default_tools_approval_mode = "writes"
startup_timeout_sec = 30
tool_timeout_sec = 60
```
包装脚本固定使用 `gitea-mcp==0.5.1`,避免 `uvx` 自动升级造成协议或工具集合漂移。升级版本时先在独立分支验证 `initialize`、`tools/list` 和一条只读 API,再更新版本号。
## 工具审批
默认策略:
- 自动允许只读:`list_repos`、`read_file`、`list_issues`、`get_issue`、`list_branches`、`list_pull_requests`。
- 写入前确认:`create_issue`、`update_issue`、`add_comment`、`create_branch`、`commit_changes`、`create_pr`。
- 破坏性动作再次确认:`merge_pr`、关闭 Issue、覆盖文件、批量操作。
如果 Codex 版本支持 `enabled_tools` / `disabled_tools`,应再用 allowlist 收窄工具,而不是只依靠提示词。
## 本地验证
只检查文件格式,不连接 Gitea、不显示 Token:
```powershell
./scripts/gitea-mcp.ps1 -CheckConfig
```
连接预检:
```powershell
./scripts/gitea-mcp.ps1 doctor
```
预检至少确认:实例可达、Token 有效、当前用户正确、MCP 版本固定。失败时查看系统临时目录中的 `gitea-mcp-<PID>.stderr.log`;日志不得复制 Token 或敏感正文。
## 降级规则
- Gitea / MCP 不可用:允许继续已领取任务的本地工作,不允许领取新任务或猜测远端状态。
- 恢复连接后:先拉取默认分支并重新读取任务 Issue,再提交或更新状态。
- MCP 读取结果与本地 checkout 冲突:以明确记录的提交 SHA 为比较基准,不静默覆盖本地未提交改动。
## 按需读取
启用 [`agent-context.json`](agent-context.json) 后,agent 不用通过 MCP 全量读取 `docs/`:
1. 获取默认分支头 SHA 作为 `context_ref`。
2. 读取清单和 `bootstrap.always_read`。
3. 按本轮任务类型读取对应 `routes`。
4. 保存 `read_file` 返回的文件 SHA;同一会话内 SHA 未变化时复用内容。
Gitea 中的文件与本地 `docs/` 是同一 Git 工件的远端与 checkout,不要再创建第三份人工同步副本。
## Issue / PR 协调
多 agent 协作时遵循 [`gitea-collaboration.md`](gitea-collaboration.md):
- 任务文件保存规格和长期证据,Issue 保存实时状态,PR 保存评审与合并决策。
- 领取互斥依赖 dispatcher 串行分配;`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 人工回收。
## 治理检查
离线检查不需要 Token,可放进 Gitea Actions:
```powershell
python -m unittest discover -s tests -p "test_*.py"
python scripts/validate_harness_governance.py
```
远端一致性检查单独运行,严格只读:
```powershell
python scripts/audit_gitea_coordination.py --repo 【owner/repo】 --dispatcher 【Gitea登录名】
```
远端审计区分“不一致”(退出码 1)和配置 / 网络 / 权限失败(退出码 2),并验证 dispatcher 评论身份、任务依赖和过期 claim;`--dispatcher` 可由非敏感环境变量 `GITEA_DISPATCHER_LOGIN` 代替。它不会更新标签、关闭 Issue、合并 PR 或删除分支。
+37
View File
@@ -0,0 +1,37 @@
# 方法对照表
> 把最常见的长时 coding-agent 失败模式,对应到本仓库里最该先补的工件或规则。
> 出问题时先查这张表,对症补对应工件,不要把更多规则一股脑堆进一个超长入口文件。
## 失败模式 → 首要修复 → 工件
| 失败模式 | 实际表现 | 首要修复 | 主要工件 |
| --- | --- | --- | --- |
| 新会话摸黑 | 新会话花大量时间重新摸索状态和启动方式 | 让仓库成为唯一事实来源 | [`current-state.md`](current-state.md) + [`tasks/`](tasks/README.md) 任务文件的执行记录 |
| 启动脆弱 | 每轮会话都要重新学怎么启动、装依赖、跑测试 | 统一启动与验证路径 | [`../init.sh`](../init.sh) / [`../init.ps1`](../init.ps1) |
| 范围蔓延 | 一次启动多个任务,最后没有一个完整收尾 | 限制当前活跃范围,一轮只做一个任务 | [`tasks/README.md`](tasks/README.md) |
| 提前宣布完成 | 代码改了就说"完成了",但没有可运行证据 | 把完成绑定到验证证据 | [`tasks/README.md`](tasks/README.md)(passing 需证据)+ [`clean-state-checklist.md`](clean-state-checklist.md) |
| 交接薄弱 | 下一轮看不出哪里可用、哪里坏了、接下来做什么 | 每轮留下明确的当前快照和下一步 | [`current-state.md`](current-state.md) |
| 评审主观 | 质量判断靠个人记忆和感觉,agent 容易自我说服通过 | 用固定维度做评分 | [`evaluator-rubric.md`](evaluator-rubric.md) |
| 代码库悄悄退化 | 速度上去了,但几轮会话后代码越来越难审、边界越来越糊 | 定期给代码库健康度打分 | [`quality-document.md`](quality-document.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 同时把同一 Issue 改为 doing,读回后都以为成功 | 由 dispatcher 串行分配,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) |
| claim 长期占用 | Issue 仍 doing,但 agent 已退出或分支无活动,后续任务无法分配 | 只读审计 `lease_until`,人工核实后回收,不自动抢占 | [`gitea-collaboration.md`](gitea-collaboration.md) + [`../scripts/audit_gitea_coordination.py`](../scripts/audit_gitea_coordination.py) |
| 规则悄悄漂移 | 导航、任务元数据、模板或敏感配置在多轮提交后不一致 | 用同一离线治理命令在本地和 Actions 检查 | [`../scripts/validate_harness_governance.py`](../scripts/validate_harness_governance.py) |
## 使用原则
- 优先补最能直接消除当前失败模式的那**一个**工件,不要一次铺开全部。
- 工件之间用链接互相引用,让全新 agent 不问人也能从一个文件跳到相关规则。
- 同一个事实只维护一份,避免多个文件互相打架。
- 修复落地的同一轮会话里,就把对应工件更新掉。
## 评审 vs 健康度:两个不同的问题
- [`evaluator-rubric.md`](evaluator-rubric.md) 回答:**"这轮 agent 做得好不好?"**(单次输出质量)
- [`quality-document.md`](quality-document.md) 回答:**"这个项目在变强还是变弱?"**(代码库本身质量)
两者配合:rubric 守住每轮交付,quality document 守住长期趋势。
+64
View File
@@ -0,0 +1,64 @@
# 质量文档
> 给项目的每个产品领域和架构层打分,跟踪代码库随时间是变强还是变弱,回答"这个项目在变强还是变弱"。
> 它评的是**代码库本身的质量**;单次 agent 输出质量见 [`evaluator-rubric.md`](evaluator-rubric.md)。
## 使用时机
- **开始会话前**:读它,了解代码库当前哪里最弱,优先处理。
- **会话结束后**:更新评级。
- **长期**:对比不同时间点的快照,看哪些改动真正改善了代码库健康度。
- 做基准对比、清理简化、或换新 agent / 新模型时,也更新一次。
## 评级标准
- **A**:验证全部通过,结构干净,agent 能读懂,测试稳定。
- **B**:验证通过,基本干净,可读性或测试覆盖有少量缺口。
- **C**:部分可用,有已知缺口,部分代码 agent 不容易理解。
- **D**:不可用,或存在重大结构问题。
---
## 产品领域
> 把下面的占位领域换成本项目 `02-requirements.md` 里的真实功能域。
| 领域 | 评级 | 验证状态 | Agent 可读性 | 测试稳定性 | 关键缺口 | 上次更新 |
|------|------|---------|-------------|-----------|---------|---------|
| 【核心流程 1】 | - | - | - | - | - | - |
| 【核心流程 2】 | - | - | - | - | - | - |
| 【数据 / 持久化】 | - | - | - | - | - | - |
| 【账号 / 鉴权】 | - | - | - | - | - | - |
## 架构层
> 把下面的占位层换成本项目 `04-architecture.md` 里的真实分层 / 模块边界。
| 层级 | 评级 | 边界执行 | Agent 可读性 | 关键缺口 | 上次更新 |
|------|------|---------|-------------|---------|---------|
| 【前端 / 客户端 / CLI】 | - | - | - | - | - |
| 【后端 API / 核心模块】 | - | - | - | - | - |
| 【数据 / 存储层】 | - | - | - | - | - |
| 【外部服务适配】 | - | - | - | - | - |
## 变更历史
### YYYY-MM-DD
- 变更内容:
- 提升:
- 下降:
- 新发现的缺口:
- 已关闭的缺口:
---
## 进阶:验证 harness 是否可以简化
Harness 里的每个组件(规则、脚本、检查清单)都编码了一个假设——"模型做不到这件事"。模型变强后,这些假设可能过时。用本文档检查某个组件是否还有必要:
1. 拍一份本文档快照。
2. 移除一个 harness 组件。
3. 跑一轮基准任务。
4. 再拍一份快照。
5. 对比——评级没降,说明那个组件多余,可以去掉;降了,就恢复。
+61
View File
@@ -0,0 +1,61 @@
# 页面与视图结构(Gio)
> 本项目是 Gio 桌面应用,没有 URL 路由;本文约定页面(视图)划分、职责和组件归属。现代版与 Win7 版共享 ViewModel 和交互语义,Gio 控件实现允许分别适配。
## 主窗口布局
```text
┌─────────────────────────────────────────────┐
│ 顶部栏:搜索框 · 分类筛选 · 检测更新 · 下载队列入口 │
├────────┬────────────────────────────────────┤
│ 左侧 │ 中部:虚拟化软件列表(layout.List) │
│ 视图 │ 每项:图标·名称·版本·状态·进度·主操作按钮 │
│ 全部 │ │
│ 已安装 │ 右侧/弹层:软件详情 │
│ 可更新 │ 版本 · 简介 · 教程 · 授权状态 · 操作 │
│ 最近使用│ │
├────────┴────────────────────────────────────┤
│ 底部状态栏:网络 · 任务数 · 磁盘 · 盒子版本(Legacy 标识) │
└─────────────────────────────────────────────┘
```
## 视图清单
| 视图 | 职责 | MVP |
| --- | --- | --- |
| 软件列表(主视图) | 全部/已安装/可更新/下载中/最近使用切换;搜索、分类、多标签筛选;滚动位置稳定 | P0 |
| 软件详情(弹层或右栏) | 版本、简介、教程链接、授权状态;下载/更新/启动/取消操作 | P0 |
| 下载队列 | 所有任务的进度、速度、剩余时间;暂停/取消/重试 | P0 |
| 设置 | 并发数、目录、代理、自动检查更新、beta 通道、日志级别、便携模式(V2) | P0(最小集) |
| 授权 | 许可证导入、已授权软件列表、machine 信息、换绑/申诉入口 | P0 |
| 提示/错误弹窗 | 失败原因、重试路径、更新确认、退出运行中软件提示 | P0 |
## 组件归属
| 组件 | 归属 | 说明 |
| --- | --- | --- |
| AppShell | 全局 | 窗口框架、顶部栏、左侧导航、底部状态栏 |
| AppList | 主视图 | 惰性 `layout.List`;控件状态按软件 ID 保存,不按列表序号 |
| AppRow | 主视图 | 单个软件项:状态、进度和主操作按钮 |
| AppDetail | 详情 | 详情展示与操作 |
| DownloadPanel | 下载队列 | 任务列表与控制 |
| SettingsForm | 设置 | 设置项读写(经 application 用例) |
| LicensePanel | 授权 | 导入与状态展示 |
| Toast/Dialog | 全局 | 错误与确认交互 |
每个页面用独立 struct 保存:`widget.Clickable`、`widget.Editor`、`layout.List`、过滤条件、当前 ViewModel、pending RequestID、临时提示和错误状态。
## 交互规则(硬约束)
- 每帧先 drain 点击事件,再提交用例;后台只发布事件,UI 在 ApplyEvent 中更新 ViewModel 后 `Window.Invalidate`。
- Layout 中不得读磁盘、访问网络、计算哈希;图标走内存 + 磁盘缓存(按 DPI)。
- 数百个软件项滚动不卡顿是验收标准,不是优化项。
- 未验证/不兼容的软件显示原因,操作按钮禁用,不静默失败。
- Win7 版允许减少动画、阴影和高成本渲染,但交互语义与现代版一致,并明显展示 Legacy 标识。
## 导航规则
- 列表 → 详情:点击软件项;详情保留返回/关闭。
- 任一视图可进下载队列(顶部入口);任务完成后返回时列表状态已回填。
- 启动需授权的软件而无有效许可证时,引导到授权视图,不直接报错退出。
- 更新需要退出运行中的软件时,弹确认对话框,等待用户处理,超时取消更新。
+99
View File
@@ -0,0 +1,99 @@
# 任务文件(一任务一文件 · 默认任务管理方式)
> 本目录是项目任务的**默认存放位置**:每个任务一个文件 `docs/tasks/T-<编号>.md`,单 agent 与多 agent 并发通用。
> 阶段划分、里程碑和待办池见路线图 [`../06-tasks.md`](../06-tasks.md);路线图只读,不跟踪单任务状态。
## 为什么默认一任务一文件
- **单 agent**:领任务只读一个文件就拿到完整上下文(背景、方案、验收、执行记录),不用在看板、进度流水、快照三个共享文件之间跳转同步;执行记录和任务绑定,审查时 `git log -p` 一个文件即可回放全程。
- **多 agent 并发**:单个大看板 + 多写者 = **编辑竞争**(读到旧版本、反复重读)、**ID 撞号**(全局递增号是共享计数器)、**合并冲突**(相邻行改动)。一任务一文件后:改哪个任务只动哪个文件,agent 之间互不抢占;ID 撞号在**新建文件时当场暴露**(文件已存在就换号)。
- **零迁移**:项目从单 agent 长到多 agent,无需切换任何约定。
## 文件命名与 ID
- 文件名:`docs/tasks/T-<编号>.md`(如 `docs/tasks/T-101.md`);同族细分用后缀 `T-101a.md`。
- 落实路线图建议任务时,**沿用路线图 `../06-tasks.md` 里的建议编号**(如 T-101)。
- 路线图之外的新任务:取「路线图建议编号 + `docs/tasks/` 现有文件」里最大的 `T-###`,`+1`。
- **建文件即防撞**:若目标编号文件已存在(别的 agent 先建了),改用下一个号,**不要覆盖别人的文件**。
- 模板 `_template.md` 以下划线开头,不是真实任务、不参与编号扫描。
## 每个任务文件的结构(frontmatter + 正文)
```markdown
---
id: T-101
title: 一句话任务名
phase: 1 # 所属阶段,沿用路线图的 Phase 编号
deps: [T-100] # 依赖的任务 ID
status: TODO # TODO | DOING | DONE | BLOCKED
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】
---
## 问题 / 背景
## 方案
## 验收要点
## 边界(不改什么)
## 协作约束
## 执行记录
```
## 领取 / 完成流程
- 状态:`TODO` · `DOING` · `DONE` · `BLOCKED`。每个 agent 同时最多一个活跃任务;项目可以并行多个写路径互不重叠的任务。
- 每个 agent 一次只领一个 `status: TODO` 且依赖全 `DONE` 的任务,取编号最靠前的;若本目录暂无可领任务,先按路线图把下一个建议任务落成任务文件,再领取。
- `write_paths` 必须在动手前写清。两个活跃任务路径相同,或一条是另一条的目录前缀,均视为冲突,不能并行。
- 做完自测、按「passing 需证据」把验证命令与结果写清、改 `status: DONE`。
- **执行记录写进本任务文件的 `## 执行记录` 一节**(改了什么、跑了什么验证、结果、决策)——不逐任务追加共享的 `progress.md`(可选历史归档)、也不逐任务覆盖 `current-state.md`(项目级快照,只在启动/验证路径、目录结构或 blocker 变化时更新)。
- **只改自己那个任务文件**;不要编辑别人正在做的任务文件。
未启用 Gitea 时,在独立分支 / worktree 中把任务文件从 `TODO` 改为 `DOING` 即可。启用 Gitea 时,必须先按 [`../gitea-collaboration.md`](../gitea-collaboration.md) 由 dispatcher 串行分配并创建 `claims/T-<编号>` 防御性标记;assignee、标签、读回和普通 create-branch API 都不能单独提供并发互斥。
## 与 Gitea Issue / PR 的映射(可选)
- 一个任务文件对应一个主 Issue;Issue 负责实时领取、阻塞和评审状态,任务文件负责版本化规格和长期证据。
- 先把任务文件合入默认分支,再创建 Issue;随后把 Issue 编号回填任务文件并合入默认分支,最后才添加 `status/todo`。映射未完成的 Issue 不可领取。
- Issue、claim 分支、工作分支和 PR 都携带同一个 `T-<编号>`;不得用一个 PR 顺带完成多个任务。
- 工作分支命名为 `agent/<agent-id>/T-<编号>`,每个 agent 使用独立 worktree。
- MVP 由一个 dispatcher / 主 agent 串行分配任务,以此保证同一任务不被重复领取,并保证不同任务的 `write_paths` 不冲突。唯一 claim 分支只拦截顺序重试和常见旁路,不能替代 dispatcher 的互斥保证。
- 进入评审后 Issue 使用唯一 `status/review`;PR 合并且默认分支任务文件为 `DONE` 后,Issue 才能关闭并标记 `status/done`。
- 领取、结构化 claim 评论、过期锁回收和分支清理的完整规则见 [`../gitea-collaboration.md`](../gitea-collaboration.md)。
## 用户指令暗语(可选约定)
> 用户的工作流通常固定为:提 bug/需求 → 讨论定案 → 落成任务文件 → 提交 → 实现 → 提交。
> 为减少重复输入,可约定以下触发词;agent 读到即按约定执行。默认值:不注明视角就是全栈工程师视角;每步产物默认提交 git(只提交本次相关文件)。
| 用户输入 | agent 执行 |
| --- | --- |
| `bug: <现象>` / `需求: <描述>` | 先查代码再给分析和方案,**只讨论不改代码** |
| `grill: <方案>` | 反方评审,逐点挑战该方案 |
| `落task` | 把已讨论定案落成 `docs/tasks/T-<编号>.md`(按上述规则查号防撞),**只写文档不写代码,写完自动提交 git** |
| `审 T-<编号>` | **以 git 历史为准**(`git log -p` 该任务文件找出最近改动),先核代码事实,再审核该改动是否合理、给缺口 |
| `补` | 把讨论新增的结论补进当前任务文件并提交 git |
| `做 T-<编号>` | 实现该任务 + 跑任务内验证命令;**验证全绿才提交**(执行记录、状态 DONE、提交);验证失败 → 报告、**不提交**、状态留 DOING 或标 BLOCKED 记原因 |
| `记backlog: <一行>` | 追加进待办池(`docs/06-tasks.md` Backlog)并提交,只记一行、不建任务文件 |
补充规则:
- `落task`/`补` 可带参数(`落task <主题>`、`补 T-<编号>`);新会话或无对话上下文时 agent **必须先问清指代对象,不得猜**。
- 采用时把这套暗语同时写进项目的 `AGENTS.md` 工作规则,并**声明 `AGENTS.md` 为唯一权威源**(agent 记忆、模板副本仅为指针/种子)——多副本不声明权威源,改触发词时必然漂移。
- 触发词可按团队习惯改名,关键是「一个词 = 一个流程阶段 + 默认动作」。
## 看板视图
- 现阶段:`ls docs/tasks/` + 看各文件 frontmatter 的 `status`/`deps` 挑任务。
- 可选:加一个脚本把所有任务文件的 frontmatter 汇总成一张只读看板表,agent 不手改看板。
## 与路线图和共享文件的关系
- [`../06-tasks.md`](../06-tasks.md):只读路线图(Phase 划分、里程碑、Backlog、建议拆分清单);未启用 Gitea 时以任务文件 frontmatter 为准,启用后以 Issue 为实时状态、合并后的任务文件为长期事实。
- `../../progress.md`:可选工件,用作历史归档或项目级大事记;执行记录写各任务文件,不逐任务追加。
- [`../current-state.md`](../current-state.md):项目级快照(启动/验证路径、目录要点、blocker);任务状态不在此维护,可由脚本汇总 frontmatter 生成。
- [`../gitea-collaboration.md`](../gitea-collaboration.md):启用 Gitea 时的任务映射、串行分配、防重复 claim、写路径防撞和 PR 状态协议。
+63
View File
@@ -0,0 +1,63 @@
---
id: T-001
title: 初始化 monorepo 骨架(core / app-modern / app-win7 + go.work)
phase: 0
deps: []
status: TODO
created: 2026-07-16
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-001.md
- go.work
- core/
- app-modern/
- app-win7/
- init.sh
- init.ps1
- docs/00-ai-start-here.md
- docs/03-tech-stack.md
- docs/current-state.md
---
## 问题 / 背景
仓库当前只有 harness 文档,没有任何 Go 代码。按 `docs/04-architecture.md` 建立「单仓库、双构建」的工程骨架,是所有后续任务的前置。git 仓库与 Gitea 远端(origin)已存在,init 脚本三个命令仍是占位符。
## 方案
1. 创建 `core/`(go.mod,`go 1.20`),含 `domain/`、`application/` 空包和一个最小可测函数(如 SemVer 比较占位)+ 对应测试,保证 `go test ./...` 有真实断言可跑。
2. 创建 `app-modern/`(go.mod,锁定 Go 1.25 + Gio v0.10.1)与 `app-win7/`(go.mod,`go 1.20` + Gio v0.6.0),各含 `cmd/softbox/main.go`(暂可为无 UI 的最小 main,空 Gio 窗口归 T-003)、`ui/gio/`、`platform/windows/` 空包;两模块 require core(replace 由 go.work 处理,go.mod 不留本机路径)。
3. 根目录创建 `go.work` 纳入三个模块。
4. 替换 `init.sh` / `init.ps1` 顶部三个命令变量:
- INSTALL:`go work sync`
- VERIFY:`cd core && go vet ./... && go test -count=1 ./...`(脚本内等价写法)
- START:打印双目标构建命令(桌面应用无常驻 dev server)
5. 把真实命令同步到 `docs/03-tech-stack.md`、`docs/00-ai-start-here.md`、`docs/current-state.md`。
## 验收要点
- `go work sync` 成功。
- `cd core && go vet ./... && go test -count=1 ./...` 通过(Linux 无头环境),至少一个真实测试。
- `cd app-modern && GOOS=windows GOARCH=amd64 go build ./cmd/softbox` 产出 EXE。
- `cd app-win7 && GOTOOLCHAIN=go1.20.14 GOOS=windows GOARCH=amd64 go build ./cmd/softbox` 产出 EXE(证明 Go 1.20 兼容)。
- `./init.sh` 全流程跑通,不再提示占位符未替换。
- core 的 go.mod 无 Gio、无 Windows 专属依赖;依赖方向 app-* → core。
- `docs/current-state.md` 已覆盖更新为骨架完成后的现实。
## 边界(不改什么)
- 不实现任何业务功能(清单、下载、安装、授权均不做)。
- 不写 Gio 界面(空窗口在 T-003)。
- 不建 `schemas/`、`testdata/` 业务内容。
- 不引入除 Gio 外的第三方依赖。
## 协作约束
(未启用 Gitea;单 agent 直接在独立分支领取,状态改 DOING 后开工。)
## 执行记录
(做完在此记录:改了哪些文件、跑的验证命令与结果、阻塞、关键决策。)
+40
View File
@@ -0,0 +1,40 @@
---
id: T-XXX
title: 一句话任务名
phase: 1
deps: []
status: TODO
created: 【日期】
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-XXX.md
- 【允许修改的仓库相对路径】
---
## 问题 / 背景
(现象、根因、为什么要做)
## 方案
(怎么改,落到"改哪个文件、改成什么")
## 验收要点
(可验证的完成标准;按「passing 需证据」写清跑哪个验证命令,不写"应该能用")
## 边界(不改什么)
(明确不碰的模块/流程)
## 协作约束
(启用 Gitea 时填写对应 Issue、领取时的 `context_ref`、claim / 工作分支;任何新增写路径先检查与其他活跃任务是否重叠。)
## 执行记录
(做完在此记录:改了哪些文件、跑的验证命令与结果、阻塞、关键决策。
执行记录只写进本任务文件,不逐任务追加共享的 `progress.md`,避免多 agent 抢改共享文件。)