feat: 对decide的功能进行调整

This commit is contained in:
2025-12-15 04:01:12 +08:00
parent ed7c45b48e
commit 3b07a8160a
11 changed files with 284 additions and 71 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "aide-plugin",
"description": "Aide 工作流体系插件,提供任务准备和执行的标准化流程",
"version": "1.0.0",
"version": "1.0.2",
"author": {
"name": "Aide Team"
},

View File

@@ -14,6 +14,14 @@ argument-hint: [任务细则文档路径]
---
## 前置准备
**首先触发 `aide` skill 学习 aide 命令的使用方法。**
这是必要步骤,确保你了解 `aide flow` 等命令的正确用法。
---
## 开始
### 确定任务细则

View File

@@ -7,6 +7,22 @@ argument-hint: [无参数]
你正在使用 Aide 工作流体系。本命令帮助你快速认知项目并确保环境就绪。
---
## 核心约束(必须遵守)
**禁止直接读取或编辑 `.aide/` 目录下的任何文件!**
- ❌ 禁止使用 Read 工具读取 `.aide/config.toml`
- ❌ 禁止使用 Edit/Write 工具修改 `.aide/` 下的文件
- ✅ 必须通过 `aide config get/set` 读写配置
- ✅ 必须通过 `aide env set` 修改环境配置
- ✅ 配置有误时,触发 `env-config` skill 学习正确的设置方法
**原因**`.aide/` 是 aide 程序的内部数据目录,直接操作可能导致格式错误或状态不一致。
---
## 执行步骤
### 1. Aide 运行时环境检测
@@ -31,7 +47,7 @@ aide init
此命令会:
- 创建 `.aide/` 目录(如不存在)
- 生成默认配置文件 `.aide/config.toml`
- 生成默认配置文件
- 检查并更新 `.gitignore`
### 3. 项目认知
@@ -51,40 +67,40 @@ aide init
aide env ensure
```
此命令会:
- 读取 `.aide/config.toml` 中的环境配置
- 检测并修复项目开发环境
- 输出环境状态和配置信息
根据输出处理:
- `✓`:环境就绪,继续到步骤 5
- `⚠`:有警告但可继续,记录并继续
- `✗`**触发 `env-config` skill**按指导完成配置后重试
- `✓`:环境就绪,**直接进入步骤 5**(不要再查看配置文件)
- `⚠`:有警告但可继续,记录警告信息并继续
- `✗`**触发 `env-config` skill**学习如何使用 `aide env set` 命令修复配置,然后重试
**注意**
- 此时不带 `--runtime` 参数,会读取项目配置文件检查项目环境
- aide env 会输出项目配置信息,包括默认的任务文档路径,记住这些信息供后续使用
**环境检测失败时的正确做法**
1. 触发 `env-config` skill 获取配置指导
2. 使用 `aide env set <模块>.<配置项> <值>` 修改配置
3. 重新执行 `aide env ensure` 验证
**错误做法**(禁止):
- 直接编辑 `.aide/config.toml` 文件
### 5. 汇报就绪状态
向用户汇报
环境检测通过后,直接向用户汇报(不要再读取配置文件)
```
项目概况:[来自步骤3的概要]
环境状态:✓ 环境就绪 (python:3.12, ...)
项目配置:
- 任务原文档task-now.md
- 任务细则task-spec.md
环境状态:[aide env ensure 的输出结果]
Aide 已就绪,可用命令:
- /aide:prep [文档路径] - 任务准备
- /aide:exec [文档路径] - 任务执行
```
> 注:任务文档路径等配置信息已在 aide env ensure 输出中显示,无需额外查看配置文件。
---
## 注意事项
1. 如果在初始化过程中发现严重环境问题无法解决,建议用户修复后重开对话
2. 本命令只做认知和环境准备,不修改任何业务代码
3. 所有输出使用简体中文
1. **不要读取 `.aide/` 目录下的文件**——所有配置操作通过 aide 命令完成
2. 如果在初始化过程中发现严重环境问题无法解决,建议用户修复后重开对话
3. 本命令只做认知和环境准备,不修改任何业务代码
4. 所有输出使用简体中文

View File

@@ -14,6 +14,14 @@ argument-hint: [任务原文档路径]
---
## 前置准备
**首先触发 `aide` skill 学习 aide 命令的使用方法。**
这是必要步骤,确保你了解 `aide flow` 等命令的正确用法。
---
## 开始
### 启动流程追踪
@@ -95,15 +103,15 @@ aide flow next-step "任务优化完成,生成待定项"
### 有待定项时
提交待定项数据:
1. 将待定项数据写入 JSON 文件(如 `.aide/pending-items.json`
2. 提交待定项数据:
```bash
aide decide submit '<json数据>'
aide decide submit .aide/pending-items.json
```
告知用户访问链接进行确认
用户完成后获取结果:
3. 告知用户访问输出的链接进行确认
4. 用户完成后获取结果:
```bash
aide decide result

