1
0

完成一个简易的全局skill、command管理器

This commit is contained in:
2026-02-25 14:33:56 +08:00
parent f4cb809f9d
commit 2d327b5af8
60 changed files with 6053 additions and 1 deletions

View File

@@ -0,0 +1,322 @@
## 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` 命令(未来增强)诊断和修复配置