feat: 实现部分aide

This commit is contained in:
2025-12-13 05:01:28 +08:00
parent bb58aee6ee
commit 6dc675ffdc
14 changed files with 511 additions and 1 deletions

4
.gitignore vendored
View File

@@ -1 +1,3 @@
anthropic-agent-skills/
anthropic-agent-skills/
.aide/
__pycache__/

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
# Aide 项目概览
本仓库包含三部分产物:
- **aide-marketplace/**Claude Code 插件目录commands + skills
- **aide-program/**aide 程序实现(本阶段提供初始化、配置与环境管理)
- **ai-agent-memory/** & **docs/**:原始流程文档与参考资料
当前完成情况插件与文档已就绪aide 程序已实现基础 CLI后续将补充 `aide flow``aide decide` 细节。
## 快速开始aide 程序)
### 环境准备
1. 确认已安装 `uv`0.9+)。
2. 在仓库根目录创建虚拟环境并安装依赖:
```bash
uv venv .venv
uv pip install -r requirements.txt --python .venv
```
### 可用命令
- 初始化配置与 .aide 目录(会写入 `.gitignore`
```bash
./aide-program/aide.sh init
```
- 检测运行时环境(不读取配置)
```bash
./aide-program/aide.sh env ensure --runtime
```
- 检测项目环境,确保 `.venv`、`requirements`、任务文档路径等
```bash
./aide-program/aide.sh env ensure
```
- 读取/设置配置(示例)
```bash
./aide-program/aide.sh config get task.source
./aide-program/aide.sh config set task.spec task-spec.md
```
> Windows 可使用 `aide-program\\aide.bat`,命令参数一致。
### 配置文件
`aide init` 会生成 `.aide/config.toml`,默认字段:
- `runtime.python_min`:最小 Python 版本(默认 3.11
- `task.source` / `task.spec`:任务原文档与细则文档默认路径
- `env.venv` / `env.requirements`:虚拟环境与依赖文件位置
- `flow.phases`流程环节名称flow/decide 功能尚未实现)
## 未完成功能
- `aide flow` 进度追踪、`aide decide` 待定项 Web 界面尚未实现,后续阶段补充。
- 配置写入时暂不保留注释,必要时可重新运行 `aide init` 重置为模板后再调整。
## 参考
- 需求规格:`aide-requirements.md`
- 设计讨论:`discuss/` 目录Phase 1/2 已完成,当前进入程序实现阶段)

16
aide-program/aide.bat Normal file
View File

@@ -0,0 +1,16 @@
@echo off
setlocal
set SCRIPT_DIR=%~dp0
set PROJECT_ROOT=%SCRIPT_DIR%..
set VENV_PY=%PROJECT_ROOT%\.venv\Scripts\python.exe
set PYTHONPATH=%PROJECT_ROOT%\aide-program;%PYTHONPATH%
if not exist "%VENV_PY%" (
echo ✗ 未找到虚拟环境请先运行uv venv .venv ^&^& uv pip install -r requirements.txt
exit /b 1
)
pushd "%PROJECT_ROOT%"
"%VENV_PY%" -m aide %*
popd

15
aide-program/aide.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
VENV_PY="${PROJECT_ROOT}/.venv/bin/python"
if [ ! -x "$VENV_PY" ]; then
echo "✗ 未找到虚拟环境请先运行uv venv .venv && uv pip install -r requirements.txt" >&2
exit 1
fi
cd "$PROJECT_ROOT"
export PYTHONPATH="${PROJECT_ROOT}/aide-program${PYTHONPATH:+:$PYTHONPATH}"
exec "$VENV_PY" -m aide "$@"

View File

@@ -0,0 +1,5 @@
"""
Aide 命令行工具包。
当前版本聚焦基础环境检测与配置管理flow/decide 功能尚未实现。
"""

View File

@@ -0,0 +1,5 @@
from aide.main import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
# 核心基础模块(配置与输出)

View File

@@ -0,0 +1,109 @@
"""配置管理:生成默认配置、读取/写入配置、维护 .aide 目录与 .gitignore。"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import tomllib
from tomli_w import dumps as toml_dumps
from aide.core import output
DEFAULT_CONFIG = """# Aide 默认配置(由 aide init 生成)
# runtime: aide 自身运行要求
# task: 任务文档路径
# env: 虚拟环境与依赖配置
# flow: 环节名称列表,供流程校验使用
[runtime]
python_min = "3.11"
use_uv = true
[task]
source = "task-now.md"
spec = "task-spec.md"
[env]
venv = ".venv"
requirements = "requirements.txt"
[flow]
phases = ["task-optimize", "flow-design", "impl", "verify", "docs", "finish"]
"""
class ConfigManager:
def __init__(self, root: Path):
self.root = root
self.aide_dir = self.root / ".aide"
self.config_path = self.aide_dir / "config.toml"
self.decisions_dir = self.aide_dir / "decisions"
self.logs_dir = self.aide_dir / "logs"
def ensure_base_dirs(self) -> None:
self.aide_dir.mkdir(parents=True, exist_ok=True)
self.decisions_dir.mkdir(parents=True, exist_ok=True)
self.logs_dir.mkdir(parents=True, exist_ok=True)
def ensure_gitignore(self) -> None:
gitignore_path = self.root / ".gitignore"
marker = ".aide/"
if gitignore_path.exists():
content = gitignore_path.read_text(encoding="utf-8").splitlines()
if any(line.strip() == marker for line in content):
return
content.append(marker)
gitignore_path.write_text("\n".join(content) + "\n", encoding="utf-8")
else:
gitignore_path.write_text(f"{marker}\n", encoding="utf-8")
def ensure_config(self) -> dict[str, Any]:
self.ensure_base_dirs()
if not self.config_path.exists():
self.config_path.write_text(DEFAULT_CONFIG, encoding="utf-8")
output.ok("已创建默认配置 .aide/config.toml")
return self.load_config()
def load_config(self) -> dict[str, Any]:
if not self.config_path.exists():
return {}
try:
with self.config_path.open("rb") as f:
return tomllib.load(f)
except Exception as exc: # pragma: no cover - 兼容性输出
output.err(f"读取配置失败: {exc}")
return {}
def get_value(self, key: str) -> Any:
data = self.load_config()
return self._walk_get(data, key)
def set_value(self, key: str, value: Any) -> None:
data = self.ensure_config()
self._walk_set(data, key, value)
self._write_config(data)
output.ok(f"已更新 {key} = {value!r}")
def _write_config(self, data: dict[str, Any]) -> None:
self.config_path.write_text(toml_dumps(data), encoding="utf-8")
@staticmethod
def _walk_get(data: dict[str, Any], dotted_key: str) -> Any:
current: Any = data
for part in dotted_key.split("."):
if not isinstance(current, dict):
return None
if part not in current:
return None
current = current[part]
return current
@staticmethod
def _walk_set(data: dict[str, Any], dotted_key: str, value: Any) -> None:
parts = dotted_key.split(".")
current = data
for part in parts[:-1]:
if part not in current or not isinstance(current[part], dict):
current[part] = {}
current = current[part]
current[parts[-1]] = value

View File

@@ -0,0 +1,24 @@
"""输出格式工具,统一使用前缀符号。"""
def ok(message: str) -> None:
print(f"{message}")
def warn(message: str) -> None:
print(f"{message}")
def err(message: str) -> None:
print(f"{message}")
def info(message: str) -> None:
print(f"{message}")
def step(message: str, current: int | None = None, total: int | None = None) -> None:
if current is not None and total is not None:
print(f"[{current}/{total}] {message}")
else:
print(f"[·] {message}")

1
aide-program/aide/env/__init__.py vendored Normal file
View File

@@ -0,0 +1 @@
# 环境检测相关模块

119
aide-program/aide/env/ensure.py vendored Normal file
View File

@@ -0,0 +1,119 @@
"""环境检测与修复逻辑。"""
from __future__ import annotations
import platform
import subprocess
import sys
from pathlib import Path
from aide.core import output
from aide.core.config import ConfigManager
class EnvManager:
def __init__(self, root: Path):
self.root = root
def ensure(self, runtime_only: bool, cfg: ConfigManager) -> bool:
"""运行环境检测入口。"""
required_py = self._get_required_python(cfg, runtime_only)
if not self._check_python_version(required_py):
return False
uv_version = self._check_uv()
if uv_version is None:
return False
if runtime_only:
output.ok(f"运行时环境就绪 (python:{platform.python_version()}, uv:{uv_version})")
return True
config = cfg.ensure_config()
cfg.ensure_gitignore()
env_config = config.get("env", {})
venv_path = self.root / env_config.get("venv", ".venv")
req_path = self.root / env_config.get("requirements", "requirements.txt")
self._ensure_requirements_file(req_path)
if not self._ensure_venv(venv_path):
return False
if not self._install_requirements(venv_path, req_path):
return False
task_config = config.get("task", {})
output.info(f"任务原文档: {task_config.get('source', 'task-now.md')}")
output.info(f"任务细则文档: {task_config.get('spec', 'task-spec.md')}")
output.ok(f"环境就绪 (python:{platform.python_version()}, uv:{uv_version}, venv:{venv_path})")
return True
@staticmethod
def _get_required_python(cfg: ConfigManager, runtime_only: bool) -> str:
if runtime_only:
return "3.11"
data = cfg.load_config()
runtime = data.get("runtime", {})
return str(runtime.get("python_min", "3.11"))
@staticmethod
def _parse_version(version: str) -> tuple[int, ...]:
parts = []
for part in version.split("."):
try:
parts.append(int(part))
except ValueError:
break
return tuple(parts)
def _check_python_version(self, required: str) -> bool:
current = self._parse_version(platform.python_version())
target = self._parse_version(required)
if current >= target:
return True
output.err(f"Python 版本不足,要求>={required},当前 {platform.python_version()}")
return False
def _check_uv(self) -> str | None:
try:
result = subprocess.run(
["uv", "--version"],
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError) as exc:
output.err(f"未检测到 uv请先安装{exc}")
return None
def _ensure_venv(self, venv_path: Path) -> bool:
if venv_path.exists():
return True
output.info(f"创建虚拟环境: {venv_path}")
try:
subprocess.run(["uv", "venv", str(venv_path)], check=True)
output.ok("已创建虚拟环境")
return True
except subprocess.CalledProcessError as exc:
output.err(f"创建虚拟环境失败: {exc}")
return False
@staticmethod
def _ensure_requirements_file(req_path: Path) -> None:
if req_path.exists():
return
req_path.write_text("# 在此添加依赖\n", encoding="utf-8")
output.warn(f"未找到 {req_path.name},已创建空文件")
def _install_requirements(self, venv_path: Path, req_path: Path) -> bool:
if not req_path.exists():
output.err(f"缺少 {req_path}")
return False
cmd = ["uv", "pip", "install", "-r", str(req_path), "--python", str(venv_path)]
output.info("安装依赖uv pip install -r requirements.txt")
try:
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
return True
except subprocess.CalledProcessError as exc:
output.err(f"安装依赖失败: {exc}")
return False

110
aide-program/aide/main.py Normal file
View File

@@ -0,0 +1,110 @@
"""aide 命令行入口。"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from typing import Any
from aide.core import output
from aide.core.config import ConfigManager
from aide.env.ensure import EnvManager
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if not hasattr(args, "func"):
parser.print_help()
return 0
try:
result = args.func(args)
except KeyboardInterrupt:
output.err("操作已取消")
return 1
if result is False:
return 1
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="aide", description="Aide 工作流辅助工具")
subparsers = parser.add_subparsers(dest="command")
init_parser = subparsers.add_parser("init", help="初始化 .aide 目录与默认配置")
init_parser.set_defaults(func=handle_init)
env_parser = subparsers.add_parser("env", help="环境管理")
env_sub = env_parser.add_subparsers(dest="env_command")
ensure_parser = env_sub.add_parser("ensure", help="检测并修复运行环境")
ensure_parser.add_argument("--runtime", action="store_true", help="仅检查 aide 运行时环境")
ensure_parser.set_defaults(func=handle_env_ensure)
config_parser = subparsers.add_parser("config", help="配置管理")
config_sub = config_parser.add_subparsers(dest="config_command")
get_parser = config_sub.add_parser("get", help="读取配置值")
get_parser.add_argument("key", help="使用点号分隔的键名,如 task.source")
get_parser.set_defaults(func=handle_config_get)
set_parser = config_sub.add_parser("set", help="设置配置值")
set_parser.add_argument("key", help="使用点号分隔的键名,如 task.source")
set_parser.add_argument("value", help="要写入的值,支持 bool/int/float/字符串")
set_parser.set_defaults(func=handle_config_set)
parser.add_argument("--version", action="version", version="aide dev")
return parser
def handle_init(args: argparse.Namespace) -> bool:
root = Path.cwd()
cfg = ConfigManager(root)
cfg.ensure_config()
cfg.ensure_gitignore()
output.ok("初始化完成,.aide/ 与默认配置已准备就绪")
return True
def handle_env_ensure(args: argparse.Namespace) -> bool:
if args.env_command != "ensure":
output.err("请指定 env 子命令,如: aide env ensure")
return False
root = Path.cwd()
cfg = ConfigManager(root)
manager = EnvManager(root)
return manager.ensure(runtime_only=args.runtime, cfg=cfg)
def handle_config_get(args: argparse.Namespace) -> bool:
root = Path.cwd()
cfg = ConfigManager(root)
value = cfg.get_value(args.key)
if value is None:
output.warn(f"未找到配置项 {args.key}")
return False
output.info(f"{args.key} = {value!r}")
return True
def handle_config_set(args: argparse.Namespace) -> bool:
root = Path.cwd()
cfg = ConfigManager(root)
parsed_value = _parse_value(args.value)
cfg.set_value(args.key, parsed_value)
return True
def _parse_value(raw: str) -> Any:
lowered = raw.lower()
if lowered in {"true", "false"}:
return lowered == "true"
try:
if "." in raw:
return float(raw)
return int(raw)
except ValueError:
return raw
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,47 @@
# 背景与范围
- 已审阅 `aide-requirements.md``ai-agent-memory/` 全部文件、`docs/` 全部文件、`aide-marketplace/` 现有 commands 与 SKILL、`discuss/01-03``reply/re-03.md``CLAUDE.md`/`AGENTS.md`。当前 Phase1/2 已完成re-03 调整已落实。
- 任务:在 `aide-program/` 下实现 aide 程序系统的入口封装与 Python 核心功能,**暂不实现/细化 aide flow 与 aide decide**。
# 设计聚焦
- 入口封装:提供 `aide.sh``aide.bat` 调用 `.venv` 下的 Python执行 `python -m aide`
- CLI 范围(本阶段):`aide init``aide env ensure [--runtime]``aide config get/set`。flow/decide 仅预留配置,不实现逻辑。
- 输出规范:统一使用 `✓/⚠/✗/→` 前缀,遵循“静默即成功”原则,中文提示。
- 配置管理:`.aide/config.toml`,默认包含 `runtime`python_min`task`source/spec 默认 `task-now.md`/`task-spec.md`)、`env`venv/requirements`flow.phases`。支持 `config get/set`,缺失时自动创建默认配置与 `.aide/` 目录。
- 环境检测:
- `--runtime`:检测 Python 版本、`uv` 可用性,不读配置。
- 完整 `ensure`:读取配置,确保 `.aide/` 结构、`.gitignore` 条目、`.venv`(用 `uv venv` 创建)、`requirements.txt` 存在并通过 `uv pip install -r` 安装依赖,输出任务文档路径提示。
- 依赖尽量标准库TOML 写入使用轻量依赖 `tomli-w`,列入 `requirements.txt` 并通过 uv 安装。
# 目录与模块规划aide-program/
```
aide-program/
├── aide.sh / aide.bat # 入口脚本,调用 .venv 下 Python
└── aide/
├── __init__.py
├── __main__.py # 支持 python -m aide
├── main.py # CLI 解析与命令分发
├── core/
│ ├── __init__.py
│ ├── config.py # 配置读写、默认生成、gitignore 维护
│ └── output.py # 统一输出前缀
└── env/
├── __init__.py
└── ensure.py # 环境检测与依赖安装
```
# 开发计划
1) 初始化 `aide-program/` 目录与入口脚本骨架;补充 `requirements.txt``.venv`(已建)。
2) 实现核心模块输出工具、配置管理、环境检测、CLI 路由。
3) 补充根级 `README.md`,描述用法/约束/未实现部分;必要自检(使用 `.venv/bin/python -m aide ...`)。
4) 暂不实现 flow/decide 细节,后续再扩展。
# 实施进展(同步)
- 已创建 `aide-program/` 目录与入口脚本bash/bat通过 `.venv` 调用 `python -m aide`
- 实现模块:`core/output.py`(统一输出)、`core/config.py`(默认配置生成、读取/写入、.gitignore 维护)、`env/ensure.py`runtime 检查、venv/依赖处理)、`main.py`init/env/config CLI
- 生成默认配置 `.aide/config.toml`,补充 `.gitignore``requirements.txt` 添加 `tomli-w` 并通过 uv 安装。
- 自检:`aide init``aide env ensure --runtime``aide env ensure``aide config get task.source` 均通过,输出符合 `✓/⚠/✗/→` 规范。

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
# 基础依赖
tomli-w>=1.0.0