完成一个简易的全局skill、command管理器
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-25
|
||||
322
openspec/changes/archive/2026-02-25-build-skillmgr-cli/design.md
Normal file
322
openspec/changes/archive/2026-02-25-build-skillmgr-cli/design.md
Normal 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` 命令(未来增强)诊断和修复配置
|
||||
@@ -0,0 +1,37 @@
|
||||
## Why
|
||||
|
||||
当前手动管理和分发 AI 编程平台的 skills/commands 存在诸多问题:需要手动理解不同平台(Claude Code、OpenCode)的目录结构差异、手动复制文件、手动处理命名转换(如 OpenCode 的扁平化命名),且难以追踪已安装的内容和版本。随着 skills 数量增长和多平台支持需求,这种手动流程变得不可维护。需要一个自动化的管理工具来简化从源仓库到目标平台的完整流程。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **新增**:创建独立的 Go CLI 工具(skillmgr),提供命令行界面管理 skills/commands 的完整生命周期
|
||||
- **新增**:支持从 git 仓库拉取和缓存 skills/commands 源代码
|
||||
- **新增**:内置 Claude Code 和 OpenCode 两个平台的适配规则
|
||||
- **新增**:支持全局安装(~/.claude/、~/.opencode/)和项目级安装(./.claude/、./.opencode/)
|
||||
- **新增**:安装记录追踪系统,支持更新、卸载、清理孤立记录
|
||||
- **新增**:事务性安装机制,通过 tmp staging 避免部分失败导致的不一致状态
|
||||
- **新增**:用户交互确认(目录覆盖、冲突解决)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `repository-management`:管理源仓库配置(添加、移除、同步 git 仓库)
|
||||
- `skill-installation`:安装 skills 到目标平台(支持全局/项目作用域)
|
||||
- `command-installation`:安装 commands 到目标平台(处理命令组和平台特定命名)
|
||||
- `platform-adaptation`:适配不同 AI 编程平台的目录结构和命名规则
|
||||
- `install-tracking`:跟踪和管理安装记录(查询、更新、清理)
|
||||
- `transactional-install`:事务性文件安装(staging → commit → rollback)
|
||||
- `test-infrastructure`:测试环境隔离和自动化(零污染测试、fixture 管理、CI 集成)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
(无现有能力需要修改)
|
||||
|
||||
## Impact
|
||||
|
||||
- **新增项目**:在 `manager/` 目录下创建独立的 Go 项目(不影响现有 skills 仓库结构)
|
||||
- **用户配置**:在用户目录创建 `~/.skillmgr/` 配置目录(repository.json、install.json、cache/)
|
||||
- **目标平台**:修改目标平台的 `.claude/` 和 `.opencode/` 目录(根据用户操作)
|
||||
- **依赖**:需要 Git 客户端(用于 clone/pull 操作)
|
||||
- **兼容性**:工具设计为独立运行,不破坏现有手动管理的 skills/commands
|
||||
@@ -0,0 +1,95 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以安装命令组到全局目录
|
||||
|
||||
工具必须支持将整个命令组(commands 目录下的子目录)安装到平台配置目录。
|
||||
|
||||
#### Scenario: 全局安装命令组到 Claude Code
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install command <group> --platform claude --global`
|
||||
- **THEN** 系统将 `commands/<group>/` 下所有 .md 文件复制到 `~/.claude/commands/<group>/`
|
||||
|
||||
#### Scenario: 全局安装命令组到 OpenCode
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install command <group> --platform opencode --global`
|
||||
- **THEN** 系统将 `commands/<group>/` 下所有 .md 文件重命名为 `<group>-<action>.md` 并复制到 `~/.opencode/command/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以安装命令组到项目目录
|
||||
|
||||
工具必须支持将命令组安装到当前项目的平台配置目录。
|
||||
|
||||
#### Scenario: 项目级安装命令组到 Claude Code
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install command <group> --platform claude`
|
||||
- **THEN** 系统将命令组复制到 `./.claude/commands/<group>/`
|
||||
|
||||
#### Scenario: 项目级安装命令组到 OpenCode
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install command <group> --platform opencode`
|
||||
- **THEN** 系统将命令组扁平化复制到 `./.opencode/command/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在所有源仓库中查找命令组
|
||||
|
||||
工具必须在所有已配置源仓库的 `commands/` 目录中搜索指定命令组。
|
||||
|
||||
#### Scenario: 找到命令组
|
||||
|
||||
- **WHEN** 源仓库包含 `commands/<group>/` 目录且内有 .md 文件
|
||||
- **THEN** 系统使用该命令组进行安装
|
||||
|
||||
#### Scenario: 命令组不存在
|
||||
|
||||
- **WHEN** 所有源仓库都不包含指定命令组
|
||||
- **THEN** 系统报错"command '<group>' not found in any repository"
|
||||
|
||||
#### Scenario: 命令组目录为空
|
||||
|
||||
- **WHEN** 找到命令组目录但其中没有 .md 文件
|
||||
- **THEN** 系统报错"command group '<group>' contains no command files"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: OpenCode 平台必须扁平化命令文件名
|
||||
|
||||
工具必须在安装到 OpenCode 平台时,将命令文件重命名为 `<group>-<action>.md` 格式。
|
||||
|
||||
#### Scenario: 转换命令文件名
|
||||
|
||||
- **WHEN** 安装 `commands/lyxy-kb/init.md` 到 OpenCode
|
||||
- **THEN** 文件被重命名为 `lyxy-kb-init.md`
|
||||
|
||||
#### Scenario: 保留 .md 扩展名
|
||||
|
||||
- **WHEN** 转换文件名时
|
||||
- **THEN** 系统保留 `.md` 扩展名
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理命令组目录冲突
|
||||
|
||||
工具必须在安装前检查目标目录或文件是否已存在。
|
||||
|
||||
#### Scenario: Claude Code 命令组目录冲突
|
||||
|
||||
- **WHEN** `~/.claude/commands/<group>/` 目录已存在
|
||||
- **THEN** 系统询问用户是否覆盖
|
||||
|
||||
#### Scenario: OpenCode 命令文件冲突
|
||||
|
||||
- **WHEN** 目标 `~/.opencode/command/` 中已存在同名的 `<group>-*.md` 文件
|
||||
- **THEN** 系统询问用户是否覆盖所有冲突文件
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须记录命令组安装
|
||||
|
||||
工具必须在成功安装后将记录写入 install.json。
|
||||
|
||||
#### Scenario: 记录命令组安装信息
|
||||
|
||||
- **WHEN** 命令组安装成功
|
||||
- **THEN** 系统在 install.json 中添加 type 为 "command"、包含命令组名称和安装路径的记录
|
||||
@@ -0,0 +1,124 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须持久化安装记录
|
||||
|
||||
工具必须在每次成功安装后,将安装信息写入 `~/.skillmgr/install.json`。
|
||||
|
||||
#### Scenario: 创建新记录
|
||||
|
||||
- **WHEN** 首次安装某个 skill/command
|
||||
- **THEN** 系统在 install.json 的 installations 数组中添加新记录
|
||||
|
||||
#### Scenario: 记录包含必要字段
|
||||
|
||||
- **WHEN** 创建安装记录
|
||||
- **THEN** 记录必须包含 type、name、source_repo、platform、scope、install_path、installed_at、updated_at
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以查询已安装项
|
||||
|
||||
工具必须提供命令列出所有已安装的 skills 和 commands。
|
||||
|
||||
#### Scenario: 列出所有已安装项
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list` 命令
|
||||
- **THEN** 系统显示所有 install.json 中的记录
|
||||
|
||||
#### Scenario: 按类型过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --type skill` 或 `--type command`
|
||||
- **THEN** 系统仅显示对应类型的安装记录
|
||||
|
||||
#### Scenario: 按平台过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --platform claude` 或 `--platform opencode`
|
||||
- **THEN** 系统仅显示对应平台的安装记录
|
||||
|
||||
#### Scenario: 按作用域过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --global` 或省略该参数
|
||||
- **THEN** 系统仅显示对应作用域的安装记录
|
||||
|
||||
#### Scenario: 无已安装项
|
||||
|
||||
- **WHEN** install.json 为空或不存在
|
||||
- **THEN** 系统提示"无已安装的 skills/commands"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以卸载已安装项
|
||||
|
||||
工具必须提供卸载功能,删除文件并移除安装记录。
|
||||
|
||||
#### Scenario: 卸载 skill
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr uninstall skill <name> --platform <platform> --global`
|
||||
- **THEN** 系统从 install.json 查找记录,删除对应目录,移除记录
|
||||
|
||||
#### Scenario: 卸载 command
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr uninstall command <group> --platform <platform> --global`
|
||||
- **THEN** 系统删除对应的命令文件或目录,移除记录
|
||||
|
||||
#### Scenario: 卸载不存在的项
|
||||
|
||||
- **WHEN** install.json 中无对应记录
|
||||
- **THEN** 系统提示"未找到安装记录",不执行删除
|
||||
|
||||
#### Scenario: 安装路径已被手动删除
|
||||
|
||||
- **WHEN** install.json 有记录但文件已不存在
|
||||
- **THEN** 系统仅移除记录,不报错
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以更新已安装项
|
||||
|
||||
工具必须提供更新功能,重新从源仓库安装最新版本。
|
||||
|
||||
#### Scenario: 更新单个 skill
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update skill <name> --platform <platform> --global`
|
||||
- **THEN** 系统从源重新安装到原路径,更新 updated_at 字段
|
||||
|
||||
#### Scenario: 更新单个 command
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update command <group> --platform <platform> --global`
|
||||
- **THEN** 系统从源重新安装到原路径,更新 updated_at 字段
|
||||
|
||||
#### Scenario: 更新所有已安装项
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update --all`
|
||||
- **THEN** 系统遍历 install.json 中所有记录,逐个更新
|
||||
|
||||
#### Scenario: 源仓库找不到原始项
|
||||
|
||||
- **WHEN** 更新时源仓库中不再存在该 skill/command
|
||||
- **THEN** 系统报错,不修改已安装文件和记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以清理孤立记录
|
||||
|
||||
工具必须提供命令扫描并清理 install.json 中文件路径已不存在的记录。
|
||||
|
||||
#### Scenario: 扫描孤立记录
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr clean` 命令
|
||||
- **THEN** 系统遍历 install.json 中所有记录,检查 install_path 是否存在
|
||||
|
||||
#### Scenario: 清理孤立记录
|
||||
|
||||
- **WHEN** 发现安装路径不存在的记录
|
||||
- **THEN** 系统列出这些记录并从 install.json 中删除
|
||||
|
||||
#### Scenario: 无孤立记录
|
||||
|
||||
- **WHEN** 所有记录的安装路径都存在
|
||||
- **THEN** 系统提示"无孤立记录"
|
||||
|
||||
#### Scenario: 显示清理结果
|
||||
|
||||
- **WHEN** 清理完成
|
||||
- **THEN** 系统显示清理的记录数量和详情(type、name、platform、scope、路径)
|
||||
@@ -0,0 +1,104 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须支持 Claude Code 平台
|
||||
|
||||
工具必须内置 Claude Code 平台的目录结构和命名规则。
|
||||
|
||||
#### Scenario: Skill 安装路径
|
||||
|
||||
- **WHEN** 安装 skill 到 Claude Code
|
||||
- **THEN** 目标路径为 `<base>/.claude/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Command 安装路径
|
||||
|
||||
- **WHEN** 安装 command 到 Claude Code
|
||||
- **THEN** 目标路径为 `<base>/.claude/commands/<command-group>/`
|
||||
|
||||
#### Scenario: 保持源目录结构
|
||||
|
||||
- **WHEN** 复制文件到 Claude Code
|
||||
- **THEN** 系统保持源仓库的目录结构不变
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须支持 OpenCode 平台
|
||||
|
||||
工具必须内置 OpenCode 平台的目录结构和命名规则。
|
||||
|
||||
#### Scenario: Skill 全局安装路径
|
||||
|
||||
- **WHEN** 全局安装 skill 到 OpenCode
|
||||
- **THEN** 目标路径为 `~/.config/opencode/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Skill 项目级安装路径
|
||||
|
||||
- **WHEN** 项目级安装 skill 到 OpenCode
|
||||
- **THEN** 目标路径为 `./.opencode/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Command 全局安装路径
|
||||
|
||||
- **WHEN** 全局安装 command 到 OpenCode
|
||||
- **THEN** 目标路径为 `~/.config/opencode/commands/`
|
||||
|
||||
#### Scenario: Command 项目级安装路径
|
||||
|
||||
- **WHEN** 项目级安装 command 到 OpenCode
|
||||
- **THEN** 目标路径为 `./.opencode/commands/`
|
||||
|
||||
#### Scenario: Skill 保持结构
|
||||
|
||||
- **WHEN** 复制 skill 到 OpenCode
|
||||
- **THEN** 系统保持源目录结构
|
||||
|
||||
#### Scenario: Command 扁平化
|
||||
|
||||
- **WHEN** 复制 command 到 OpenCode
|
||||
- **THEN** 系统将文件重命名为 `<group>-<action>.md` 并放置在 commands/ 目录下
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须根据作用域确定基础路径
|
||||
|
||||
工具必须根据全局/项目作用域计算正确的基础路径。
|
||||
|
||||
#### Scenario: 全局作用域
|
||||
|
||||
- **WHEN** 用户指定 `--global` 参数
|
||||
- **THEN** 基础路径为用户主目录(`~` 或 `$HOME`)
|
||||
|
||||
#### Scenario: 项目作用域
|
||||
|
||||
- **WHEN** 用户未指定 `--global` 参数
|
||||
- **THEN** 基础路径为当前工作目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须生成文件映射表
|
||||
|
||||
适配器必须生成源文件到目标文件的完整映射表,供事务性安装使用。
|
||||
|
||||
#### Scenario: Skill 文件映射
|
||||
|
||||
- **WHEN** 适配 skill 文件
|
||||
- **THEN** 系统返回源路径到目标路径的 map(包括所有子目录和文件)
|
||||
|
||||
#### Scenario: Command 文件映射(Claude)
|
||||
|
||||
- **WHEN** 适配 command 到 Claude Code
|
||||
- **THEN** 系统返回 `commands/<group>/<action>.md` → `<base>/.claude/commands/<group>/<action>.md` 的映射
|
||||
|
||||
#### Scenario: Command 文件映射(OpenCode)
|
||||
|
||||
- **WHEN** 适配 command 到 OpenCode
|
||||
- **THEN** 系统返回 `commands/<group>/<action>.md` → `<base>/commands/<group>-<action>.md` 的映射(全局时 base 为 `~/.config/opencode`,项目级时为 `./.opencode`)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理不支持的平台
|
||||
|
||||
工具必须拒绝处理未实现的平台。
|
||||
|
||||
#### Scenario: 不支持的平台参数
|
||||
|
||||
- **WHEN** 用户指定未实现的平台(如 `--platform cursor`)
|
||||
- **THEN** 系统报错"unsupported platform: cursor"
|
||||
@@ -0,0 +1,78 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以添加源仓库
|
||||
|
||||
工具必须允许用户添加 git 仓库作为 skills/commands 的源,并将配置持久化到 `~/.skillmgr/repository.json`。
|
||||
|
||||
#### Scenario: 成功添加新仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr add <git-url>` 命令
|
||||
- **THEN** 系统克隆仓库到 `~/.skillmgr/cache/` 并将配置写入 repository.json
|
||||
|
||||
#### Scenario: 添加已存在的仓库
|
||||
|
||||
- **WHEN** 用户添加已存在的仓库(同名)
|
||||
- **THEN** 系统提示"仓库名称已存在,请先使用 `skillmgr remove <name>` 移除",拒绝添加
|
||||
|
||||
#### Scenario: 指定仓库别名
|
||||
|
||||
- **WHEN** 用户使用 `--name` 参数指定仓库别名
|
||||
- **THEN** 系统使用指定的别名作为仓库名称
|
||||
|
||||
#### Scenario: 指定分支
|
||||
|
||||
- **WHEN** 用户使用 `--branch` 参数指定分支
|
||||
- **THEN** 系统克隆指定分支而非默认分支
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以移除源仓库
|
||||
|
||||
工具必须允许用户从配置中移除已添加的源仓库。
|
||||
|
||||
#### Scenario: 成功移除仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr remove <name>` 命令
|
||||
- **THEN** 系统从 repository.json 中删除对应配置
|
||||
|
||||
#### Scenario: 移除不存在的仓库
|
||||
|
||||
- **WHEN** 用户尝试移除不存在的仓库名称
|
||||
- **THEN** 系统提示仓库不存在,不报错
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以列出已配置的源仓库
|
||||
|
||||
工具必须提供命令列出所有已添加的源仓库及其信息。
|
||||
|
||||
#### Scenario: 列出所有仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list-repos` 命令
|
||||
- **THEN** 系统显示所有仓库的名称、URL、分支和添加时间
|
||||
|
||||
#### Scenario: 无已配置仓库
|
||||
|
||||
- **WHEN** 用户执行列表命令但 repository.json 为空
|
||||
- **THEN** 系统提示"无已配置的源仓库"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以同步源仓库
|
||||
|
||||
工具必须提供命令从远程拉取最新代码,更新本地缓存。
|
||||
|
||||
#### Scenario: 同步单个仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr sync <name>` 命令
|
||||
- **THEN** 系统对指定仓库执行 `git pull`
|
||||
|
||||
#### Scenario: 同步所有仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr sync` 不带参数
|
||||
- **THEN** 系统对所有已配置仓库执行 `git pull`
|
||||
|
||||
#### Scenario: Git 操作失败
|
||||
|
||||
- **WHEN** git pull 失败(网络错误、冲突等)
|
||||
- **THEN** 系统显示 git 错误信息并继续处理其他仓库
|
||||
@@ -0,0 +1,95 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以安装 skill 到全局目录
|
||||
|
||||
工具必须支持将 skill 安装到用户主目录下的平台配置目录(如 `~/.claude/skills/`)。
|
||||
|
||||
#### Scenario: 全局安装到 Claude Code
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform claude --global`
|
||||
- **THEN** 系统将 skill 复制到 `~/.claude/skills/<name>/`
|
||||
|
||||
#### Scenario: 全局安装到 OpenCode
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform opencode --global`
|
||||
- **THEN** 系统将 skill 复制到 `~/.config/opencode/skills/<name>/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以安装 skill 到项目目录
|
||||
|
||||
工具必须支持将 skill 安装到当前项目目录下的平台配置目录。
|
||||
|
||||
#### Scenario: 项目级安装到 Claude Code
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install skill <name> --platform claude`
|
||||
- **THEN** 系统将 skill 复制到 `./claude/skills/<name>/`
|
||||
|
||||
#### Scenario: 项目级安装到 OpenCode
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install skill <name> --platform opencode`
|
||||
- **THEN** 系统将 skill 复制到 `./.opencode/skills/<name>/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在所有源仓库中查找 skill
|
||||
|
||||
工具必须在所有已配置的源仓库缓存中搜索指定的 skill。
|
||||
|
||||
#### Scenario: 在第一个仓库找到
|
||||
|
||||
- **WHEN** 第一个仓库包含目标 skill
|
||||
- **THEN** 系统使用该仓库的 skill 进行安装
|
||||
|
||||
#### Scenario: 在后续仓库找到
|
||||
|
||||
- **WHEN** 前面的仓库不包含目标 skill,但后续仓库包含
|
||||
- **THEN** 系统使用找到的第一个匹配仓库
|
||||
|
||||
#### Scenario: 所有仓库都不包含
|
||||
|
||||
- **WHEN** 所有源仓库都不包含目标 skill
|
||||
- **THEN** 系统报错"skill '<name>' not found in any repository"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以临时指定源仓库
|
||||
|
||||
工具必须支持通过 `--from` 参数临时指定源仓库 URL,不保存到配置文件。
|
||||
|
||||
#### Scenario: 使用临时仓库安装
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform claude --global --from <git-url>`
|
||||
- **THEN** 系统从指定 URL 拉取仓库并安装,不修改 repository.json
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理目录已存在的情况
|
||||
|
||||
工具必须在安装前检查目标目录是否已存在,并根据情况处理。
|
||||
|
||||
#### Scenario: install.json 有记录且目录存在
|
||||
|
||||
- **WHEN** 目标 skill 已通过 skillmgr 安装
|
||||
- **THEN** 系统询问用户是否覆盖,默认为否
|
||||
|
||||
#### Scenario: install.json 无记录但目录存在
|
||||
|
||||
- **WHEN** 目标目录存在但不在 install.json 中
|
||||
- **THEN** 系统询问用户是否覆盖该目录,默认为否
|
||||
|
||||
#### Scenario: 用户拒绝覆盖
|
||||
|
||||
- **WHEN** 用户选择不覆盖
|
||||
- **THEN** 系统取消安装,不修改任何文件
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须记录安装操作
|
||||
|
||||
工具必须在成功安装后将记录写入 `~/.skillmgr/install.json`。
|
||||
|
||||
#### Scenario: 记录包含完整信息
|
||||
|
||||
- **WHEN** 安装成功完成
|
||||
- **THEN** 系统在 install.json 中添加包含 type、name、platform、scope、install_path、installed_at、updated_at 的记录
|
||||
@@ -0,0 +1,153 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 测试必须不污染用户环境
|
||||
|
||||
工具的所有测试必须通过环境变量隔离配置和安装目录,不影响用户的实际数据和系统配置。
|
||||
|
||||
#### Scenario: 配置目录隔离
|
||||
|
||||
- **WHEN** 测试运行时设置 `SKILLMGR_TEST_ROOT` 环境变量
|
||||
- **THEN** 系统使用该环境变量指定的目录作为配置根目录,而非 `~/.skillmgr/`
|
||||
|
||||
#### Scenario: 安装目标目录隔离
|
||||
|
||||
- **WHEN** 测试运行时设置 `SKILLMGR_TEST_BASE` 环境变量
|
||||
- **THEN** 系统使用该环境变量指定的目录作为全局/项目基础路径,而非用户主目录或当前工作目录
|
||||
|
||||
#### Scenario: 生产模式不受影响
|
||||
|
||||
- **WHEN** 环境变量未设置(生产模式)
|
||||
- **THEN** 系统使用默认路径(`~/.skillmgr/`、`~/.claude/` 等)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须自动清理临时资源
|
||||
|
||||
所有测试创建的临时目录、文件和 git 仓库必须在测试结束后自动清理,不留垃圾文件。
|
||||
|
||||
#### Scenario: 使用 Go 测试框架自动清理
|
||||
|
||||
- **WHEN** 测试使用 `t.TempDir()` 创建临时目录
|
||||
- **THEN** Go 测试框架在测试结束时自动删除该目录及其所有内容
|
||||
|
||||
#### Scenario: 测试失败时也清理
|
||||
|
||||
- **WHEN** 测试失败或 panic
|
||||
- **THEN** 临时资源仍然被自动清理
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须支持并行执行
|
||||
|
||||
测试设计必须允许多个测试并行运行,互不干扰,充分利用多核性能。
|
||||
|
||||
#### Scenario: 独立测试环境
|
||||
|
||||
- **WHEN** 使用 `go test -parallel N` 并行运行多个测试
|
||||
- **THEN** 每个测试使用独立的临时目录,不产生竞态条件
|
||||
|
||||
#### Scenario: 配置隔离
|
||||
|
||||
- **WHEN** 多个测试同时设置环境变量
|
||||
- **THEN** 每个测试的环境变量设置独立生效(通过 t.Setenv 或 defer os.Unsetenv)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户交互必须可 mock
|
||||
|
||||
所有涉及用户输入的功能必须支持测试时注入 mock 输入,不依赖真实的标准输入。
|
||||
|
||||
#### Scenario: Mock 用户确认输入
|
||||
|
||||
- **WHEN** 测试需要模拟用户输入 "y" 或 "n"
|
||||
- **THEN** `prompt.ConfirmWithReader` 函数接受 `io.Reader` 参数,测试时传入 `strings.NewReader("y\n")`
|
||||
|
||||
#### Scenario: 生产模式使用真实输入
|
||||
|
||||
- **WHEN** 生产代码调用 `prompt.Confirm`
|
||||
- **THEN** 内部调用 `ConfirmWithReader(message, os.Stdin)` 读取真实用户输入
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须使用真实文件系统
|
||||
|
||||
测试应使用真实的文件系统操作和 git 命令,而非 mock,以确保行为与生产一致。
|
||||
|
||||
#### Scenario: 真实文件复制测试
|
||||
|
||||
- **WHEN** 测试文件复制功能
|
||||
- **THEN** 在临时目录中创建真实文件,执行复制,验证结果
|
||||
|
||||
#### Scenario: 真实 git 操作测试
|
||||
|
||||
- **WHEN** 测试 git clone/pull 功能
|
||||
- **THEN** 在临时目录中初始化真实的 git 仓库,执行 git 命令
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试数据必须可复用
|
||||
|
||||
测试应提供标准的 fixture 数据和辅助函数,避免每个测试重复构建测试环境。
|
||||
|
||||
#### Scenario: Fixture 仓库
|
||||
|
||||
- **WHEN** 测试需要一个标准的 skills/commands 仓库
|
||||
- **THEN** 从 `testdata/fixtures/test-repo/` 复制 fixture 并初始化为 git 仓库
|
||||
|
||||
#### Scenario: 测试辅助函数
|
||||
|
||||
- **WHEN** 测试需要设置隔离环境
|
||||
- **THEN** 调用 `setupTestEnv(t)` 函数自动设置环境变量和临时目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试覆盖必须全面
|
||||
|
||||
测试必须覆盖所有核心模块、关键路径和边界条件。
|
||||
|
||||
#### Scenario: 单元测试覆盖
|
||||
|
||||
- **WHEN** 实现任何核心函数(config、adapter、repo、installer)
|
||||
- **THEN** 必须编写对应的单元测试,覆盖正常和异常情况
|
||||
|
||||
#### Scenario: 集成测试覆盖
|
||||
|
||||
- **WHEN** 实现跨模块功能(完整安装流程)
|
||||
- **THEN** 必须编写集成测试,验证端到端行为
|
||||
|
||||
#### Scenario: 平台兼容性测试
|
||||
|
||||
- **WHEN** 支持多个平台(Claude Code、OpenCode)
|
||||
- **THEN** 每个平台都必须有独立的兼容性测试
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试脚本必须简化运行
|
||||
|
||||
必须提供自动化脚本,简化测试环境设置和测试执行。
|
||||
|
||||
#### Scenario: 自动化测试脚本
|
||||
|
||||
- **WHEN** 开发者运行 `./scripts/test.sh`
|
||||
- **THEN** 脚本自动设置测试环境变量、运行所有测试、清理临时资源
|
||||
|
||||
#### Scenario: 沙盒测试环境
|
||||
|
||||
- **WHEN** 开发者运行 `./scripts/sandbox.sh`
|
||||
- **THEN** 脚本创建隔离的沙盒环境,允许手动测试而不影响系统
|
||||
|
||||
---
|
||||
|
||||
### Requirement: CI/CD 集成必须无缝
|
||||
|
||||
测试必须能在 CI/CD 环境中稳定运行,不依赖本地环境配置。
|
||||
|
||||
#### Scenario: GitHub Actions 集成
|
||||
|
||||
- **WHEN** 在 CI 中运行测试
|
||||
- **THEN** 通过环境变量设置测试路径,所有测试通过且自动清理
|
||||
|
||||
#### Scenario: 测试失败报告
|
||||
|
||||
- **WHEN** 测试失败
|
||||
- **THEN** CI 系统捕获失败信息、日志和覆盖率报告
|
||||
@@ -0,0 +1,126 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须使用临时目录进行 staging
|
||||
|
||||
工具必须在系统临时目录中创建 staging 区域,组装完整的目标文件树后再移动到最终位置。
|
||||
|
||||
#### Scenario: 创建 staging 目录
|
||||
|
||||
- **WHEN** 开始安装事务
|
||||
- **THEN** 系统在 `/tmp/` 或 `os.TempDir()` 创建唯一的临时目录(如 `skillmgr-xxxxx/`)
|
||||
|
||||
#### Scenario: Staging 目录结构与目标一致
|
||||
|
||||
- **WHEN** 在 staging 中组装文件
|
||||
- **THEN** staging 目录结构必须与最终目标目录结构完全一致
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须先完整复制到 staging
|
||||
|
||||
工具必须将所有源文件完整复制到 staging 目录,应用平台适配规则。
|
||||
|
||||
#### Scenario: 复制所有文件到 staging
|
||||
|
||||
- **WHEN** 执行 Stage 阶段
|
||||
- **THEN** 系统根据平台适配器返回的文件映射,将所有文件复制到 staging
|
||||
|
||||
#### Scenario: 复制失败回滚
|
||||
|
||||
- **WHEN** 复制过程中任何文件失败
|
||||
- **THEN** 系统删除 staging 目录,报错终止,不影响目标目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须验证 staging 完整性
|
||||
|
||||
工具必须在提交前验证 staging 目录中的文件完整性。
|
||||
|
||||
#### Scenario: 验证文件数量
|
||||
|
||||
- **WHEN** 所有文件复制到 staging
|
||||
- **THEN** 系统验证 staging 中文件数量与预期映射表一致
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须原子性提交 staging
|
||||
|
||||
工具必须在 staging 验证通过后,将整个 staging 目录移动到目标位置。
|
||||
|
||||
#### Scenario: 同文件系统移动
|
||||
|
||||
- **WHEN** staging 和目标在同一文件系统
|
||||
- **THEN** 系统使用 `os.Rename()` 原子性移动
|
||||
|
||||
#### Scenario: 跨文件系统复制
|
||||
|
||||
- **WHEN** `os.Rename()` 失败(跨文件系统)
|
||||
- **THEN** 系统 fallback 到递归复制 + 删除 staging
|
||||
|
||||
#### Scenario: 覆盖已存在目录
|
||||
|
||||
- **WHEN** 目标目录已存在(用户已确认覆盖)
|
||||
- **THEN** 系统先删除目标目录,再移动 staging
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在失败时自动回滚
|
||||
|
||||
工具必须在任何阶段失败时,自动清理 staging 目录,不留垃圾文件。
|
||||
|
||||
#### Scenario: Stage 阶段失败
|
||||
|
||||
- **WHEN** 文件复制到 staging 失败
|
||||
- **THEN** 系统删除 staging 目录,不修改目标
|
||||
|
||||
#### Scenario: Commit 阶段失败
|
||||
|
||||
- **WHEN** 移动 staging 到目标失败
|
||||
- **THEN** 系统删除 staging 目录,目标目录保持原状(或已删除的状态)
|
||||
|
||||
#### Scenario: defer 确保清理
|
||||
|
||||
- **WHEN** 事务对象被销毁
|
||||
- **THEN** 系统使用 defer 确保 staging 目录被清理
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须提供事务接口
|
||||
|
||||
工具必须提供清晰的事务接口,包含 Stage、Commit、Rollback 方法。
|
||||
|
||||
#### Scenario: 创建事务对象
|
||||
|
||||
- **WHEN** 开始安装流程
|
||||
- **THEN** 系统创建 Transaction 对象,包含 stagingDir、targetDir、fileMap 字段
|
||||
|
||||
#### Scenario: 调用 Stage 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Stage()`
|
||||
- **THEN** 系统执行文件复制到 staging
|
||||
|
||||
#### Scenario: 调用 Commit 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Commit()`
|
||||
- **THEN** 系统将 staging 移动到目标
|
||||
|
||||
#### Scenario: 调用 Rollback 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Rollback()`
|
||||
- **THEN** 系统删除 staging 目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须确保目标目录的父目录存在
|
||||
|
||||
工具必须在提交前确保目标目录的父目录已创建。
|
||||
|
||||
#### Scenario: 创建父目录
|
||||
|
||||
- **WHEN** 提交 staging 到目标
|
||||
- **THEN** 系统先创建目标目录的父目录(如 `~/.claude/skills/`)
|
||||
|
||||
#### Scenario: 父目录创建失败
|
||||
|
||||
- **WHEN** 父目录创建失败(权限不足等)
|
||||
- **THEN** 系统报错,回滚 staging
|
||||
191
openspec/changes/archive/2026-02-25-build-skillmgr-cli/tasks.md
Normal file
191
openspec/changes/archive/2026-02-25-build-skillmgr-cli/tasks.md
Normal file
@@ -0,0 +1,191 @@
|
||||
## 1. 项目初始化
|
||||
|
||||
- [x] 1.1 在 `manager/` 目录创建 Go 项目,初始化 go.mod(module: skillmgr)
|
||||
- [x] 1.2 创建项目目录结构(cmd/、internal/、pkg/、testdata/)
|
||||
- [x] 1.3 添加 Cobra 依赖(github.com/spf13/cobra)
|
||||
- [x] 1.4 创建 .gitignore 文件
|
||||
- [x] 1.5 创建测试脚本目录(scripts/)
|
||||
|
||||
## 2. 核心类型定义
|
||||
|
||||
- [x] 2.1 创建 `internal/types/types.go`,定义 Platform、ItemType、Scope 枚举类型
|
||||
- [x] 2.2 定义 Repository 结构体(name、url、branch、added_at)
|
||||
- [x] 2.3 定义 RepositoryConfig 结构体(repositories 数组)
|
||||
- [x] 2.4 定义 InstallRecord 结构体(type、name、source_repo、platform、scope、install_path、installed_at、updated_at)
|
||||
- [x] 2.5 定义 InstallConfig 结构体(installations 数组)
|
||||
- [x] 2.6 定义 SkillMetadata 和 CommandGroup 结构体
|
||||
|
||||
## 3. 配置管理模块
|
||||
|
||||
- [x] 3.1 创建 `internal/config/paths.go`,实现配置目录路径函数(GetConfigRoot、GetRepositoryConfigPath、GetInstallConfigPath、GetCachePath)
|
||||
- [x] 3.2 在 GetConfigRoot 中添加环境变量 SKILLMGR_TEST_ROOT 支持(测试隔离)
|
||||
- [x] 3.3 实现 EnsureConfigDirs 函数,确保配置目录存在
|
||||
- [x] 3.4 创建 `internal/config/repository.go`,实现 LoadRepositoryConfig 和 SaveRepositoryConfig
|
||||
- [x] 3.5 实现 AddRepository 函数(检查同名仓库,存在则拒绝并提示先移除)
|
||||
- [x] 3.6 实现 RemoveRepository 和 FindRepository 函数
|
||||
- [x] 3.7 创建 `internal/config/install.go`,实现 LoadInstallConfig 和 SaveInstallConfig
|
||||
- [x] 3.8 实现 AddInstallRecord、RemoveInstallRecord、FindInstallRecord、UpdateInstallRecord 函数
|
||||
- [x] 3.9 实现 CleanOrphanRecords 函数(扫描并清理安装路径不存在的记录)
|
||||
|
||||
## 4. 文件工具模块
|
||||
|
||||
- [x] 4.1 创建 `pkg/fileutil/fileutil.go`
|
||||
- [x] 4.2 实现 CopyFile 函数(复制单个文件并保留权限)
|
||||
- [x] 4.3 实现 CopyDir 函数(递归复制目录)
|
||||
|
||||
## 5. Git 仓库管理模块
|
||||
|
||||
- [x] 5.1 创建 `internal/repo/git.go`
|
||||
- [x] 5.2 实现 URLToPathName 函数(将 git URL 转换为缓存目录名)
|
||||
- [x] 5.3 实现 CloneOrPull 函数(检查仓库是否存在,不存在则 clone,存在则 pull)
|
||||
- [x] 5.4 实现 cloneRepo 函数(执行 git clone --depth 1)
|
||||
- [x] 5.5 实现 pullRepo 函数(执行 git pull)
|
||||
- [x] 5.6 创建 `internal/repo/scanner.go`
|
||||
- [x] 5.7 实现 ScanSkills 函数(扫描 skills/ 目录,返回 skill 列表)
|
||||
- [x] 5.8 实现 ScanCommands 函数(扫描 commands/ 目录,返回命令组列表)
|
||||
- [x] 5.9 实现 FindSkill 函数(在所有仓库缓存中查找指定 skill)
|
||||
- [x] 5.10 实现 FindCommand 函数(在所有仓库缓存中查找指定命令组)
|
||||
|
||||
## 6. 平台适配器模块
|
||||
|
||||
- [x] 6.1 创建 `internal/adapter/adapter.go`,定义 PlatformAdapter 接口
|
||||
- [x] 6.2 实现 GetAdapter 函数(根据 Platform 返回对应适配器,不支持则报错)
|
||||
- [x] 6.3 实现 getBasePath 辅助函数(根据 Scope 返回基础路径,支持 SKILLMGR_TEST_BASE 环境变量)
|
||||
- [x] 6.4 创建 `internal/adapter/claude.go`,实现 ClaudeAdapter 结构体
|
||||
- [x] 6.5 实现 Claude 的 GetSkillInstallPath 和 GetCommandInstallPath 方法
|
||||
- [x] 6.6 实现 Claude 的 AdaptSkill 方法(遍历源目录,生成文件映射)
|
||||
- [x] 6.7 实现 Claude 的 AdaptCommand 方法(保持目录结构)
|
||||
- [x] 6.8 创建 `internal/adapter/opencode.go`,实现 OpenCodeAdapter 结构体
|
||||
- [x] 6.9 实现 OpenCode 的 GetSkillInstallPath(注意 command 是单数)和 GetCommandInstallPath 方法
|
||||
- [x] 6.10 实现 OpenCode 的 AdaptSkill 方法(与 Claude 相同)
|
||||
- [x] 6.11 实现 OpenCode 的 AdaptCommand 方法(扁平化文件名:<group>-<action>.md)
|
||||
|
||||
## 7. 事务性安装模块
|
||||
|
||||
- [x] 7.1 创建 `internal/installer/transaction.go`
|
||||
- [x] 7.2 定义 Transaction 结构体(stagingDir、targetDir、fileMap)
|
||||
- [x] 7.3 实现 NewTransaction 函数(在系统临时目录创建 staging)
|
||||
- [x] 7.4 实现 Stage 方法(复制所有文件到 staging,创建必要的子目录)
|
||||
- [x] 7.5 实现 Commit 方法(确保父目录存在,删除已存在的目标,移动 staging 到目标)
|
||||
- [x] 7.6 处理 Commit 中的跨文件系统情况(Rename 失败则 fallback 到 CopyDir)
|
||||
- [x] 7.7 实现 Rollback 方法(删除 staging 目录)
|
||||
- [x] 7.8 在 NewTransaction 中使用 defer 确保清理
|
||||
|
||||
## 8. 用户交互模块
|
||||
|
||||
- [x] 8.1 创建 `internal/prompt/prompt.go`
|
||||
- [x] 8.2 实现 ConfirmWithReader 函数(接受 io.Reader,支持测试 mock)
|
||||
- [x] 8.3 实现 Confirm 函数(调用 ConfirmWithReader,使用 os.Stdin)
|
||||
|
||||
## 9. 安装器模块
|
||||
|
||||
- [x] 9.1 创建 `internal/installer/installer.go`
|
||||
- [x] 9.2 实现 checkExistingInstallation 函数(检查 install.json 记录和目录存在性,询问用户是否覆盖)
|
||||
- [x] 9.3 实现 InstallSkill 函数(查找 skill、获取适配器、确定路径、检查冲突、适配、事务安装、记录)
|
||||
- [x] 9.4 实现 InstallCommand 函数(查找 command、获取适配器、确定路径、检查冲突、适配、事务安装、记录)
|
||||
- [x] 9.5 创建 `internal/installer/uninstaller.go`
|
||||
- [x] 9.6 实现 UninstallSkill 函数(查找记录、删除目录、移除记录)
|
||||
- [x] 9.7 实现 UninstallCommand 函数(查找记录、删除目录或文件、移除记录)
|
||||
|
||||
## 10. CLI 根命令
|
||||
|
||||
- [x] 10.1 创建 `cmd/skillmgr/root.go`
|
||||
- [x] 10.2 定义 rootCmd,设置 Use、Short、Long
|
||||
- [x] 10.3 实现 Execute 函数
|
||||
- [x] 10.4 在 init 中调用 config.EnsureConfigDirs 初始化配置目录
|
||||
- [x] 10.5 创建 `cmd/skillmgr/main.go`,调用 Execute
|
||||
|
||||
## 11. 仓库管理命令
|
||||
|
||||
- [x] 11.1 创建 `cmd/skillmgr/add.go`,实现 addCmd
|
||||
- [x] 11.2 添加 --name 和 --branch 参数
|
||||
- [x] 11.3 实现 RunE:解析参数、调用 repo.CloneOrPull、调用 config.AddRepository、显示成功信息
|
||||
- [x] 11.4 创建 `cmd/skillmgr/remove.go`,实现 removeCmd
|
||||
- [x] 11.5 实现 RunE:调用 config.RemoveRepository
|
||||
- [x] 11.6 创建 `cmd/skillmgr/list_repos.go`,实现 listReposCmd
|
||||
- [x] 11.7 实现 RunE:调用 config.LoadRepositoryConfig、格式化输出
|
||||
- [x] 11.8 创建 `cmd/skillmgr/sync.go`,实现 syncCmd
|
||||
- [x] 11.9 实现 RunE:支持指定仓库名或同步所有,调用 repo.CloneOrPull
|
||||
|
||||
## 12. 安装命令
|
||||
|
||||
- [x] 12.1 创建 `cmd/skillmgr/install.go`,实现 installCmd
|
||||
- [x] 12.2 添加 --platform(必需)、--global、--from 参数
|
||||
- [x] 12.3 实现 Args 验证(必须有 2 个参数:type 和 name)
|
||||
- [x] 12.4 实现 RunE:解析 type(skill/command)、调用对应安装函数
|
||||
- [x] 12.5 处理 --from 参数(TODO:临时仓库,暂时跳过实现)
|
||||
|
||||
## 13. 追踪管理命令
|
||||
|
||||
- [x] 13.1 创建 `cmd/skillmgr/list.go`,实现 listCmd
|
||||
- [x] 13.2 添加 --type、--platform、--global 参数
|
||||
- [x] 13.3 实现 RunE:加载 install.json、根据参数过滤、格式化输出
|
||||
- [x] 13.4 创建 `cmd/skillmgr/uninstall.go`,实现 uninstallCmd
|
||||
- [x] 13.5 添加 --platform(必需)、--global 参数
|
||||
- [x] 13.6 实现 Args 验证和 RunE:调用对应卸载函数
|
||||
- [x] 13.7 创建 `cmd/skillmgr/update.go`,实现 updateCmd
|
||||
- [x] 13.8 添加 --platform、--global、--all 参数
|
||||
- [x] 13.9 实现 RunE:支持更新单个或全部已安装项
|
||||
- [x] 13.10 创建 `cmd/skillmgr/clean.go`,实现 cleanCmd
|
||||
- [x] 13.11 实现 RunE:调用 config.CleanOrphanRecords、显示清理结果
|
||||
|
||||
## 14. 搜索命令(可选)
|
||||
|
||||
- [x] 14.1 创建 `cmd/skillmgr/search.go`,实现 searchCmd
|
||||
- [x] 14.2 实现 RunE:遍历所有仓库缓存、扫描 skills 和 commands、匹配关键词、输出结果
|
||||
|
||||
## 15. 错误处理和用户体验优化
|
||||
|
||||
- [x] 15.1 确保所有 Git 操作失败时显示清晰错误信息
|
||||
- [x] 15.2 安装/卸载成功时显示 ✓ 符号和路径信息
|
||||
- [x] 15.3 配置文件解析错误时提示用户检查 JSON 格式
|
||||
- [x] 15.4 未找到 skill/command 时列出可用项
|
||||
|
||||
## 16. 测试基础设施
|
||||
|
||||
- [x] 16.1 创建 `testdata/fixtures/` 目录
|
||||
- [x] 16.2 创建测试用 fixture 仓库(test-repo,包含 2 个 skills 和 1 个 command 组)
|
||||
- [x] 16.3 编写测试辅助函数 setupTestRepo(初始化临时 git 仓库)
|
||||
- [x] 16.4 编写测试辅助函数 copyFixtureRepo(复制 fixture 并初始化 git)
|
||||
- [x] 16.5 创建 `scripts/test.sh`(设置测试环境变量并运行测试)
|
||||
- [x] 16.6 创建 `scripts/sandbox.sh`(手动测试沙盒环境)
|
||||
|
||||
## 17. 单元测试
|
||||
|
||||
- [x] 17.1 创建 `internal/config/paths_test.go`,测试环境变量覆盖
|
||||
- [x] 17.2 创建 `internal/config/repository_test.go`,测试仓库配置增删改查
|
||||
- [x] 17.3 测试 AddRepository 拒绝同名仓库场景
|
||||
- [x] 17.4 创建 `internal/config/install_test.go`,测试安装记录管理
|
||||
- [x] 17.5 测试 CleanOrphanRecords 功能
|
||||
- [x] 17.6 创建 `internal/adapter/claude_test.go`,测试路径计算和文件映射
|
||||
- [x] 17.7 创建 `internal/adapter/opencode_test.go`,测试扁平化命名转换
|
||||
- [x] 17.8 创建 `internal/repo/git_test.go`,测试 URL 转换
|
||||
- [x] 17.9 创建 `internal/installer/transaction_test.go`,测试 Stage/Commit/Rollback
|
||||
- [x] 17.10 创建 `internal/prompt/prompt_test.go`,测试用户输入 mock
|
||||
- [x] 17.11 创建 `pkg/fileutil/fileutil_test.go`,测试文件复制
|
||||
|
||||
## 18. 集成测试
|
||||
|
||||
- [x] 18.1 创建 `internal/installer/installer_test.go`
|
||||
- [x] 18.2 测试完整安装流程(add repo → install skill → 验证文件和记录)
|
||||
- [x] 18.3 测试冲突覆盖场景(已安装 → 再次安装 → 用户确认)
|
||||
- [x] 18.4 测试事务回滚(Stage 失败 → 验证 staging 清理)
|
||||
- [x] 18.5 测试卸载流程(install → uninstall → 验证删除)
|
||||
- [x] 18.6 测试更新流程(install → update → 验证更新)
|
||||
- [x] 18.7 测试清理孤立记录(install → 手动删除 → clean)
|
||||
- [x] 18.8 测试 Claude Code 平台安装(skill 和 command)
|
||||
- [x] 18.9 测试 OpenCode 平台安装(skill 和 command 扁平化)
|
||||
|
||||
## 19. 构建和手动验证
|
||||
|
||||
- [x] 19.1 编写 Makefile 或构建脚本,支持 `go build -o skillmgr`
|
||||
- [x] 19.2 在沙盒环境手动测试基本流程
|
||||
- [x] 19.3 验证全局和项目级安装
|
||||
- [x] 19.4 验证两个平台的适配正确性
|
||||
|
||||
## 20. 文档
|
||||
|
||||
- [x] 20.1 编写 README.md,包含安装说明、使用示例、命令参考
|
||||
- [x] 20.2 记录配置文件格式和路径
|
||||
- [x] 20.3 添加常见问题和故障排除指南
|
||||
- [x] 20.4 添加测试说明(如何运行测试、测试环境变量)
|
||||
Reference in New Issue
Block a user