1
0
Files
Skill/openspec/changes/archive/2026-02-25-build-skillmgr-cli/design.md

323 lines
10 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.
## 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` 命令(未来增强)诊断和修复配置