323 lines
10 KiB
Markdown
323 lines
10 KiB
Markdown
## Context
|
||
|
||
当前手动管理 AI 编程平台的 skills/commands 面临以下挑战:
|
||
|
||
- **平台差异**:Claude Code 使用目录结构,OpenCode 使用扁平化文件命名(如 `lyxy-kb-init.md`)
|
||
- **手动操作**:需要手动理解每个平台的规范、复制文件、重命名
|
||
- **追踪困难**:无法知道哪些 skills/commands 已安装、来自哪个源、何时安装
|
||
- **更新风险**:手动更新容易遗漏文件或破坏已有配置
|
||
|
||
目标用户是作者本人,工具需要简单直接,优先实现核心功能而非过度设计。
|
||
|
||
**约束条件**:
|
||
- 单用户场景,不考虑多用户协作
|
||
- 仅支持最新版本,不处理多版本共存
|
||
- 不解析依赖关系,用户手动管理依赖
|
||
- 平台支持范围:Claude Code、OpenCode(未来可扩展)
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- 自动化从 git 源仓库到目标平台的完整安装流程
|
||
- 支持全局和项目级两种安装作用域
|
||
- 内置 Claude Code 和 OpenCode 的平台适配规则
|
||
- 记录所有安装操作,支持查询、更新、卸载、清理
|
||
- 事务性安装机制,避免部分失败导致的不一致状态
|
||
- 用户友好的交互体验(确认覆盖、清晰的错误提示)
|
||
|
||
**Non-Goals:**
|
||
|
||
- ❌ 多版本管理(语义化版本、版本锁定、版本冲突解决)
|
||
- ❌ 依赖解析和自动安装依赖
|
||
- ❌ 插件化的平台适配器系统
|
||
- ❌ 复杂的仓库注册中心或包索引服务
|
||
- ❌ 跨平台迁移或批量同步
|
||
|
||
## Decisions
|
||
|
||
### 1. 技术栈选择:Go + Cobra
|
||
|
||
**决策**:使用 Go 语言开发,CLI 框架选择 Cobra。
|
||
|
||
**理由**:
|
||
- **Go**:编译为单一可执行文件,无运行时依赖,跨平台分发简单
|
||
- **Cobra**:业界标准 CLI 框架(kubectl、docker 都用它),支持子命令、自动帮助生成、参数验证
|
||
- **替代方案**:标准库 `flag` 包功能过于简单,不适合多子命令场景
|
||
|
||
**影响**:开发者需要熟悉 Go 和 Cobra 的基本用法。
|
||
|
||
---
|
||
|
||
### 2. 配置文件格式:JSON
|
||
|
||
**决策**:使用 JSON 格式存储配置(repository.json、install.json)。
|
||
|
||
**理由**:
|
||
- Go 标准库原生支持,无需第三方依赖
|
||
- 结构清晰,易于程序读写和人工检查
|
||
- **替代方案**:YAML(需要第三方库)、TOML(生态较小)
|
||
|
||
**配置结构**:
|
||
```
|
||
~/.skillmgr/
|
||
├── repository.json # 源仓库列表
|
||
├── install.json # 安装记录
|
||
└── cache/ # git 仓库缓存
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 事务性安装:Tmp Staging
|
||
|
||
**决策**:采用三阶段事务性安装(Stage → Commit → Rollback)。
|
||
|
||
**理由**:
|
||
- 避免部分文件复制失败导致目标目录处于不一致状态
|
||
- 先在系统临时目录(`os.TempDir()`)组装完整的目标文件树
|
||
- 验证成功后一次性移动到最终位置
|
||
- 失败时自动清理临时目录,不污染目标
|
||
|
||
**流程**:
|
||
```
|
||
1. 创建 staging 目录(/tmp/skillmgr-xxxxx/)
|
||
2. 复制所有文件到 staging(应用平台适配规则)
|
||
3. 验证 staging 目录完整性
|
||
4. 移动 staging 到目标位置(原子操作)
|
||
5. 失败则删除 staging,不影响目标
|
||
```
|
||
|
||
**替代方案**:直接复制到目标(风险高)、使用 Git worktree(过于复杂)。
|
||
|
||
---
|
||
|
||
### 4. 平台适配器:内置而非插件化
|
||
|
||
**决策**:将 Claude Code 和 OpenCode 的适配规则硬编码在程序内,不支持用户自定义适配器。
|
||
|
||
**理由**:
|
||
- 目标平台数量少且稳定(2 个),插件系统收益低
|
||
- 硬编码保证规则的正确性和一致性
|
||
- 简化实现和维护成本
|
||
- **替代方案**:配置文件定义适配规则(增加复杂度)、插件系统(过度设计)
|
||
|
||
**适配器接口**:
|
||
```go
|
||
type PlatformAdapter interface {
|
||
GetSkillInstallPath(scope, name) (string, error)
|
||
GetCommandInstallPath(scope, group) (string, error)
|
||
AdaptSkill(sourcePath, destPath) (map[string]string, error)
|
||
AdaptCommand(sourcePath, destPath, group) (map[string]string, error)
|
||
}
|
||
```
|
||
|
||
**差异处理**:
|
||
- **Skills**:两个平台都保持目录结构,直接复制
|
||
- **Commands**:
|
||
- Claude Code 保持目录结构(`commands/lyxy-kb/init.md`)
|
||
- OpenCode 扁平化文件名(`command/lyxy-kb-init.md`)
|
||
|
||
---
|
||
|
||
### 5. 安装策略:复制而非符号链接
|
||
|
||
**决策**:全局和项目级安装都使用文件复制,不使用符号链接。
|
||
|
||
**理由**:
|
||
- 避免符号链接在跨平台和跨文件系统时的兼容性问题(尤其是 Windows)
|
||
- 项目可以独立于全局安装,避免意外修改影响其他项目
|
||
- 磁盘空间在现代系统中不是瓶颈
|
||
- **替代方案**:全局符号链接(复杂度高,跨平台问题)
|
||
|
||
**影响**:
|
||
- 更新全局安装不会自动影响项目级安装(需显式更新)
|
||
- 多个项目可以独立更新各自的 skills/commands
|
||
|
||
---
|
||
|
||
### 6. 命令文件组织:命令组概念
|
||
|
||
**决策**:将 commands 按目录组织,整个目录作为"命令组"一起安装。
|
||
|
||
**理由**:
|
||
- 源仓库中 commands 按功能分组(如 `commands/lyxy-kb/` 包含 init/ask/ingest/rebuild)
|
||
- 命令组内的命令通常有关联,应一起安装
|
||
- 简化用户操作,避免逐个命令安装
|
||
|
||
**命令组到命令的映射**:
|
||
- Claude Code:`/lyxy-kb-init` → `commands/lyxy-kb/init.md`
|
||
- OpenCode:`/lyxy-kb:init` → `command/lyxy-kb-init.md`
|
||
|
||
---
|
||
|
||
### 7. 安装记录清理:Clean 命令
|
||
|
||
**决策**:提供 `clean` 命令扫描并清理孤立记录(install.json 中存在但目标路径已删除)。
|
||
|
||
**理由**:
|
||
- 用户可能手动删除已安装的目录
|
||
- 避免 install.json 与实际文件系统状态不一致
|
||
- 不自动清理(避免误删),由用户显式触发
|
||
|
||
**实现**:
|
||
```bash
|
||
skillmgr clean
|
||
# 扫描 install.json 中所有记录
|
||
# 检查 install_path 是否存在
|
||
# 列出孤立记录并确认删除
|
||
```
|
||
|
||
---
|
||
|
||
### 8. 目录冲突处理:用户决策
|
||
|
||
**决策**:安装前检查目标目录是否存在,存在时由用户决定是否覆盖。
|
||
|
||
**场景**:
|
||
1. **install.json 有记录 + 目录存在**:已安装,询问是否覆盖
|
||
2. **install.json 无记录 + 目录存在**:未被 skillmgr 管理的目录,询问是否覆盖
|
||
3. **install.json 有记录 + 目录不存在**:孤立记录,清理记录后继续安装
|
||
|
||
**用户交互**:
|
||
```
|
||
Skill 'lyxy-kb' is already installed. Overwrite? [y/N]:
|
||
```
|
||
|
||
---
|
||
|
||
### 9. 项目结构:独立 Go 项目
|
||
|
||
**决策**:在 `manager/` 目录下创建独立的 Go 项目,与现有 skills 仓库分离。
|
||
|
||
**目录结构**:
|
||
```
|
||
manager/
|
||
├── cmd/skillmgr/ # CLI 命令实现
|
||
├── internal/ # 内部包(不对外暴露)
|
||
│ ├── config/ # 配置文件读写
|
||
│ ├── repo/ # Git 仓库管理
|
||
│ ├── adapter/ # 平台适配器
|
||
│ ├── installer/ # 安装逻辑
|
||
│ └── prompt/ # 用户交互
|
||
├── pkg/ # 可对外暴露的包
|
||
│ └── fileutil/ # 文件工具
|
||
├── go.mod
|
||
└── README.md
|
||
```
|
||
|
||
**理由**:
|
||
- 不污染现有 skills 仓库结构
|
||
- 工具本身可以独立开发、测试、发布
|
||
- 清晰的模块边界
|
||
|
||
---
|
||
|
||
### 10. 测试隔离:环境变量注入
|
||
|
||
**决策**:通过环境变量覆盖配置和目标路径,实现零污染测试。
|
||
|
||
**理由**:
|
||
- 测试不应影响用户的实际配置(`~/.skillmgr/`)和安装目录(`~/.claude/`)
|
||
- 环境变量注入是轻量级且侵入性最小的方案
|
||
- 支持并行测试(每个测试独立目录)
|
||
|
||
**实现**:
|
||
```go
|
||
// 配置路径注入
|
||
func GetConfigRoot() (string, error) {
|
||
if testRoot := os.Getenv("SKILLMGR_TEST_ROOT"); testRoot != "" {
|
||
return testRoot, nil
|
||
}
|
||
// 生产模式...
|
||
}
|
||
|
||
// 目标路径注入
|
||
func getBasePath(scope Scope) (string, error) {
|
||
if testBase := os.Getenv("SKILLMGR_TEST_BASE"); testBase != "" {
|
||
return testBase, nil
|
||
}
|
||
// 生产模式...
|
||
}
|
||
```
|
||
|
||
**测试使用**:
|
||
```go
|
||
func TestInstall(t *testing.T) {
|
||
testRoot := t.TempDir()
|
||
testBase := t.TempDir()
|
||
os.Setenv("SKILLMGR_TEST_ROOT", testRoot)
|
||
os.Setenv("SKILLMGR_TEST_BASE", testBase)
|
||
defer os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||
defer os.Unsetenv("SKILLMGR_TEST_BASE")
|
||
// 测试代码...
|
||
}
|
||
```
|
||
|
||
**替代方案**:
|
||
- **依赖注入**(将路径作为参数传递):侵入性强,需要重构所有函数签名
|
||
- **Mock 文件系统**(如 afero):复杂度高,且无法测试真实文件系统行为
|
||
- **专用测试模式标志**:需要额外的全局状态管理
|
||
|
||
## Risks / Trade-offs
|
||
|
||
### 1. 无版本管理
|
||
|
||
**风险**:用户无法回退到旧版本的 skill/command,更新可能引入破坏性变更。
|
||
|
||
**缓解**:
|
||
- 文档中建议用户在重要项目中使用 git 管理项目配置目录(如 `.claude/`)
|
||
- 工具记录 `updated_at` 时间,方便追溯
|
||
|
||
---
|
||
|
||
### 2. 无依赖解析
|
||
|
||
**风险**:安装 command 时,依赖的 skill 可能未安装(如 `lyxy-kb` 命令依赖 `lyxy-reader-office` skill)。
|
||
|
||
**缓解**:
|
||
- 在 skill 的 SKILL.md 中明确记录依赖关系(如 `compatibility` 字段)
|
||
- 错误提示中建议用户检查依赖
|
||
- 未来可选增强:扫描 SKILL.md 提示缺失依赖
|
||
|
||
---
|
||
|
||
### 3. Git 依赖
|
||
|
||
**风险**:工具依赖系统中已安装 Git 客户端,无 Git 则无法拉取仓库。
|
||
|
||
**缓解**:
|
||
- 在 README 中明确前置条件
|
||
- 首次运行时检测 Git 是否可用,提示安装
|
||
- 错误消息中包含 Git 安装指引
|
||
|
||
---
|
||
|
||
### 4. 跨文件系统移动失败
|
||
|
||
**风险**:`os.Rename()` 在跨文件系统时会失败(如 tmp 在 tmpfs,目标在 ext4)。
|
||
|
||
**缓解**:
|
||
- 捕获 Rename 错误,fallback 到递归复制 + 删除 staging
|
||
- 在事务实现中明确处理两种路径
|
||
|
||
---
|
||
|
||
### 5. 平台适配规则变化
|
||
|
||
**风险**:Claude Code 或 OpenCode 未来修改目录结构规范,导致工具失效。
|
||
|
||
**缓解**:
|
||
- 将适配规则集中在 `internal/adapter/` 包中,便于修改
|
||
- 提供版本号,用户可锁定工具版本以保证稳定性
|
||
- 文档中建议关注平台更新公告
|
||
|
||
---
|
||
|
||
### 6. 手动修改配置文件
|
||
|
||
**风险**:用户手动编辑 repository.json 或 install.json 可能破坏格式,导致解析失败。
|
||
|
||
**缓解**:
|
||
- JSON 解析错误时提示备份并重建配置文件
|
||
- 提供 `doctor` 命令(未来增强)诊断和修复配置
|