diff --git a/.gitignore b/.gitignore index 01dac5a..1a63c77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -anthropic-agent-skills/ \ No newline at end of file +anthropic-agent-skills/ +.aide/ +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bb9d78 --- /dev/null +++ b/README.md @@ -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 已完成,当前进入程序实现阶段) diff --git a/aide-program/aide.bat b/aide-program/aide.bat new file mode 100644 index 0000000..2d760cc --- /dev/null +++ b/aide-program/aide.bat @@ -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 diff --git a/aide-program/aide.sh b/aide-program/aide.sh new file mode 100755 index 0000000..fffe429 --- /dev/null +++ b/aide-program/aide.sh @@ -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 "$@" diff --git a/aide-program/aide/__init__.py b/aide-program/aide/__init__.py new file mode 100644 index 0000000..5925810 --- /dev/null +++ b/aide-program/aide/__init__.py @@ -0,0 +1,5 @@ +""" +Aide 命令行工具包。 + +当前版本聚焦基础环境检测与配置管理,flow/decide 功能尚未实现。 +""" diff --git a/aide-program/aide/__main__.py b/aide-program/aide/__main__.py new file mode 100644 index 0000000..dde501f --- /dev/null +++ b/aide-program/aide/__main__.py @@ -0,0 +1,5 @@ +from aide.main import main + + +if __name__ == "__main__": + main() diff --git a/aide-program/aide/core/__init__.py b/aide-program/aide/core/__init__.py new file mode 100644 index 0000000..9ce7ae3 --- /dev/null +++ b/aide-program/aide/core/__init__.py @@ -0,0 +1 @@ +# 核心基础模块(配置与输出) diff --git a/aide-program/aide/core/config.py b/aide-program/aide/core/config.py new file mode 100644 index 0000000..b79a634 --- /dev/null +++ b/aide-program/aide/core/config.py @@ -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 diff --git a/aide-program/aide/core/output.py b/aide-program/aide/core/output.py new file mode 100644 index 0000000..50488c4 --- /dev/null +++ b/aide-program/aide/core/output.py @@ -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}") diff --git a/aide-program/aide/env/__init__.py b/aide-program/aide/env/__init__.py new file mode 100644 index 0000000..b79952c --- /dev/null +++ b/aide-program/aide/env/__init__.py @@ -0,0 +1 @@ +# 环境检测相关模块 diff --git a/aide-program/aide/env/ensure.py b/aide-program/aide/env/ensure.py new file mode 100644 index 0000000..3d813e1 --- /dev/null +++ b/aide-program/aide/env/ensure.py @@ -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 diff --git a/aide-program/aide/main.py b/aide-program/aide/main.py new file mode 100644 index 0000000..86acf54 --- /dev/null +++ b/aide-program/aide/main.py @@ -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()) diff --git a/discuss/04-aide-program实施方案.md b/discuss/04-aide-program实施方案.md new file mode 100644 index 0000000..86b0620 --- /dev/null +++ b/discuss/04-aide-program实施方案.md @@ -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` 均通过,输出符合 `✓/⚠/✗/→` 规范。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e942a4d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# 基础依赖 +tomli-w>=1.0.0