feat: 完成aide decide设计文档

This commit is contained in:
2025-12-15 01:37:19 +08:00
parent a4e2d54860
commit b67ff60c70
8 changed files with 2548 additions and 3 deletions

View File

@@ -1,5 +1,16 @@
# aide decide 子命令设计文档 # aide decide 子命令设计文档
## 零、详细设计文档包
本文档为概览设计;更细的实现交接规格见:
- [aide-program/docs/commands/decide/README.md](decide/README.md)
- [aide-program/docs/commands/decide/cli.md](decide/cli.md)
- [aide-program/docs/commands/decide/server.md](decide/server.md)
- [aide-program/docs/commands/decide/web.md](decide/web.md)
- [aide-program/docs/commands/decide/storage.md](decide/storage.md)
- [aide-program/docs/commands/decide/verification.md](decide/verification.md)
## 一、背景 ## 一、背景
### 1.1 解决的问题 ### 1.1 解决的问题

View File

@@ -0,0 +1,225 @@
# aide decide 详细设计(实现交接包)
本目录为 `aide decide` 子命令的**详细设计**。目标是让接手开发者在不阅读额外上下文的情况下,能够依据本文档集完成实现、联调与验证。
实现位置:
- 核心实现:`aide-program/aide/decide/`
- CLI 入口:`aide-program/aide/main.py``aide decide ...` 子命令树
上游/关联文档:
- 概览设计:[`aide-program/docs/commands/decide.md`](../decide.md)
- 数据格式规范(输入/输出格式):[`aide-program/docs/formats/data.md`](../../formats/data.md)
- 配置格式规范:[`aide-program/docs/formats/config.md`](../../formats/config.md)
- 插件侧调用契约:[`/aide:prep`](../../../../aide-marketplace/aide-plugin/docs/commands/prep.md)
## 一、范围与目标
### 1.1 目标
- 提供**程序化的待定项确认机制**,替代终端中的逐项文本确认
- 以 Web 界面呈现待定项,支持选项选择与备注添加
- 存储决策记录,支持历史追溯
- 遵循"静默即成功"的输出原则
### 1.2 非目标
- 不分析待定项内容(这是 LLM 的职责)
- 不做决策建议或推荐排序
- 不修改业务代码
- 不实现复杂的用户认证或多用户支持
## 二、关键约定(必须先统一)
1. **技术选型**
- HTTP 服务:使用 Python 标准库 `http.server`,无需额外依赖
- Web 前端:使用纯 HTML/CSS/JavaScript无需构建工具直接嵌入 Python 代码或作为静态资源
2. **服务生命周期**
- `aide decide '<json>'` 启动服务并阻塞等待
- 用户在 Web 界面提交决策后,服务自动关闭
- 服务关闭后LLM 调用 `aide decide result` 获取结果
3. **端口配置**
- 默认端口3721
- 端口被占用时尝试下一个端口3722、3723...),最多尝试 10 次
- 可通过配置文件 `decide.port` 指定固定端口
4. **数据存储**
- 待处理数据:`.aide/decisions/pending.json`
- 历史记录:`.aide/decisions/{timestamp}.json`
- `.aide/` 默认被 gitignore
5. **超时策略**
- 默认无超时(等待用户操作)
- 可通过配置 `decide.timeout` 设置超时时间(秒)
- 超时后服务关闭,`aide decide result` 返回错误
## 三、文档索引(按实现模块拆分)
| 文档 | 内容 |
|------|------|
| [cli.md](cli.md) | CLI 命令规格、参数校验、输出规范 |
| [server.md](server.md) | HTTP 服务设计、API 端点、生命周期管理 |
| [web.md](web.md) | Web 前端设计、组件结构、交互流程 |
| [storage.md](storage.md) | 数据存储设计、文件格式、生命周期 |
| [verification.md](verification.md) | 验证清单(实现完成后的自检) |
## 四、推荐实现模块划分(仅文件/职责约定)
实现位于 `aide-program/aide/decide/`,按职责拆分为:
```
aide/decide/
├── __init__.py # 模块导出
├── types.py # 数据类型定义DecideInput, DecideOutput 等)
├── storage.py # 数据存储pending.json 读写、历史记录)
├── server.py # HTTP 服务器(启动、路由、关闭)
├── handlers.py # API 请求处理器
└── web/ # 静态资源目录
├── index.html # 主页面
├── style.css # 样式
└── app.js # 交互逻辑
```
各模块职责:
- `types`:定义数据结构,与 `aide-program/docs/formats/data.md` 保持一致
- `storage`:负责 pending.json 和历史记录的读写、清理
- `server`HTTP 服务器生命周期管理、端口探测、静态资源服务
- `handlers`:处理 API 请求(获取待定项、提交决策)
- `web/`:纯静态前端资源,由 server 提供服务
> 注:本文档只约定职责与接口,不提供实现代码。
## 五、实现任务拆分(建议顺序)
### 阶段1基础设施
1. 创建 `aide/decide/` 目录结构
2. 实现 `types.py`:定义 DecideInput、DecideItem、Option、DecideOutput、Decision 等数据类型
3. 实现 `storage.py`pending.json 读写、历史记录保存
### 阶段2CLI 入口
4.`main.py` 添加 `aide decide` 子命令路由
5. 实现 JSON 解析与验证逻辑
6. 实现 `aide decide result` 命令
### 阶段3HTTP 服务
7. 实现 `server.py`HTTP 服务器基础框架
8. 实现端口探测逻辑
9. 实现 `handlers.py`API 端点处理
10. 实现服务生命周期管理(启动、等待、关闭)
### 阶段4Web 前端
11. 创建 `web/index.html`:页面结构
12. 创建 `web/style.css`:样式设计
13. 创建 `web/app.js`:交互逻辑(加载数据、选择选项、提交决策)
### 阶段5集成与验证
14. 端到端测试:完整流程验证
15.`verification.md` 逐项检查
### 依赖关系
```
@startuml
skinparam defaultFontName "PingFang SC"
[阶段1: 基础设施] as P1
[阶段2: CLI入口] as P2
[阶段3: HTTP服务] as P3
[阶段4: Web前端] as P4
[阶段5: 集成验证] as P5
P1 --> P2
P1 --> P3
P2 --> P3
P3 --> P4
P1 --> P5
P2 --> P5
P3 --> P5
P4 --> P5
@enduml
```
## 六、整体业务流程
```
@startuml
skinparam defaultFontName "PingFang SC"
participant LLM
participant "aide decide" as CLI
participant "HTTP Server" as Server
participant "Web Browser" as Browser
participant User
== 提交待定项 ==
LLM -> CLI : aide decide '<json>'
CLI -> CLI : 解析并验证 JSON
CLI -> CLI : 保存到 pending.json
CLI -> Server : 启动 HTTP 服务
CLI --> LLM : 输出访问链接
note right: → Web 服务已启动\n→ 请访问: http://localhost:3721\n→ 等待用户完成决策...
== 用户操作 ==
LLM -> User : 告知访问链接
User -> Browser : 打开链接
Browser -> Server : GET /
Server --> Browser : 返回 index.html
Browser -> Server : GET /api/items
Server --> Browser : 返回待定项数据
Browser -> User : 渲染界面
User -> Browser : 选择选项、添加备注
User -> Browser : 点击提交
Browser -> Server : POST /api/submit
Server -> Server : 保存决策结果
Server --> Browser : 返回成功
Server -> Server : 关闭服务
== 获取结果 ==
CLI --> LLM : 服务已关闭
LLM -> CLI : aide decide result
CLI -> CLI : 读取最新决策
CLI --> LLM : 返回 JSON 结果
@enduml
```
## 七、风险与待定项(需要开发前确认)
### 7.1 需要确认的设计决策
| 问题 | 建议方案 | 备选方案 |
|------|----------|----------|
| 前端是否需要支持移动端 | 否,仅支持桌面浏览器 | 响应式设计 |
| 是否支持多个待定项会话并行 | 否,同一时间只能有一个 pending | 支持多会话 |
| 服务启动后是否自动打开浏览器 | 否,仅输出链接 | 使用 webbrowser 模块自动打开 |
### 7.2 潜在风险
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 端口被占用 | 服务无法启动 | 自动尝试下一个端口 |
| 用户长时间不操作 | 服务一直阻塞 | 可配置超时时间 |
| 浏览器兼容性 | 界面显示异常 | 使用标准 HTML/CSS/JS避免新特性 |
| JSON 数据过大 | 解析/传输慢 | 限制 items 数量(建议 ≤50 |
### 7.3 后续优化方向(不在本次实现范围)
- 支持键盘快捷键操作
- 支持决策历史查看
- 支持决策导出Markdown/PDF
- 支持自定义主题
## 八、相关文档
- [program 导览](../../README.md)
- [decide 概览设计](../decide.md)
- [数据格式文档](../../formats/data.md)
- [配置格式文档](../../formats/config.md)
- [aide skill 设计文档](../../../../aide-marketplace/aide-plugin/docs/skill/aide.md)

View File

@@ -0,0 +1,365 @@
# aide decide CLI 规格
## 一、命令一览
`aide decide` 提供两个子命令:
| 子命令 | 语法API 约定) | 成功输出 | 主要用途 |
|--------|------------------|----------|----------|
| (默认) | `aide decide '<json>'` | 输出访问链接,阻塞等待 | 提交待定项数据并启动 Web 服务 |
| result | `aide decide result` | 输出 JSON 结果 | 获取用户决策结果 |
## 二、命令详细规格
### 2.1 aide decide提交数据并启动服务
**语法**
```
aide decide '<json_data>'
```
**参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `<json_data>` | string | 是 | 待定项 JSON 数据,需用引号包裹 |
**输入数据格式**
`aide-program/docs/formats/data.md` 的"待定项数据格式"章节。
**成功输出**
```
→ Web 服务已启动
→ 请访问: http://localhost:3721
→ 等待用户完成决策...
```
服务关闭后:
```
✓ 决策已完成
```
**错误输出**
```
✗ JSON 解析失败: <具体错误>
建议: 检查 JSON 格式是否正确
```
```
✗ 数据验证失败: <具体错误>
建议: 检查必填字段是否完整
```
```
✗ 无法启动服务: 端口 3721-3730 均被占用
建议: 关闭占用端口的程序,或在配置中指定其他端口
```
**行为流程**
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:接收 JSON 参数;
:解析 JSON;
if (解析成功?) then (是)
else (否)
:输出错误: JSON 解析失败;
stop
endif
:验证数据格式;
if (验证通过?) then (是)
else (否)
:输出错误: 数据验证失败;
stop
endif
:保存到 pending.json;
:探测可用端口;
if (找到可用端口?) then (是)
else (否)
:输出错误: 端口均被占用;
stop
endif
:启动 HTTP 服务;
:输出访问链接;
:阻塞等待用户操作;
:用户提交决策;
:保存决策结果;
:关闭服务;
:输出: 决策已完成;
stop
@enduml
```
### 2.2 aide decide result获取决策结果
**语法**
```
aide decide result
```
**参数**:无
**成功输出**
直接输出 JSON 格式的决策结果(便于 LLM 解析):
```json
{
"decisions": [
{"id": 1, "chosen": "option_a"},
{"id": 2, "chosen": "option_b", "note": "用户的补充说明"}
]
}
```
**错误输出**
```
✗ 尚无决策结果
建议: 请等待用户在 Web 界面完成操作
```
```
✗ 未找到待定项数据
建议: 请先执行 aide decide '<json>'
```
```
✗ 决策结果已过期
建议: 请重新执行 aide decide '<json>'
```
**行为流程**
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:检查 pending.json 是否存在;
if (存在?) then (是)
else (否)
:输出错误: 未找到待定项数据;
stop
endif
:查找最新决策记录;
if (存在决策记录?) then (是)
else (否)
:输出错误: 尚无决策结果;
stop
endif
:验证决策记录与 pending 匹配;
if (匹配?) then (是)
else (否)
:输出错误: 决策结果已过期;
stop
endif
:输出 JSON 结果;
stop
@enduml
```
## 三、参数校验规则
### 3.1 JSON 数据校验
**必填字段**
| 字段 | 类型 | 校验规则 |
|------|------|----------|
| `task` | string | 非空字符串 |
| `source` | string | 非空字符串 |
| `items` | array | 非空数组,至少包含 1 个待定项 |
**待定项DecideItem校验**
| 字段 | 类型 | 校验规则 |
|------|------|----------|
| `id` | number | 正整数,在 items 中唯一 |
| `title` | string | 非空字符串 |
| `options` | array | 非空数组,至少包含 2 个选项 |
**选项Option校验**
| 字段 | 类型 | 校验规则 |
|------|------|----------|
| `value` | string | 非空字符串,在同一 item 的 options 中唯一 |
| `label` | string | 非空字符串 |
**可选字段**
| 字段 | 类型 | 默认值 |
|------|------|--------|
| `location` | object | null |
| `context` | string | null |
| `recommend` | string | null若提供必须是 options 中某个 value |
| `score` | number | null若提供范围 0-100 |
| `pros` | array | null |
| `cons` | array | null |
### 3.2 校验错误信息
校验失败时,错误信息应明确指出:
1. 哪个字段出错
2. 期望的格式/值
3. 实际收到的值(如适用)
**示例**
```
✗ 数据验证失败: items[0].options 至少需要 2 个选项,当前只有 1 个
建议: 为每个待定项提供至少 2 个可选方案
```
```
✗ 数据验证失败: items[1].recommend 值 "invalid" 不在 options 中
建议: recommend 必须是 options 中某个选项的 value
```
## 四、输出规范
### 4.1 静默原则
- 成功时输出必要的状态信息(访问链接、完成提示)
- 错误时输出详细的错误信息和建议
- `aide decide result` 成功时仅输出 JSON便于程序解析
### 4.2 固定前缀
沿用 `aide-program/docs/README.md` 的输出规范:
| 前缀 | 函数 | 用途 |
|------|------|------|
| `✓` | `output.ok()` | 成功 |
| `✗` | `output.err()` | 失败 |
| `→` | `output.info()` | 进行中/信息 |
### 4.3 JSON 输出格式
`aide decide result` 的 JSON 输出:
- 使用 UTF-8 编码
- 紧凑格式(无缩进),便于程序解析
- 输出到 stdout错误信息输出到 stderr
## 五、退出码
| 退出码 | 含义 |
|-------:|------|
| 0 | 成功 |
| 1 | 失败(参数错误、校验失败、服务启动失败等) |
## 六、典型调用序列
### 6.1 正常流程
```bash
# 1. LLM 提交待定项数据
$ aide decide '{"task":"实现用户认证","source":"task.md","items":[...]}'
→ Web 服务已启动
→ 请访问: http://localhost:3721
→ 等待用户完成决策...
✓ 决策已完成
# 2. LLM 获取决策结果
$ aide decide result
{"decisions":[{"id":1,"chosen":"jwt"},{"id":2,"chosen":"bcrypt"}]}
```
### 6.2 服务超时
```bash
# 配置了超时时间的情况
$ aide decide '{"task":"...","source":"...","items":[...]}'
→ Web 服务已启动
→ 请访问: http://localhost:3721
→ 等待用户完成决策...
⚠ 服务超时,已自动关闭
$ aide decide result
✗ 尚无决策结果
建议: 请等待用户在 Web 界面完成操作
```
### 6.3 与 /aide:prep 的集成
```
/aide:prep 流程中:
1. LLM 分析任务,识别待定项
2. LLM 构造 JSON 数据
3. LLM 调用 aide decide '<json>'
4. LLM 告知用户访问链接
5. 用户在 Web 界面完成决策
6. LLM 调用 aide decide result 获取结果
7. LLM 根据决策结果继续任务
```
## 七、方法签名原型
```
# CLI 入口(在 main.py 中)
def cmd_decide(args: list[str]) -> int:
"""
处理 aide decide 命令
args[0] 为 JSON 数据或 "result"
返回退出码
"""
def cmd_decide_submit(json_data: str) -> int:
"""
提交待定项数据并启动服务
1. 解析并验证 JSON
2. 保存到 pending.json
3. 启动 HTTP 服务
4. 等待用户操作
5. 返回退出码
"""
def cmd_decide_result() -> int:
"""
获取决策结果
1. 检查 pending.json
2. 查找最新决策记录
3. 验证匹配性
4. 输出 JSON 结果
5. 返回退出码
"""
```
## 八、相关文档
- [decide 详细设计入口](README.md)
- [HTTP 服务设计](server.md)
- [数据存储设计](storage.md)
- [数据格式文档](../../formats/data.md)