View File

@@ -208,25 +208,31 @@ aide flow 会自动校验环节跳转是否合理:
## aide decide - 待定项确认
通过 Web 界面处理待定项确认。
通过 Web 界面处理待定项确认。服务在后台运行,用户完成决策后自动关闭。
```
aide decide {submit,result} ...
子命令:
submit <json> 提交待定项数据启动 Web 服务
submit <file> 从文件读取待定项数据启动后台 Web 服务
result 获取用户决策结果
```
### aide decide submit
提交待定项数据启动 Web 服务。
从 JSON 文件读取待定项数据启动后台 Web 服务,立即返回
```bash
aide decide submit '<json数据>'
aide decide submit <json文件路径>
```
**JSON 格式**
**使用流程**
1. 将待定项数据写入 JSON 文件
2. 执行 `aide decide submit <文件路径>` 启动服务
3. 告知用户访问 Web 界面进行决策
4. 用户完成后执行 `aide decide result` 获取结果
**JSON 文件格式**
```json
{
"task": "任务简述",
@@ -267,7 +273,7 @@ aide decide submit '<json数据>'
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `port` | 3721 | 起始端口 |
| `port` | 3721 | 起始端口(自动探测可用端口) |
| `bind` | `"127.0.0.1"` | 监听地址,设为 `"0.0.0.0"` 可允许外部访问 |
| `url` | `""` | 自定义访问地址,为空时自动生成 |
| `timeout` | 0 | 超时时间0 表示不超时 |
@@ -276,10 +282,11 @@ aide decide submit '<json数据>'
```
→ Web 服务已启动
→ 请访问: http://localhost:3721
等待用户完成决策...
✓ 决策已完成
→ 用户完成决策后执行 aide decide result 获取结果
```
> 注:服务在后台运行,命令立即返回。用户提交决策后服务自动关闭。
### aide decide result
获取用户决策结果。
@@ -298,6 +305,10 @@ aide decide result
}
```
**错误情况**
- 尚无决策结果(服务运行中):提示等待用户完成操作
- 尚无决策结果(服务已关闭):提示重新执行 submit
> 注:`note` 字段仅在用户添加备注时出现
> 注:如果数据中有 `recommend` 字段,对应选项会默认选中

View File

@@ -1,9 +1,8 @@
"""aide decide 模块入口。"""
from .cli import cmd_decide, cmd_decide_result, cmd_decide_submit
from .cli import cmd_decide_result, cmd_decide_submit
__all__ = [
"cmd_decide",
"cmd_decide_submit",
"cmd_decide_result",
]

View File

