Files
agent-aide/aide-program/docs/commands/decide/storage.md
2025-12-15 02:59:03 +08:00

384 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 submit '<json>'` 执行时
- 读取Web 前端通过 API 获取
- 保留:决策完成后保留,用于 `aide decide result` 验证匹配性
- 覆盖:下次 `aide decide submit '<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 submit '<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 submit '<json>'
```
### 6.2 文件缺失
| 场景 | 错误信息 |
|------|----------|
| .aide/ 不存在 | `✗ .aide 目录不存在,请先执行 aide init` |
| pending.json 不存在 | `✗ 未找到待定项数据,请先执行 aide decide submit '<json>'` |
| 历史记录不存在 | `✗ 尚无决策结果,请等待用户完成操作` |
### 6.3 数据不一致
当 pending.json 和历史记录的 session_id 不匹配时:
```
✗ 决策结果已过期
建议: pending.json 已被更新,请重新执行 aide decide submit '<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)