View File

@@ -0,0 +1,472 @@
# aide decide HTTP 服务设计
## 一、概述
aide decide 使用内置 HTTP 服务器提供 Web 界面,供用户确认待定项。本文档定义服务器的设计规格。
### 1.1 技术选型
- 使用 Python 标准库 `http.server`
- 无需额外依赖
- 单线程阻塞模式(简化实现,满足单用户场景)
### 1.2 服务职责
| 职责 | 说明 |
|------|------|
| 静态资源服务 | 提供 HTML/CSS/JS 文件 |
| API 服务 | 提供数据获取和提交接口 |
| 生命周期管理 | 启动、等待、关闭 |
## 二、服务生命周期
### 2.1 状态机
```
@startuml
skinparam defaultFontName "PingFang SC"
[*] --> 初始化 : 调用 start()
初始化 --> 端口探测 : 配置加载完成
端口探测 --> 启动失败 : 无可用端口
端口探测 --> 运行中 : 找到可用端口
启动失败 --> [*]
运行中 --> 关闭中 : 收到提交/超时/中断
关闭中 --> 已关闭
已关闭 --> [*]
@enduml
```
### 2.2 启动流程
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:加载配置;
note right: decide.port, decide.timeout
:确定起始端口;
if (配置了固定端口?) then (是)
:使用配置端口;
else (否)
:使用默认端口 3721;
endif
:端口探测循环;
repeat
:尝试绑定端口;
if (绑定成功?) then (是)
:记录实际端口;
break
else (否)
:端口 += 1;
endif
repeat while (尝试次数 < 10?)
if (找到可用端口?) then (是)
else (否)
:返回错误;
stop
endif
:创建 HTTP 服务器;
:注册请求处理器;
:输出访问链接;
:进入服务循环;
stop
@enduml
```
### 2.3 关闭流程
服务在以下情况下关闭:
| 触发条件 | 处理方式 |
|----------|----------|
| 用户提交决策 | 正常关闭,返回成功 |
| 超时(若配置) | 正常关闭,返回警告 |
| 键盘中断Ctrl+C | 正常关闭,返回中断 |
| 异常错误 | 异常关闭,返回错误 |
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:收到关闭信号;
:停止接受新请求;
:等待当前请求完成;
note right: 最多等待 5 秒
:关闭 socket;
:清理资源;
:返回关闭原因;
stop
@enduml
```
## 三、端口配置
### 3.1 端口探测策略
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `decide.port` | 3721 | 起始端口 |
| 最大尝试次数 | 10 | 从起始端口开始尝试 |
**探测逻辑**
1.`decide.port` 开始
2. 尝试绑定端口
3. 若失败,尝试下一个端口
4. 最多尝试 10 次
5. 全部失败则返回错误
### 3.2 端口占用检测
```
check_port_available(port: int) -> bool:
"""
检查端口是否可用
1. 创建 socket
2. 尝试绑定到 127.0.0.1:port
3. 成功则端口可用,关闭 socket 返回 True
4. 失败则端口被占用,返回 False
"""
```
## 四、API 端点设计
### 4.1 端点一览
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/` | 返回主页面index.html |
| GET | `/style.css` | 返回样式文件 |
| GET | `/app.js` | 返回脚本文件 |
| GET | `/api/items` | 获取待定项数据 |
| POST | `/api/submit` | 提交决策结果 |
### 4.2 GET /api/items
**请求**:无参数
**响应**
成功200
```json
{
"task": "实现用户认证模块",
"source": "task-now.md",
"items": [
{
"id": 1,
"title": "认证方式选择",
"context": "任务描述中未明确指定认证方式",
"options": [
{"value": "jwt", "label": "JWT Token 认证", "score": 85, "pros": [...], "cons": [...]},
{"value": "session", "label": "Session 认证", "score": 70, "pros": [...], "cons": [...]}
],
"recommend": "jwt"
}
]
}
```
失败500
```json
{
"error": "无法读取待定项数据",
"detail": "文件不存在或格式错误"
}
```
### 4.3 POST /api/submit
**请求**
Content-Type: `application/json`
```json
{
"decisions": [
{"id": 1, "chosen": "jwt"},
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
]
}
```
**响应**
成功200
```json
{
"success": true,
"message": "决策已保存"
}
```
校验失败400
```json
{
"error": "决策数据无效",
"detail": "缺少待定项 2 的决策"
}
```
服务器错误500
```json
{
"error": "保存失败",
"detail": "无法写入文件"
}
```
**提交后行为**
1. 验证决策数据完整性(所有待定项都有决策)
2. 保存决策结果到历史记录
3. 设置关闭标志
4. 返回成功响应
5. 服务器在响应发送后关闭
### 4.4 静态资源服务
| 路径 | 文件 | Content-Type |
|------|------|--------------|
| `/` | `web/index.html` | `text/html; charset=utf-8` |
| `/style.css` | `web/style.css` | `text/css; charset=utf-8` |
| `/app.js` | `web/app.js` | `application/javascript; charset=utf-8` |
**资源加载方式**
- 方案A从文件系统读取开发时便于修改
- 方案B嵌入 Python 代码(部署时无需额外文件)
建议使用方案A资源文件放在 `aide/decide/web/` 目录下。
## 五、请求处理
### 5.1 请求处理流程
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:接收 HTTP 请求;
:解析请求路径和方法;
switch (路径)
case (/)
:返回 index.html;
case (/style.css)
:返回 style.css;
case (/app.js)
:返回 app.js;
case (/api/items)
if (方法 == GET?) then (是)
:读取 pending.json;
:返回 JSON 数据;
else (否)
:返回 405 Method Not Allowed;
endif
case (/api/submit)
if (方法 == POST?) then (是)
:解析请求体;
:验证决策数据;
:保存决策结果;
:设置关闭标志;
:返回成功响应;
else (否)
:返回 405 Method Not Allowed;
endif
case (其他)
:返回 404 Not Found;
endswitch
stop
@enduml
```
### 5.2 CORS 处理
由于前端和后端在同一服务器,通常不需要 CORS。但为了开发调试方便建议添加以下响应头
```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
```
### 5.3 错误处理
| HTTP 状态码 | 场景 |
|-------------|------|
| 200 | 请求成功 |
| 400 | 请求数据无效 |
| 404 | 路径不存在 |
| 405 | 方法不允许 |
| 500 | 服务器内部错误 |
## 六、超时处理
### 6.1 配置项
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `decide.timeout` | 0 | 超时时间0 表示无超时 |
### 6.2 超时实现
```
@startuml
skinparam defaultFontName "PingFang SC"
start
fork
:服务循环;
:处理请求;
fork again
:超时计时器;
:等待 timeout 秒;
:设置超时标志;
end fork
if (超时?) then (是)
:关闭服务;
:输出警告;
else (否)
:正常关闭;
endif
stop
@enduml
```
**注意**:由于使用单线程模型,超时检测需要在请求处理间隙进行,或使用 `select` 实现非阻塞等待。
## 七、方法签名原型
```
class DecideServer:
"""HTTP 服务器"""
root: Path # 项目根目录
port: int # 实际使用的端口
timeout: int # 超时时间(秒)
pending_path: Path # pending.json 路径
web_dir: Path # 静态资源目录
should_close: bool # 关闭标志
close_reason: str # 关闭原因
def __init__(self, root: Path) -> None:
"""初始化服务器"""
def start(self) -> bool:
"""
启动服务器
1. 加载配置
2. 探测可用端口
3. 创建 HTTP 服务器
4. 输出访问链接
5. 进入服务循环
6. 返回是否成功完成
"""
def stop(self, reason: str) -> None:
"""
停止服务器
1. 设置关闭标志和原因
2. 关闭 socket
"""
def _find_available_port(self) -> int | None:
"""
探测可用端口
从配置端口开始,最多尝试 10 次
返回可用端口或 None
"""
def _serve_forever(self) -> None:
"""
服务循环
处理请求直到 should_close 为 True
"""
class DecideHandler:
"""请求处理器"""
server: DecideServer # 服务器引用
def handle_request(self, method: str, path: str, body: bytes) -> Response:
"""
处理请求
根据 method 和 path 分发到具体处理函数
"""
def handle_index(self) -> Response:
"""返回主页面"""
def handle_static(self, filename: str) -> Response:
"""返回静态资源"""
def handle_get_items(self) -> Response:
"""处理 GET /api/items"""
def handle_submit(self, body: bytes) -> Response:
"""处理 POST /api/submit"""
Response = tuple[int, dict[str, str], bytes]
# (状态码, 响应头, 响应体)
```
## 八、安全考虑
### 8.1 绑定地址
- 默认绑定到 `127.0.0.1`(仅本地访问)
- 不绑定到 `0.0.0.0`(避免外部访问)
### 8.2 输入验证
- 验证 JSON 格式
- 验证决策数据完整性
- 限制请求体大小(建议 1MB
### 8.3 路径遍历防护
- 静态资源只从 `web/` 目录提供
- 不允许 `..` 路径
## 九、相关文档
- [decide 详细设计入口](README.md)
- [CLI 规格](cli.md)
- [Web 前端设计](web.md)
- [数据存储设计](storage.md)

View File

@@ -0,0 +1,383 @@
# aide decide 数据存储设计
## 一、概述
aide decide 的数据存储负责管理待定项数据和决策记录的持久化。
### 1.1 存储位置
所有数据存储在项目根目录的 `.aide/decisions/` 下:
```
.aide/
└── decisions/
├── pending.json # 当前待处理的待定项
└── 2025-01-15T10-30-00.json # 历史决策记录
```
### 1.2 数据格式规范
数据格式以 `aide-program/docs/formats/data.md` 为准,本文档补充存储相关的实现细节。
## 二、文件说明
### 2.1 pending.json
**用途**:存储当前待处理的待定项数据
**生命周期**
- 创建:`aide decide '<json>'` 执行时
- 读取Web 前端通过 API 获取
- 保留:决策完成后保留,用于 `aide decide result` 验证匹配性
- 覆盖:下次 `aide decide '<json>'` 执行时覆盖
**内容格式**与输入数据格式相同DecideInput
```json
{
"task": "实现用户认证模块",
"source": "task-now.md",
"items": [...],
"_meta": {
"created_at": "2025-01-15T10:30:00+08:00",
"session_id": "2025-01-15T10-30-00"
}
}
```
**元数据字段**`_meta`
- `created_at`创建时间ISO 8601 格式)
- `session_id`:会话标识(用于匹配决策记录)
### 2.2 历史决策记录
**文件名格式**`{session_id}.json`
**示例**`2025-01-15T10-30-00.json`
**内容格式**
```json
{
"input": {
"task": "实现用户认证模块",
"source": "task-now.md",
"items": [...]
},
"output": {
"decisions": [
{"id": 1, "chosen": "jwt"},
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
]
},
"completed_at": "2025-01-15T10:35:00+08:00"
}
```
## 三、存储操作
### 3.1 保存待定项数据
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:接收 DecideInput 数据;
:生成 session_id;
note right: 格式: YYYY-MM-DDTHH-mm-ss
:添加 _meta 字段;
:确保 .aide/decisions/ 目录存在;
:写入 pending.json;
note right: 原子写入
stop
@enduml
```
### 3.2 保存决策结果
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:接收 DecideOutput 数据;
:读取 pending.json;
:提取 session_id;
:构造 DecisionRecord;
note right: input + output + completed_at
:写入 {session_id}.json;
note right: 原子写入
stop
@enduml
```
### 3.3 读取决策结果
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:检查 pending.json 是否存在;
if (存在?) then (是)
else (否)
:返回错误: 未找到待定项数据;
stop
endif
:读取 pending.json;
:提取 session_id;
:构造历史记录文件名;
note right: {session_id}.json
:检查历史记录是否存在;
if (存在?) then (是)
else (否)
:返回错误: 尚无决策结果;
stop
endif
:读取历史记录;
:返回 output.decisions;
stop
@enduml
```
## 四、并发与原子性
### 4.1 原子写入
为避免写入过程中断导致文件损坏,必须使用原子写入:
1. 写入临时文件:`{filename}.tmp`
2. 确保写入完成fsync如实现选择支持
3. 原子重命名为目标文件
```
save_atomic(path: Path, data: dict) -> None:
"""
原子写入 JSON 文件
1. 序列化为 JSON 字符串
2. 写入 {path}.tmp
3. 重命名为 {path}
"""
```
### 4.2 并发控制
由于 aide decide 是单用户场景,且同一时间只有一个会话,通常不需要复杂的并发控制。
但为了安全,建议:
- 写入前检查文件是否被其他进程占用
- 使用文件锁(可选)
### 4.3 编码规范
- 文件编码UTF-8
- 换行符:`\n`Unix 风格)
- JSON 格式:缩进 2 空格,便于人工阅读
## 五、生命周期管理
### 5.1 文件生命周期
| 文件 | 创建时机 | 删除时机 |
|------|----------|----------|
| pending.json | `aide decide '<json>'` | 不自动删除,下次覆盖 |
| {session_id}.json | 用户提交决策 | 不自动删除 |
### 5.2 历史记录清理
历史记录不自动清理,由用户手动管理。
建议的清理策略(可作为后续功能):
- 保留最近 N 条记录
- 保留最近 N 天的记录
- 提供 `aide decide clean` 命令
### 5.3 目录初始化
首次使用时,需要确保目录存在:
```
ensure_decisions_dir(root: Path) -> Path:
"""
确保 .aide/decisions/ 目录存在
1. 检查 .aide/ 是否存在
2. 若不存在,返回错误(需要先执行 aide init
3. 创建 decisions/ 子目录(如不存在)
4. 返回目录路径
"""
```
## 六、容错与恢复
### 6.1 JSON 解析失败
当文件存在但无法解析时:
```
✗ 无法解析 pending.json: <具体错误>
建议: 文件可能已损坏,请重新执行 aide decide '<json>'
```
### 6.2 文件缺失
| 场景 | 错误信息 |
|------|----------|
| .aide/ 不存在 | `✗ .aide 目录不存在,请先执行 aide init` |
| pending.json 不存在 | `✗ 未找到待定项数据,请先执行 aide decide '<json>'` |
| 历史记录不存在 | `✗ 尚无决策结果,请等待用户完成操作` |
### 6.3 数据不一致
当 pending.json 和历史记录的 session_id 不匹配时:
```
✗ 决策结果已过期
建议: pending.json 已被更新,请重新执行 aide decide '<json>'
```
## 七、方法签名原型
```
class DecideStorage:
"""决策数据存储管理"""
root: Path # 项目根目录
decisions_dir: Path # .aide/decisions/ 目录
pending_path: Path # pending.json 路径
def __init__(self, root: Path) -> None:
"""
初始化存储管理器
1. 设置路径
2. 确保目录存在
"""
def save_pending(self, data: DecideInput) -> str:
"""
保存待定项数据
1. 生成 session_id
2. 添加 _meta 字段
3. 原子写入 pending.json
4. 返回 session_id
"""
def load_pending(self) -> DecideInput | None:
"""
加载待定项数据
返回 DecideInput 或 None文件不存在
"""
def get_session_id(self) -> str | None:
"""
获取当前会话 ID
从 pending.json 的 _meta.session_id 读取
"""
def save_result(self, output: DecideOutput) -> None:
"""
保存决策结果
1. 读取 pending.json 获取 input 和 session_id
2. 构造 DecisionRecord
3. 原子写入 {session_id}.json
"""
def load_result(self) -> DecideOutput | None:
"""
加载决策结果
1. 获取 session_id
2. 读取 {session_id}.json
3. 返回 output 部分
"""
def has_pending(self) -> bool:
"""检查是否有待处理的待定项"""
def has_result(self) -> bool:
"""检查是否有决策结果"""
def _ensure_dir(self) -> None:
"""确保目录存在"""
def _save_atomic(self, path: Path, data: dict) -> None:
"""原子写入 JSON 文件"""
def _load_json(self, path: Path) -> dict | None:
"""加载 JSON 文件"""
# 数据类型定义(与 types.py 一致)
DecideInput:
task: str
source: str
items: list[DecideItem]
_meta: MetaInfo | None
MetaInfo:
created_at: str
session_id: str
DecideOutput:
decisions: list[Decision]
DecisionRecord:
input: DecideInput
output: DecideOutput
completed_at: str
```
## 八、与其他模块的关系
### 8.1 被调用方
| 调用方 | 调用的方法 |
|--------|------------|
| CLI (cmd_decide_submit) | save_pending() |
| CLI (cmd_decide_result) | load_result(), has_pending(), has_result() |
| Server (handle_get_items) | load_pending() |
| Server (handle_submit) | save_result() |
### 8.2 依赖
| 依赖项 | 用途 |
|--------|------|
| core/config.py | 读取项目根目录 |
| json | JSON 序列化/反序列化 |
| pathlib | 路径操作 |
| datetime | 时间戳生成 |
## 九、相关文档
- [decide 详细设计入口](README.md)
- [CLI 规格](cli.md)
- [HTTP 服务设计](server.md)
- [数据格式文档](../../formats/data.md)

View File

@@ -0,0 +1,528 @@
# 验证清单(实现完成后的自检)
本清单用于在实现 `aide decide` 后进行验证,确保行为符合设计文档与 plugin 契约。
## 一、准备条件
- 已执行 `aide init`,确保 `.aide/` 目录存在
- 准备测试用的 JSON 数据(见附录)
- 确保端口 3721 未被占用(或准备测试端口探测)
## 二、CLI 用例
### 2.1 正常提交待定项
**步骤**
1. 执行 `aide decide '<valid_json>'`
**期望**
- 输出包含 `→ Web 服务已启动`
- 输出包含 `→ 请访问: http://localhost:3721`(或其他端口)
- 输出包含 `→ 等待用户完成决策...`
- `.aide/decisions/pending.json` 被创建
- 服务阻塞等待
### 2.2 JSON 解析失败
**步骤**
1. 执行 `aide decide 'invalid json'`
**期望**
- 输出 `✗ JSON 解析失败: ...`
- 退出码为 1
- 不创建 pending.json
- 不启动服务
### 2.3 数据验证失败
**步骤**
1. 执行 `aide decide '{"task":"test"}'`(缺少必填字段)
**期望**
- 输出 `✗ 数据验证失败: ...`
- 错误信息明确指出缺少的字段
- 退出码为 1
### 2.4 获取决策结果(无数据)
**步骤**
1. 确保 `.aide/decisions/` 为空
2. 执行 `aide decide result`
**期望**
- 输出 `✗ 未找到待定项数据`
- 退出码为 1
### 2.5 获取决策结果(有 pending 无 result
**步骤**
1. 执行 `aide decide '<json>'` 并立即中断Ctrl+C
2. 执行 `aide decide result`
**期望**
- 输出 `✗ 尚无决策结果`
- 退出码为 1
### 2.6 获取决策结果(正常)
**步骤**
1. 完成一次完整的决策流程
2. 执行 `aide decide result`
**期望**
- 输出 JSON 格式的决策结果
- JSON 可被正确解析
- 包含所有待定项的决策
- 退出码为 0
## 三、HTTP 服务用例
### 3.1 端口探测
**步骤**
1. 占用端口 3721`nc -l 3721`
2. 执行 `aide decide '<json>'`
**期望**
- 服务在 3722 端口启动
- 输出显示正确的端口号
### 3.2 端口全部被占用
**步骤**
1. 占用端口 3721-3730
2. 执行 `aide decide '<json>'`
**期望**
- 输出 `✗ 无法启动服务: 端口 3721-3730 均被占用`
- 退出码为 1
### 3.3 GET /api/items
**步骤**
1. 启动服务
2. 使用 curl 访问 `http://localhost:3721/api/items`
**期望**
- 返回 200 状态码
- Content-Type 为 `application/json`
- 返回的 JSON 与 pending.json 内容一致
### 3.4 POST /api/submit正常
**步骤**
1. 启动服务
2. 使用 curl 提交决策:
```bash
curl -X POST http://localhost:3721/api/submit \
-H "Content-Type: application/json" \
-d '{"decisions":[{"id":1,"chosen":"jwt"}]}'
```
**期望**
- 返回 200 状态码
- 返回 `{"success":true,"message":"决策已保存"}`
- 服务关闭
- 历史记录文件被创建
### 3.5 POST /api/submit数据不完整
**步骤**
1. 启动服务(待定项有 2 个)
2. 提交只包含 1 个决策的数据
**期望**
- 返回 400 状态码
- 返回错误信息指出缺少的决策
- 服务不关闭
### 3.6 静态资源服务
**步骤**
1. 启动服务
2. 访问 `http://localhost:3721/`
3. 访问 `http://localhost:3721/style.css`
4. 访问 `http://localhost:3721/app.js`
**期望**
- 所有资源返回 200 状态码
- Content-Type 正确
- 内容非空
### 3.7 404 处理
**步骤**
1. 启动服务
2. 访问 `http://localhost:3721/nonexistent`
**期望**
- 返回 404 状态码
### 3.8 服务中断Ctrl+C
**步骤**
1. 启动服务
2. 按 Ctrl+C
**期望**
- 服务正常关闭
- 无异常输出
- 退出码为 0 或 130SIGINT
## 四、Web 前端用例
### 4.1 页面加载
**步骤**
1. 启动服务
2. 在浏览器中打开页面
**期望**
- 页面正常渲染
- 显示任务名称和来源
- 显示所有待定项
- 提交按钮为禁用状态
### 4.2 选项选择
**步骤**
1. 点击某个选项
**期望**
- 选项被选中(视觉反馈)
- 进度更新(如 "已完成 1/2 项"
- 若所有项已选择,提交按钮启用
### 4.3 推荐选项显示
**步骤**
1. 提交包含 recommend 字段的数据
2. 查看页面
**期望**
- 推荐选项有特殊标记
- 卡片头部显示推荐信息
### 4.4 备注输入
**步骤**
1. 在备注框中输入文字
2. 提交决策
3. 查看决策结果
**期望**
- 备注内容被保存
- 决策结果中包含 note 字段
### 4.5 提交决策
**步骤**
1. 选择所有待定项的选项
2. 点击提交按钮
**期望**
- 显示提交中状态
- 提交成功后显示成功提示
- 页面提示可以关闭
### 4.6 提交失败处理
**步骤**
1. 模拟服务端错误(如断开网络)
2. 点击提交按钮
**期望**
- 显示错误提示
- 提交按钮恢复可用
- 可以重试
### 4.7 浏览器兼容性
**步骤**
1. 在 Chrome、Firefox、Safari、Edge 中分别测试
**期望**
- 页面正常渲染
- 交互功能正常
- 无 JavaScript 错误
## 五、数据存储用例
### 5.1 pending.json 创建
**步骤**
1. 执行 `aide decide '<json>'`
2. 检查 `.aide/decisions/pending.json`
**期望**
- 文件存在
- JSON 格式正确
- 包含 `_meta` 字段
- `_meta.session_id` 格式正确
### 5.2 历史记录创建
**步骤**
1. 完成一次决策流程
2. 检查 `.aide/decisions/` 目录
**期望**
- 存在 `{session_id}.json` 文件
- 文件包含 input、output、completed_at 字段
- session_id 与 pending.json 中的一致
### 5.3 pending.json 覆盖
**步骤**
1. 执行 `aide decide '<json1>'`
2. 中断服务
3. 执行 `aide decide '<json2>'`
4. 检查 pending.json
**期望**
- pending.json 内容为 json2
- session_id 已更新
### 5.4 原子写入验证
**步骤**
1. 在写入过程中模拟中断
2. 检查文件状态
**期望**
- 文件要么是完整的旧内容,要么是完整的新内容
- 不会出现部分写入的损坏状态
## 六、端到端用例
### 6.1 完整流程
**步骤**
1. 执行 `aide decide '<json>'`
2. 在浏览器中打开链接
3. 选择所有选项
4. 添加备注
5. 点击提交
6. 执行 `aide decide result`
**期望**
- 整个流程无错误
- 决策结果正确反映用户选择
- 备注内容正确保存
### 6.2 与 /aide:prep 集成
**步骤**
1. 模拟 LLM 调用流程
2. 构造待定项 JSON
3. 调用 aide decide
4. 完成决策
5. 获取结果
**期望**
- 流程符合 plugin 契约
- 输出格式便于 LLM 解析
## 七、异常用例
### 7.1 .aide 目录不存在
**步骤**
1. 删除 .aide 目录
2. 执行 `aide decide '<json>'`
**期望**
- 输出 `✗ .aide 目录不存在,请先执行 aide init`
- 退出码为 1
### 7.2 pending.json 损坏
**步骤**
1. 手动写入无效 JSON 到 pending.json
2. 执行 `aide decide result`
**期望**
- 输出解析错误信息
- 建议重新执行 aide decide
### 7.3 超大 JSON 数据
**步骤**
1. 构造包含 100 个待定项的 JSON
2. 执行 `aide decide '<json>'`
**期望**
- 正常处理或给出合理的限制提示
- 不会导致内存溢出或崩溃
### 7.4 特殊字符处理
**步骤**
1. 在 task、title、note 中包含特殊字符中文、emoji、引号、换行
2. 完成决策流程
**期望**
- 特殊字符正确保存和显示
- 不会导致 JSON 解析错误
## 八、附录:测试数据
### 8.1 最小有效数据
```json
{
"task": "测试任务",
"source": "test.md",
"items": [
{
"id": 1,
"title": "测试问题",
"options": [
{"value": "a", "label": "选项A"},
{"value": "b", "label": "选项B"}
]
}
]
}
```
### 8.2 完整数据
```json
{
"task": "实现用户认证模块",
"source": "task-now.md",
"items": [
{
"id": 1,
"title": "认证方式选择",
"location": {
"file": "task-now.md",
"start": 5,
"end": 7
},
"context": "任务描述中未明确指定认证方式,需要确认",
"options": [
{
"value": "jwt",
"label": "JWT Token 认证",
"score": 85,
"pros": ["无状态", "易于扩展", "跨域友好"],
"cons": ["Token 无法主动失效", "需要处理刷新"]
},
{
"value": "session",
"label": "Session 认证",
"score": 70,
"pros": ["实现简单", "可主动失效"],
"cons": ["需要存储", "扩展性差"]
}
],
"recommend": "jwt"
},
{
"id": 2,
"title": "密码加密算法",
"context": "选择密码存储的加密算法",
"options": [
{
"value": "bcrypt",
"label": "bcrypt",
"score": 90,
"pros": ["安全性高", "自带盐值"],
"cons": ["计算较慢"]
},
{
"value": "argon2",
"label": "Argon2",
"score": 95,
"pros": ["最新标准", "抗GPU攻击"],
"cons": ["库支持较少"]
}
],
"recommend": "bcrypt"
}
]
}
```
### 8.3 期望的决策结果
```json
{
"decisions": [
{"id": 1, "chosen": "jwt"},
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
]
}
```
## 九、相关文档
- [decide 详细设计入口](README.md)
- [CLI 规格](cli.md)
- [HTTP 服务设计](server.md)
- [Web 前端设计](web.md)
- [数据存储设计](storage.md)

View File

@@ -0,0 +1,560 @@
# aide decide Web 前端设计
## 一、概述
aide decide 的 Web 前端提供待定项确认界面,供用户选择选项并提交决策。
### 1.1 技术选型
| 技术 | 选择 | 理由 |
|------|------|------|
| 框架 | 纯 HTML/CSS/JS | 无需构建工具,无 Node.js 依赖 |
| 样式 | 原生 CSS | 简单场景无需预处理器 |
| 交互 | 原生 JavaScript | 避免引入框架依赖 |
### 1.2 设计原则
- **简洁**:聚焦核心功能,无多余装饰
- **清晰**:信息层次分明,操作明确
- **可用**:支持主流浏览器,无障碍友好
## 二、页面结构
### 2.1 整体布局
```
┌─────────────────────────────────────────────────────────────┐
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 页面头部 │ │
│ │ 标题: Aide 待定项确认 │ │
│ │ 任务: <task> │ │
│ │ 来源: <source> │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 待定项列表 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ 待定项卡片 1 │ │ │
│ │ │ ... │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ 待定项卡片 2 │ │ │
│ │ │ ... │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 页面底部 │ │
│ │ [提交决策] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 待定项卡片结构
```
┌─────────────────────────────────────────────────────────────┐
│ 1. <title> [推荐: xxx] │
├─────────────────────────────────────────────────────────────┤
│ <context> │
│ │
│ 位置: <file>:<start>-<end> │
├─────────────────────────────────────────────────────────────┤
│ 选项: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ○ <option_a.label> 评分: 85 │ │
│ │ 优点: xxx, xxx │ │
│ │ 缺点: xxx │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ● <option_b.label> ← 已选中 评分: 70│ │
│ │ 优点: xxx │ │
│ │ 缺点: xxx, xxx │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ 备注(可选): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 三、HTML 结构原型
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aide 待定项确认</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<!-- 页面头部 -->
<header class="header">
<h1>Aide 待定项确认</h1>
<div class="task-info">
<p><strong>任务:</strong> <span id="task-name"></span></p>
<p><strong>来源:</strong> <span id="task-source"></span></p>
</div>
</header>
<!-- 待定项列表 -->
<main class="items-container" id="items-container">
<!-- 动态生成待定项卡片 -->
</main>
<!-- 页面底部 -->
<footer class="footer">
<div class="progress">
<span id="progress-text">已完成 0/0 项</span>
</div>
<button class="submit-btn" id="submit-btn" disabled>
提交决策
</button>
</footer>
</div>
<!-- 提交成功提示 -->
<div class="success-overlay" id="success-overlay" hidden>
<div class="success-message">
<h2>决策已提交</h2>
<p>您可以关闭此页面</p>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>
```
### 3.1 待定项卡片模板
```html
<article class="item-card" data-item-id="{id}">
<header class="item-header">
<h2 class="item-title">
<span class="item-number">{id}.</span>
{title}
</h2>
<span class="recommend-badge" data-show="{hasRecommend}">
推荐: {recommend}
</span>
</header>
<div class="item-context" data-show="{hasContext}">
{context}
</div>
<div class="item-location" data-show="{hasLocation}">
位置: {file}:{start}-{end}
</div>
<div class="options-list">
<!-- 选项列表 -->
</div>
<div class="item-note">
<label for="note-{id}">备注(可选):</label>
<textarea id="note-{id}" placeholder="添加补充说明..."></textarea>
</div>
</article>
```
### 3.2 选项模板
```html
<label class="option-item" data-value="{value}" data-recommended="{isRecommended}">
<input type="radio" name="item-{itemId}" value="{value}">
<div class="option-content">
<div class="option-header">
<span class="option-label">{label}</span>
<span class="option-score" data-show="{hasScore}">
评分: {score}
</span>
</div>
<div class="option-details" data-show="{hasDetails}">
<div class="option-pros" data-show="{hasPros}">
<strong>优点:</strong> {pros}
</div>
<div class="option-cons" data-show="{hasCons}">
<strong>缺点:</strong> {cons}
</div>
</div>
</div>
</label>
```
## 四、CSS 样式规范
### 4.1 设计变量
```css
:root {
/* 颜色 */
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-success: #16a34a;
--color-warning: #ca8a04;
--color-error: #dc2626;
--color-text: #1f2937;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
--color-background: #f9fafb;
--color-card: #ffffff;
/* 间距 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
/* 圆角 */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
/* 阴影 */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
/* 字体 */
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-size-sm: 14px;
--font-size-md: 16px;
--font-size-lg: 18px;
--font-size-xl: 24px;
}
```
### 4.2 关键样式类
| 类名 | 用途 |
|------|------|
| `.container` | 页面容器,最大宽度 800px居中 |
| `.header` | 页面头部 |
| `.item-card` | 待定项卡片 |
| `.item-card.completed` | 已选择的卡片(边框高亮) |
| `.option-item` | 选项容器 |
| `.option-item.selected` | 已选中的选项(背景高亮) |
| `.option-item[data-recommended="true"]` | 推荐选项(特殊标记) |
| `.submit-btn` | 提交按钮 |
| `.submit-btn:disabled` | 禁用状态(未完成所有选择) |
| `.success-overlay` | 提交成功遮罩 |
### 4.3 响应式设计
虽然主要面向桌面浏览器,但应保证基本的响应式支持:
```css
/* 小屏幕适配 */
@media (max-width: 640px) {
.container {
padding: var(--spacing-sm);
}
.item-card {
padding: var(--spacing-md);
}
.option-details {
flex-direction: column;
}
}
```
## 五、JavaScript 交互逻辑
### 5.1 应用状态
```javascript
const AppState = {
task: "", // 任务名称
source: "", // 来源文档
items: [], // 待定项列表
decisions: {}, // 当前决策 { itemId: chosenValue }
notes: {}, // 用户备注 { itemId: noteText }
isSubmitting: false // 提交中标志
};
```
### 5.2 核心函数
```javascript
/**
* 初始化应用
* 1. 从 API 加载数据
* 2. 渲染界面
* 3. 绑定事件
*/
async function init(): void
/**
* 加载待定项数据
* GET /api/items
*/
async function loadItems(): DecideInput
/**
* 渲染待定项列表
*/
function renderItems(data: DecideInput): void
/**
* 渲染单个待定项卡片
*/
function renderItemCard(item: DecideItem): HTMLElement
/**
* 渲染选项列表
*/
function renderOptions(item: DecideItem): HTMLElement
/**
* 处理选项选择
*/
function handleOptionSelect(itemId: number, value: string): void
/**
* 处理备注输入
*/
function handleNoteInput(itemId: number, note: string): void
/**
* 更新进度显示
*/
function updateProgress(): void
/**
* 检查是否可以提交
*/
function canSubmit(): boolean
/**
* 提交决策
* POST /api/submit
*/
async function submitDecisions(): void
/**
* 显示成功提示
*/
function showSuccess(): void
/**
* 显示错误提示
*/
function showError(message: string): void
```
### 5.3 事件绑定
```javascript
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
// 选项选择事件(事件委托)
document.getElementById('items-container').addEventListener('change', (e) => {
if (e.target.type === 'radio') {
const itemId = parseInt(e.target.name.replace('item-', ''));
const value = e.target.value;
handleOptionSelect(itemId, value);
}
});
// 备注输入事件(事件委托)
document.getElementById('items-container').addEventListener('input', (e) => {
if (e.target.tagName === 'TEXTAREA') {
const itemId = parseInt(e.target.id.replace('note-', ''));
handleNoteInput(itemId, e.target.value);
}
});
// 提交按钮点击
document.getElementById('submit-btn').addEventListener('click', submitDecisions);
```
### 5.4 交互流程
```
@startuml
skinparam defaultFontName "PingFang SC"
start
:页面加载;
:调用 init();
:GET /api/items;
if (加载成功?) then (是)
:渲染待定项列表;
:绑定事件监听;
else (否)
:显示错误信息;
stop
endif
repeat
:用户操作;
switch (操作类型)
case (选择选项)
:更新 decisions;
:高亮选中选项;
:更新进度;
case (输入备注)
:更新 notes;
case (点击提交)
if (所有项已选择?) then (是)
:禁用提交按钮;
:POST /api/submit;
if (提交成功?) then (是)
:显示成功提示;
stop
else (否)
:显示错误信息;
:恢复提交按钮;
endif
else (否)
:提示完成所有选择;
endif
endswitch
repeat while (继续操作?)
stop
@enduml
```
## 六、错误处理
### 6.1 加载错误
```javascript
async function loadItems() {
try {
const response = await fetch('/api/items');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
showError('无法加载待定项数据,请刷新页面重试');
throw error;
}
}
```
### 6.2 提交错误
```javascript
async function submitDecisions() {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(buildDecisionData())
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '提交失败');
}
showSuccess();
} catch (error) {
showError(`提交失败: ${error.message}`);
AppState.isSubmitting = false;
updateSubmitButton();
}
}
```
### 6.3 错误提示样式
```html
<div class="error-toast" id="error-toast" hidden>
<span class="error-icon"></span>
<span class="error-message" id="error-message"></span>
</div>
```
## 七、无障碍支持
### 7.1 语义化 HTML
- 使用 `<header>`, `<main>`, `<footer>`, `<article>` 等语义标签
- 使用 `<label>` 关联表单控件
- 使用 `<fieldset>``<legend>` 组织选项组
### 7.2 键盘导航
- 所有交互元素可通过 Tab 键访问
- 选项可通过方向键切换
- Enter 键可触发提交
### 7.3 ARIA 属性
```html
<main role="main" aria-label="待定项列表">
<article role="group" aria-labelledby="item-title-1">
<h2 id="item-title-1">...</h2>
<div role="radiogroup" aria-label="选项">
...
</div>
</article>
</main>
<button aria-disabled="true" aria-describedby="submit-hint">
提交决策
</button>
<span id="submit-hint" class="sr-only">
请先完成所有待定项的选择
</span>
```
## 八、浏览器兼容性
### 8.1 目标浏览器
| 浏览器 | 最低版本 |
|--------|----------|
| Chrome | 80+ |
| Firefox | 75+ |
| Safari | 13+ |
| Edge | 80+ |
### 8.2 避免使用的特性
- CSS Grid 子网格subgrid
- CSS 容器查询container queries
- JavaScript 可选链操作符(?.)在旧版本中不支持
- Top-level await
### 8.3 Polyfill 策略
不引入 polyfill通过避免新特性保证兼容性。
## 九、文件清单
```
aide/decide/web/
├── index.html # 主页面
├── style.css # 样式文件
└── app.js # 交互逻辑
```
## 十、相关文档
- [decide 详细设计入口](README.md)
- [HTTP 服务设计](server.md)
- [数据格式文档](../../formats/data.md)

View File

@@ -1,7 +1,8 @@
1. 首先阅读 @statements/optimize.md还有 @README.md 并根据其指引了解跟aide flow有关的所有信息 1. 首先阅读 @statements/optimize.md还有 @README.md 并根据其指引了解跟aide decide有关的所有信息
2. 我想对aide flow开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发, 2. 对aide decide开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发,
- 设计文档中要有一个用于指导接手人员的入口文档,其中要说明:需要了解哪些信息、需要做什么事
对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容,以此为确定目标和完整指导,开始目标程序的实际开发工作,直到构建产出程序和通过测试运行 对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容我将会打包aide-program目录以此为确定目标和完整指导aide-program/docs/ 目录下的相关文档),开始目标程序的实际开发工作,直到构建产出程序和通过测试运行运行aide decide -h 及其一系列命令与参数)
切记非常重要开发文档中不允许给出代码片段、实现代码最多只能出现类型与数据结构伪代码原型、方法签名、业务逻辑流程的plantuml流程图、API约定 切记非常重要开发文档中不允许给出代码片段、实现代码最多只能出现类型与数据结构伪代码原型、方法签名、业务逻辑流程的plantuml流程图、API约定