@@ -3,57 +3,90 @@
from __future__ import annotations
import json
import subprocess
import sys
import time
from pathlib import Path
from aide.core import output
from aide.decide.errors import DecideError
from aide.decide.server import DecideServer
from aide.decide.storage import DecideStorage
from aide.decide.types import DecideInput
def cmd_decide(args) -> bool:
"""aide decide 统一入口"""
if getattr(args, "data", None) == "result":
return cmd_decide_result()
if getattr(args, "data", None) is None:
_print_error("缺少参数: 需要传入 JSON 数据或 result")
return False
return cmd_decide_submit(args.data)
def cmd_decide_submit(json_data: str) -> bool:
"""提交待定项并启动 Web 服务。"""
def cmd_decide_submit(file_path: str) -> bool:
"""从文件读取数据,启动后台 Web 服务"""
root = Path.cwd()
storage = DecideStorage(root)
# 1. 读取 JSON 文件
json_file = Path(file_path)
if not json_file.is_absolute():
json_file = root / json_file
if not json_file.exists():
_print_error(f"文件不存在: {file_path}")
return False
try:
raw = json.loads(json_data)
raw = json.loads(json_file.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
_print_error(f"JSON 解析失败: {exc}", "检查 JSON 格式是否正确")
return False
# 2. 验证数据格式
try:
decide_input = DecideInput.from_dict(raw)
except DecideError as exc:
_print_error(f"数据验证失败: {exc}", "检查必填字段是否完整")
return False
# 3. 保存到 pending.json
try:
storage.save_pending(decide_input)
except DecideError as exc:
_print_error(str(exc))
return False
server = DecideServer(root, storage)
return server.start()
# 4. 启动后台服务
return _start_daemon(root, storage)
def _start_daemon(root: Path, storage: DecideStorage) -> bool:
"""启动后台服务进程。"""
# 启动 daemon 进程
daemon_module = "aide.decide.daemon"
try:
subprocess.Popen(
[sys.executable, "-m", daemon_module, str(root)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True, # 脱离父进程
)
except Exception as exc:
_print_error(f"启动后台服务失败: {exc}")
return False
# 等待服务启动(检查状态文件)
for _ in range(50): # 最多等待 5 秒
time.sleep(0.1)
info = storage.load_server_info()
if info and "url" in info:
output.info("Web 服务已启动")
output.info(f"请访问: {info['url']}")
output.info("用户完成决策后执行 aide decide result 获取结果")
return True
_print_error("服务启动超时", "请检查端口是否被占用")
return False
def cmd_decide_result() -> bool:
"""读取最新决策结果并输出 JSON"""
"""获取决策结果(服务在用户提交后自动关闭)"""
root = Path.cwd()
storage = DecideStorage(root)
# 检查 pending
try:
pending = storage.load_pending()
except DecideError as exc:
@@ -61,14 +94,15 @@ def cmd_decide_result() -> bool:
return False
if pending is None:
_print_error("未找到待定项数据", "请先执行 aide decide submit '<json>'")
_print_error("未找到待定项数据", "请先执行 aide decide submit <file>")
return False
session_id = pending.meta.session_id if pending.meta else None
if not session_id:
_print_error("决策结果已过期", "pending.json 已被更新,请重新执行 aide decide submit '<json>'")
_print_error("数据异常", "pending.json 缺少 session_id,请重新执行 aide decide submit")
return False
# 检查结果
try:
result = storage.load_result()
except DecideError as exc:
@@ -76,20 +110,20 @@ def cmd_decide_result() -> bool:
return False
if result is None:
has_history = any(
path.is_file()
and path.name.endswith(".json")
and path.name != "pending.json"
for path in storage.decisions_dir.glob("*.json")
)
if has_history:
_print_error("决策结果已过期", "pending.json 已被更新,请重新执行 aide decide submit '<json>'")
else:
# 检查服务是否还在运行
if storage.is_server_running():
_print_error("尚无决策结果", "请等待用户在 Web 界面完成操作")
else:
# 服务已关闭但没有结果,可能是超时或异常
_print_error("尚无决策结果", "服务可能已超时关闭,请重新执行 aide decide submit")
return False
# 输出结果
payload = json.dumps(result.to_dict(), ensure_ascii=False, separators=(",", ":"))
print(payload)
# 清理服务状态文件(如存在)
storage.clear_server_info()
return True

View File

@@ -0,0 +1,47 @@
"""后台服务入口点。
通过 subprocess 启动,独立运行 HTTP 服务。
用法: python -m aide.decide.daemon <project_root>
"""
from __future__ import annotations
import os
import signal
import sys
from pathlib import Path
def main() -> int:
if len(sys.argv) < 2:
sys.stderr.write("用法: python -m aide.decide.daemon <project_root>\n")
return 1
root = Path(sys.argv[1])
if not root.exists():
sys.stderr.write(f"目录不存在: {root}\n")
return 1
# 延迟导入,避免循环依赖
from aide.decide.server import DecideServer
from aide.decide.storage import DecideStorage
storage = DecideStorage(root)
server = DecideServer(root, storage)
# 注册信号处理
def handle_sigterm(signum: int, frame: object) -> None:
server.stop("terminated")
signal.signal(signal.SIGTERM, handle_sigterm)
# 获取当前进程 PID
pid = os.getpid()
# 启动服务(会保存 server.json阻塞等待
success = server.start_daemon(pid)
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -95,6 +95,51 @@ class DecideServer:
self.should_close = True
self.close_reason = reason
def start_daemon(self, pid: int) -> bool:
"""作为后台进程启动服务(由 daemon.py 调用)。
与 start() 的区别:
- 不输出到 stdout后台运行
- 保存服务信息到 server.json
- 退出时清理 server.json
"""
try:
config = ConfigManager(self.root).load_config()
start_port = _get_int(config, "decide", "port", default=3721)
self.timeout = _get_int(config, "decide", "timeout", default=0)
self.bind = _get_str(config, "decide", "bind", default="127.0.0.1")
self.url = _get_str(config, "decide", "url", default="")
available = self._find_available_port(start_port)
if available is None:
return False
self.port = available
handlers = DecideHandlers(
storage=self.storage,
web_dir=self.web_dir,
stop_callback=self.stop,
)
RequestHandler = self._build_request_handler(handlers)
self.httpd = DecideHTTPServer((self.bind, self.port), RequestHandler, handlers)
self.httpd.timeout = 1.0
# 生成访问地址
access_url = self.url if self.url else f"http://localhost:{self.port}"
# 保存服务信息(供 CLI 读取)
self.storage.save_server_info(pid, self.port, access_url)
# 阻塞等待用户操作
self._serve_forever()
# 清理服务信息
self.storage.clear_server_info()
return True
except Exception:
self.storage.clear_server_info()
return False
def _find_available_port(self, start: int) -> int | None:
attempts = 10
for offset in range(attempts):

View File

@@ -115,3 +115,49 @@ class DecideStorage:
except Exception as exc:
raise DecideError(f"无法解析 {path.name}: {exc}")
# ========== 服务状态管理 ==========
@property
def server_info_path(self) -> Path:
"""服务状态文件路径。"""
return self.decisions_dir / "server.json"
def save_server_info(self, pid: int, port: int, url: str) -> None:
"""保存后台服务信息。"""
self.ensure_ready()
info = {
"pid": pid,
"port": port,
"url": url,
"started_at": datetime.now().astimezone().isoformat(timespec="seconds"),
}
self._save_atomic(self.server_info_path, info)
def load_server_info(self) -> dict[str, Any] | None:
"""读取服务信息,不存在返回 None。"""
if not self.server_info_path.exists():
return None
try:
return self._load_json(self.server_info_path)
except DecideError:
return None
def clear_server_info(self) -> None:
"""清理服务状态文件。"""
if self.server_info_path.exists():
try:
self.server_info_path.unlink()
except OSError:
pass
def is_server_running(self) -> bool:
"""检查后台服务是否运行中。"""
info = self.load_server_info()
if not info or "pid" not in info:
return False
try:
os.kill(info["pid"], 0) # 检查进程是否存在
return True
except (ProcessLookupError, PermissionError, OSError):
return False

View File

@@ -9,7 +9,6 @@ from typing import Any
from aide.core import output
from aide.core.config import ConfigManager
from aide.decide import cmd_decide
from aide.env.manager import EnvManager
from aide.flow.tracker import FlowTracker
@@ -134,9 +133,9 @@ def build_parser() -> argparse.ArgumentParser:
decide_parser = subparsers.add_parser("decide", help="待定项确认与决策记录")
decide_subparsers = decide_parser.add_subparsers(dest="decide_cmd")
# aide decide submit '<json>'
# aide decide submit <file>
decide_submit_parser = decide_subparsers.add_parser("submit", help="提交待定项数据并启动 Web 服务")
decide_submit_parser.add_argument("data", help="待定项 JSON 数据")
decide_submit_parser.add_argument("file", help="待定项 JSON 数据文件路径")
decide_submit_parser.set_defaults(func=handle_decide_submit)
# aide decide result
@@ -302,18 +301,18 @@ def handle_decide_help(args: argparse.Namespace) -> bool:
print("usage: aide decide {submit,result} ...")
print("")
print("子命令:")
print(" submit <json> 提交待定项数据启动 Web 服务")
print(" submit <file> 从文件读取待定项数据启动后台 Web 服务")
print(" result 获取用户决策结果")
print("")
print("示例:")
print(" aide decide submit '{\"task\":\"...\",\"source\":\"...\",\"items\":[...]}'")
print(" aide decide submit ./pending-items.json")
print(" aide decide result")
return True
def handle_decide_submit(args: argparse.Namespace) -> bool:
from aide.decide import cmd_decide_submit
return cmd_decide_submit(args.data)
return cmd_decide_submit(args.file)
def handle_decide_result(args: argparse.Namespace) -> bool: