cangming-ai-dev-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/adapters/claude-code/.claude-plugin/plugin.json +20 -0
- data/adapters/claude-code/agents/coder.md +22 -0
- data/adapters/claude-code/agents/planner.md +24 -0
- data/adapters/claude-code/agents/reviewer.md +23 -0
- data/adapters/claude-code/hooks/hooks.json +16 -0
- data/adapters/codex/.codex-plugin/plugin.json +26 -0
- data/adapters/codex/AGENTS.md.template +45 -0
- data/core/references/review_checklist.md +48 -0
- data/core/references/verify_rules.md +48 -0
- data/core/references/workflow.md +65 -0
- data/core/scripts/detect_project_type.sh +49 -0
- data/core/scripts/summarize_context.sh +55 -0
- data/core/scripts/verify_project.sh +61 -0
- data/core/skills/code-review/SKILL.md +71 -0
- data/core/skills/plan-first/SKILL.md +56 -0
- data/core/skills/safe-code-change/SKILL.md +48 -0
- data/core/skills/verify-before-done/SKILL.md +71 -0
- data/domains/harmony/references/arkts_syntax.md +90 -0
- data/domains/harmony/references/arkui_rules.md +81 -0
- data/domains/harmony/references/common_compile_errors.md +40 -0
- data/domains/harmony/references/harmony_project_structure.md +56 -0
- data/domains/harmony/references/hvigor_ohpm_hdc_commands.md +40 -0
- data/domains/harmony/scripts/build_hap.sh +39 -0
- data/domains/harmony/scripts/lint_harmony.sh +39 -0
- data/domains/harmony/scripts/test_harmony.sh +32 -0
- data/domains/harmony/scripts/verify_harmony.sh +92 -0
- data/domains/harmony/skills/harmony-code/SKILL.md +73 -0
- data/domains/harmony/skills/harmony-plan/SKILL.md +68 -0
- data/domains/harmony/skills/harmony-verify/SKILL.md +68 -0
- data/exe/cangming-dev-kit +6 -0
- data/lib/cangming_ai_dev_kit/adapter.rb +68 -0
- data/lib/cangming_ai_dev_kit/cli.rb +88 -0
- data/lib/cangming_ai_dev_kit/commands/init.rb +53 -0
- data/lib/cangming_ai_dev_kit/commands/sync.rb +25 -0
- data/lib/cangming_ai_dev_kit/commands/verify.rb +32 -0
- data/lib/cangming_ai_dev_kit/syncer.rb +102 -0
- data/lib/cangming_ai_dev_kit/verifier.rb +134 -0
- data/lib/cangming_ai_dev_kit/version.rb +5 -0
- data/lib/cangming_ai_dev_kit.rb +7 -0
- data/marketplace/.claude-plugin/marketplace.json +21 -0
- metadata +88 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# harmony-code — HarmonyOS 代码编写
|
|
2
|
+
|
|
3
|
+
## description
|
|
4
|
+
在纯血 HarmonyOS 项目中使用 ArkTS / ArkUI 编写高质量代码。
|
|
5
|
+
|
|
6
|
+
## when to use
|
|
7
|
+
- 编写 HarmonyOS 页面/组件
|
|
8
|
+
- 实现 HarmonyOS 业务逻辑
|
|
9
|
+
- 重构 HarmonyOS 代码
|
|
10
|
+
|
|
11
|
+
## 核心规则
|
|
12
|
+
|
|
13
|
+
### 1. 只写 HarmonyOS 代码
|
|
14
|
+
- **不写** Flutter / Android / iOS 代码
|
|
15
|
+
- 使用 ArkTS(兼容 TypeScript 语法子集)
|
|
16
|
+
- 使用 ArkUI 声明式 UI
|
|
17
|
+
|
|
18
|
+
### 2. 分层架构
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
page/ — 页面层(组合组件、管理页面状态)
|
|
22
|
+
components/ — 可复用组件层
|
|
23
|
+
model/ — 数据模型层
|
|
24
|
+
service/ — 网络请求/数据处理层
|
|
25
|
+
utils/ — 工具函数层
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 3. 不把复杂逻辑塞进 build()
|
|
29
|
+
❌ 错误:
|
|
30
|
+
```typescript
|
|
31
|
+
build() {
|
|
32
|
+
// 100 行复杂逻辑
|
|
33
|
+
let result = complexCalculation(); // 不要!
|
|
34
|
+
this.dataList = transformData(rawData); // 不要!
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
✅ 正确:
|
|
39
|
+
```typescript
|
|
40
|
+
aboutToAppear() {
|
|
41
|
+
this.initData();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
build() {
|
|
45
|
+
Column() {
|
|
46
|
+
TitleBar({ title: this.title });
|
|
47
|
+
ContentList({ data: this.dataList });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private initData(): void {
|
|
52
|
+
this.dataList = this.transformData(rawData);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. 状态管理
|
|
57
|
+
- 页面级状态用 @State + @Link
|
|
58
|
+
- 跨页面状态用 AppStorage
|
|
59
|
+
- 组件级状态用 @Prop
|
|
60
|
+
- 深层传递用 @Provide + @Consume
|
|
61
|
+
|
|
62
|
+
### 5. 修改后验证
|
|
63
|
+
每次修改后必须执行:
|
|
64
|
+
```bash
|
|
65
|
+
bash domains/harmony/scripts/verify_harmony.sh
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## done criteria
|
|
69
|
+
- [ ] 符合 HarmonyOS 分层架构
|
|
70
|
+
- [ ] build() 中无复杂逻辑
|
|
71
|
+
- [ ] 状态管理使用正确
|
|
72
|
+
- [ ] 无跨平台代码混入
|
|
73
|
+
- [ ] 验证脚本通过
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# harmony-plan — HarmonyOS 项目计划
|
|
2
|
+
|
|
3
|
+
## description
|
|
4
|
+
针对纯血 HarmonyOS(鸿蒙原生)项目的计划制定。覆盖 ArkTS、ArkUI、路由、状态管理等关键环节。
|
|
5
|
+
|
|
6
|
+
## when to use
|
|
7
|
+
- 启动新的 HarmonyOS 项目
|
|
8
|
+
- 为 HarmonyOS 项目添加新功能
|
|
9
|
+
- HarmonyOS 项目重构
|
|
10
|
+
|
|
11
|
+
## workflow
|
|
12
|
+
1. 确认是纯血 HarmonyOS 项目(非 Android 套壳)
|
|
13
|
+
2. 阅读项目结构(src/ets 目录结构)
|
|
14
|
+
3. 页面拆解和路由设计
|
|
15
|
+
4. 状态管理层设计
|
|
16
|
+
5. 权限声明检查
|
|
17
|
+
6. 本地存储方案确定
|
|
18
|
+
7. 服务层设计
|
|
19
|
+
8. 测试与构建方案
|
|
20
|
+
9. 输出计划
|
|
21
|
+
|
|
22
|
+
## 检查清单
|
|
23
|
+
|
|
24
|
+
### 页面拆解
|
|
25
|
+
- [ ] 列出所有页面及导航关系
|
|
26
|
+
- [ ] 每个页面的输入和输出定义
|
|
27
|
+
- [ ] 公共组件复用拆分
|
|
28
|
+
|
|
29
|
+
### 路由
|
|
30
|
+
- [ ] router.pushUrl / router.replaceUrl 使用
|
|
31
|
+
- [ ] 路由参数类型定义
|
|
32
|
+
- [ ] 页面返回逻辑
|
|
33
|
+
|
|
34
|
+
### 状态管理
|
|
35
|
+
- [ ] @State / @Prop / @Link / @Provide + @Consume
|
|
36
|
+
- [ ] AppStorage / LocalStorage 使用
|
|
37
|
+
- [ ] 全局状态 vs 局部状态
|
|
38
|
+
|
|
39
|
+
### 权限
|
|
40
|
+
- [ ] oh-permission 声明
|
|
41
|
+
- [ ] 动态权限申请
|
|
42
|
+
- [ ] 权限被拒绝的处理
|
|
43
|
+
|
|
44
|
+
### 本地存储
|
|
45
|
+
- [ ] Preferences 使用
|
|
46
|
+
- [ ] 数据库(关系型/RDB)使用
|
|
47
|
+
- [ ] 文件存储
|
|
48
|
+
|
|
49
|
+
### 服务层
|
|
50
|
+
- [ ] http 请求封装
|
|
51
|
+
- [ ] 数据模型定义
|
|
52
|
+
- [ ] 错误处理
|
|
53
|
+
|
|
54
|
+
### 测试与构建
|
|
55
|
+
- [ ] ohpm install 依赖
|
|
56
|
+
- [ ] hvigorw assembleHap 构建
|
|
57
|
+
- [ ] hdc 安装部署
|
|
58
|
+
|
|
59
|
+
## output format
|
|
60
|
+
遵循 core/skills/plan-first/SKILL.md 的输出格式,加上 HarmonyOS 专项检查结果。
|
|
61
|
+
|
|
62
|
+
## done criteria
|
|
63
|
+
- [ ] 页面拆解完成
|
|
64
|
+
- [ ] 路由方案确定
|
|
65
|
+
- [ ] 状态管理方案确定
|
|
66
|
+
- [ ] 权限列表明确
|
|
67
|
+
- [ ] 存储方案确定
|
|
68
|
+
- [ ] 构建验证方案确定
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# harmony-verify — HarmonyOS 项目验证
|
|
2
|
+
|
|
3
|
+
## description
|
|
4
|
+
对 HarmonyOS 项目执行完整的验证流程:依赖安装、lint、测试、构建。
|
|
5
|
+
|
|
6
|
+
## when to use
|
|
7
|
+
- HarmonyOS 代码修改完成后
|
|
8
|
+
- 构建部署前
|
|
9
|
+
- PR 提交前
|
|
10
|
+
|
|
11
|
+
## workflow
|
|
12
|
+
1. 确认当前目录是 HarmonyOS 项目
|
|
13
|
+
2. 执行 `ohpm install`(如果 ohpm 可用)
|
|
14
|
+
3. 执行 lint 检查
|
|
15
|
+
4. 执行测试
|
|
16
|
+
5. 执行 `hvigorw assembleHap` 构建
|
|
17
|
+
6. 记录所有结果
|
|
18
|
+
|
|
19
|
+
## 验证步骤
|
|
20
|
+
|
|
21
|
+
### 1. 依赖安装
|
|
22
|
+
```bash
|
|
23
|
+
ohpm install
|
|
24
|
+
```
|
|
25
|
+
- 如果 ohpm 不存在:输出 SKIP,原因是命令行工具未安装
|
|
26
|
+
- 如果执行失败:输出 FAIL,需要修复依赖
|
|
27
|
+
|
|
28
|
+
### 2. Lint
|
|
29
|
+
使用 `hvigorw lint` 或等效命令。
|
|
30
|
+
- 如果命令不存在:输出 SKIP
|
|
31
|
+
- 不伪造结果
|
|
32
|
+
|
|
33
|
+
### 3. 测试
|
|
34
|
+
```bash
|
|
35
|
+
./hvigorw test
|
|
36
|
+
```
|
|
37
|
+
- 如果命令不存在:输出 SKIP
|
|
38
|
+
- 不伪造结果
|
|
39
|
+
|
|
40
|
+
### 4. 构建
|
|
41
|
+
```bash
|
|
42
|
+
./hvigorw assembleHap
|
|
43
|
+
```
|
|
44
|
+
- 如果 hvigorw 不存在:输出 SKIP
|
|
45
|
+
- 不伪造结果
|
|
46
|
+
|
|
47
|
+
## output format
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
## HarmonyOS 验证报告
|
|
51
|
+
|
|
52
|
+
| 步骤 | 命令 | 结果 | 说明 |
|
|
53
|
+
|------|------|------|------|
|
|
54
|
+
| install | ohpm install | PASS | / |
|
|
55
|
+
| lint | hvigorw lint | SKIP | 命令不存在 |
|
|
56
|
+
| test | hvigorw test | SKIP | 命令不存在 |
|
|
57
|
+
| build | hvigorw assembleHap | PASS | / |
|
|
58
|
+
|
|
59
|
+
最终结果:PASS
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## done criteria
|
|
63
|
+
- [ ] ohpm install 已完成(或明确跳过)
|
|
64
|
+
- [ ] lint 已执行(或明确跳过)
|
|
65
|
+
- [ ] 测试已执行(或明确跳过)
|
|
66
|
+
- [ ] 构建已完成(或明确跳过)
|
|
67
|
+
- [ ] 无伪造的 PASS
|
|
68
|
+
- [ ] 报告已输出
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CangmingAiDevKit
|
|
4
|
+
class Adapter
|
|
5
|
+
attr_reader :name, :relative_root, :plugin_dir_name, :plugin_file
|
|
6
|
+
|
|
7
|
+
def initialize(name, plugin_dir_name:)
|
|
8
|
+
@name = name
|
|
9
|
+
@plugin_dir_name = plugin_dir_name
|
|
10
|
+
@relative_root = "adapters/#{name}"
|
|
11
|
+
@plugin_file = "#{plugin_dir_name}/plugin.json"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def plugin_file_relative
|
|
15
|
+
"#{relative_root}/#{plugin_file}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def skill_paths(data_root)
|
|
19
|
+
paths = []
|
|
20
|
+
skills_dir = File.join(data_root, relative_root, plugin_dir_name)
|
|
21
|
+
return paths unless Dir.exist?(skills_dir)
|
|
22
|
+
Dir.glob("#{skills_dir}/skills/*")
|
|
23
|
+
paths
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def adapter_full_path(target_dir)
|
|
27
|
+
File.join(target_dir, "adapters", name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
SCAFFOLD_DIRS = %w[
|
|
31
|
+
skills
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
SCAFFOLD_FILES = {
|
|
35
|
+
"claude-code" => {
|
|
36
|
+
".claude-plugin/plugin.json" => "adapters/claude-code/.claude-plugin/plugin.json",
|
|
37
|
+
"hooks/hooks.json" => "adapters/claude-code/hooks/hooks.json",
|
|
38
|
+
"agents/planner.md" => "adapters/claude-code/agents/planner.md",
|
|
39
|
+
"agents/coder.md" => "adapters/claude-code/agents/coder.md",
|
|
40
|
+
"agents/reviewer.md" => "adapters/claude-code/agents/reviewer.md",
|
|
41
|
+
},
|
|
42
|
+
"codex" => {
|
|
43
|
+
".codex-plugin/plugin.json" => "adapters/codex/.codex-plugin/plugin.json",
|
|
44
|
+
"AGENTS.md.template" => "adapters/codex/AGENTS.md.template",
|
|
45
|
+
},
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
def scaffold_files(data_root)
|
|
49
|
+
SCAFFOLD_FILES.fetch(@name, {})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
SKILL_SOURCE_PATTERNS = [
|
|
53
|
+
"core/skills/*", # → skills/core/<name>/SKILL.md
|
|
54
|
+
"domains/*/skills/*", # → skills/<domain>/<name>/SKILL.md
|
|
55
|
+
].freeze
|
|
56
|
+
|
|
57
|
+
def self.available
|
|
58
|
+
@available ||= {
|
|
59
|
+
"claude-code" => new("claude-code", plugin_dir_name: ".claude-plugin"),
|
|
60
|
+
"codex" => new("codex", plugin_dir_name: ".codex-plugin"),
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.resolve(name)
|
|
65
|
+
available[name] || available["claude-code"]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
require_relative "commands/init"
|
|
6
|
+
require_relative "commands/sync"
|
|
7
|
+
require_relative "commands/verify"
|
|
8
|
+
|
|
9
|
+
module CangmingAiDevKit
|
|
10
|
+
class CLI
|
|
11
|
+
def self.start(argv)
|
|
12
|
+
new.run(argv)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(argv)
|
|
16
|
+
command = argv.empty? ? "help" : argv.shift
|
|
17
|
+
|
|
18
|
+
case command
|
|
19
|
+
when "init"
|
|
20
|
+
do_init(argv)
|
|
21
|
+
when "sync"
|
|
22
|
+
do_sync(argv)
|
|
23
|
+
when "verify"
|
|
24
|
+
do_verify(argv)
|
|
25
|
+
when "version", "--version", "-v"
|
|
26
|
+
puts "cangming-ai-dev-kit #{VERSION}"
|
|
27
|
+
else
|
|
28
|
+
help
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def do_init(argv)
|
|
35
|
+
adapter_name = argv.shift || "claude-code"
|
|
36
|
+
target_dir = argv.shift || "."
|
|
37
|
+
with_skills = argv.include?("--with-skills")
|
|
38
|
+
force = argv.include?("--force")
|
|
39
|
+
|
|
40
|
+
Commands::Init.new(target_dir, adapter_name, with_skills: with_skills, force: force).run
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def do_sync(argv)
|
|
44
|
+
target_dir = "."
|
|
45
|
+
adapter = nil
|
|
46
|
+
dry_run = false
|
|
47
|
+
|
|
48
|
+
parser = OptionParser.new do |opts|
|
|
49
|
+
opts.banner = "Usage: cangming-dev-kit sync [DIR] [options]"
|
|
50
|
+
|
|
51
|
+
opts.on("--adapter ADAPTER", "Sync to specific adapter (claude-code, codex)") { |v| adapter = v }
|
|
52
|
+
opts.on("--dry-run", "Preview only") { |v| dry_run = v }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
parser.parse!(argv)
|
|
56
|
+
# 如果还有剩余参数, 视为 target_dir
|
|
57
|
+
target_dir = argv.shift if argv.first && !argv.first.start_with?("-")
|
|
58
|
+
|
|
59
|
+
Commands::Sync.new(target_dir, adapter: adapter, dry_run: dry_run).run
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def do_verify(argv)
|
|
63
|
+
target_dir = argv.shift || "."
|
|
64
|
+
ok = Commands::Verify.new(target_dir).run
|
|
65
|
+
exit(1) unless ok
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def help
|
|
69
|
+
puts "cangming-ai-dev-kit #{VERSION}"
|
|
70
|
+
puts ""
|
|
71
|
+
puts "Usage: cangming-dev-kit COMMAND [options]"
|
|
72
|
+
puts ""
|
|
73
|
+
puts "Commands:"
|
|
74
|
+
puts " init ADAPTER [DIR] Bootstrap adapter skeleton"
|
|
75
|
+
puts " --with-skills Also sync skills"
|
|
76
|
+
puts " --force Overwrite existing files"
|
|
77
|
+
puts ""
|
|
78
|
+
puts " sync [DIR] Sync skills to adapter directory"
|
|
79
|
+
puts " --adapter ADAPTER Sync to specific adapter only"
|
|
80
|
+
puts " --dry-run Preview mode"
|
|
81
|
+
puts ""
|
|
82
|
+
puts " verify [DIR] Validate project structure"
|
|
83
|
+
puts " version Print version"
|
|
84
|
+
puts ""
|
|
85
|
+
puts "Adapters: claude-code, codex"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module CangmingAiDevKit
|
|
6
|
+
module Commands
|
|
7
|
+
class Init
|
|
8
|
+
def initialize(target_dir, adapter_name, with_skills: false, force: false)
|
|
9
|
+
@target_dir = target_dir
|
|
10
|
+
@adapter_name = adapter_name
|
|
11
|
+
@with_skills = with_skills
|
|
12
|
+
@force = force
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
data_root = Syncer.data_root
|
|
17
|
+
adapter = Adapter.resolve(@adapter_name)
|
|
18
|
+
dest_root = adapter.adapter_full_path(@target_dir)
|
|
19
|
+
|
|
20
|
+
FileUtils.mkdir_p(dest_root)
|
|
21
|
+
|
|
22
|
+
files = adapter.scaffold_files(data_root)
|
|
23
|
+
files.each do |relative_dest, source_relative|
|
|
24
|
+
source = File.join(data_root, source_relative)
|
|
25
|
+
dest = File.join(dest_root, relative_dest)
|
|
26
|
+
|
|
27
|
+
if File.exist?(dest) && !@force
|
|
28
|
+
puts " SKIP #{relative_dest} (已存在,使用 --force 覆盖)"
|
|
29
|
+
next
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
33
|
+
FileUtils.cp(source, dest)
|
|
34
|
+
puts " #{@adapter_name}/#{relative_dest}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 创建 skills 占位目录
|
|
38
|
+
skills_dir = File.join(dest_root, "skills")
|
|
39
|
+
FileUtils.mkdir_p(skills_dir)
|
|
40
|
+
|
|
41
|
+
if @with_skills
|
|
42
|
+
puts ""
|
|
43
|
+
puts "--- 同步 skills ---"
|
|
44
|
+
syncer = Syncer.new
|
|
45
|
+
syncer.sync(@target_dir, adapter: @adapter_name, dry_run: false)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
puts ""
|
|
49
|
+
puts "#{@adapter_name} adapter 已就绪: #{dest_root}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CangmingAiDevKit
|
|
4
|
+
module Commands
|
|
5
|
+
class Sync
|
|
6
|
+
def initialize(target_dir, adapter: nil, dry_run: false)
|
|
7
|
+
@target_dir = target_dir
|
|
8
|
+
@adapter = adapter
|
|
9
|
+
@dry_run = dry_run
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
syncer = Syncer.new
|
|
14
|
+
stats = syncer.sync(@target_dir, adapter: @adapter, dry_run: @dry_run)
|
|
15
|
+
|
|
16
|
+
puts ""
|
|
17
|
+
stats.each do |adp, info|
|
|
18
|
+
status = @dry_run ? "[dry-run]" : "[synced]"
|
|
19
|
+
puts "#{status} #{adp}: #{info[:count]} skills"
|
|
20
|
+
info[:skills].each { |s| puts " - #{s}" }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CangmingAiDevKit
|
|
4
|
+
module Commands
|
|
5
|
+
class Verify
|
|
6
|
+
def initialize(target_dir)
|
|
7
|
+
@target_dir = target_dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
verifier = Verifier.new(@target_dir)
|
|
12
|
+
result = verifier.verify
|
|
13
|
+
|
|
14
|
+
puts ""
|
|
15
|
+
puts "===== 验证汇总 ====="
|
|
16
|
+
puts " PASS: #{result.pass}"
|
|
17
|
+
puts " FAIL: #{result.fail}"
|
|
18
|
+
puts " WARN: #{result.warn}"
|
|
19
|
+
|
|
20
|
+
if result.fail.zero?
|
|
21
|
+
puts ""
|
|
22
|
+
puts " MVP_VERIFY_PASS"
|
|
23
|
+
return true
|
|
24
|
+
else
|
|
25
|
+
puts ""
|
|
26
|
+
puts " MVP_VERIFY_FAIL (#{result.fail} 项失败)"
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module CangmingAiDevKit
|
|
6
|
+
class Syncer
|
|
7
|
+
# 返回 gem 安装后的项目根目录
|
|
8
|
+
def self.gem_data_root
|
|
9
|
+
@gem_data_root ||= begin
|
|
10
|
+
spec = Gem::Specification.find_by_name("cangming-ai-dev-kit")
|
|
11
|
+
spec.gem_dir
|
|
12
|
+
end
|
|
13
|
+
rescue Gem::LoadError
|
|
14
|
+
# 开发模式:从 lib/ 往上找到项目根目录
|
|
15
|
+
File.expand_path("../..", __dir__)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.data_root
|
|
19
|
+
@data_root ||= gem_data_root
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 收集所有 skill 源
|
|
23
|
+
# 返回 [{ category:, name:, path: }]
|
|
24
|
+
def self.skill_sources
|
|
25
|
+
sources = []
|
|
26
|
+
|
|
27
|
+
root = data_root
|
|
28
|
+
return sources unless Dir.exist?(root)
|
|
29
|
+
|
|
30
|
+
# core/skills/*
|
|
31
|
+
core_skills = File.join(root, "core", "skills")
|
|
32
|
+
if Dir.exist?(core_skills)
|
|
33
|
+
Dir.glob("#{core_skills}/*").each do |dir|
|
|
34
|
+
next unless File.directory?(dir)
|
|
35
|
+
sources << { category: "core", name: File.basename(dir), path: dir }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# domains/*/skills/*
|
|
40
|
+
domains_dir = File.join(root, "domains")
|
|
41
|
+
if Dir.exist?(domains_dir)
|
|
42
|
+
Dir.glob("#{domains_dir}/*/skills/*").each do |dir|
|
|
43
|
+
next unless File.directory?(dir)
|
|
44
|
+
parts = dir.split("/")
|
|
45
|
+
domain = parts[-3]
|
|
46
|
+
name = parts[-1]
|
|
47
|
+
sources << { category: domain, name: name, path: dir }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sources
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 列出所有可用 adapter 的名称
|
|
55
|
+
def self.adapters_available
|
|
56
|
+
@adapters_available ||= ["claude-code", "codex"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 同步 skills 到指定 adapter(s)
|
|
60
|
+
# target_dir: 目标根目录(包含 adapters/<name>/skills/)
|
|
61
|
+
# adapter: 可选,仅同步指定 adapter
|
|
62
|
+
# dry_run: 仅预览,不实际复制
|
|
63
|
+
# 返回同步统计 { adapter => { count:, skills: [] } }
|
|
64
|
+
def sync(target_dir, adapter: nil, dry_run: false)
|
|
65
|
+
adapters = adapter ? [adapter] : self.class.adapters_available
|
|
66
|
+
sources = self.class.skill_sources
|
|
67
|
+
stats = {}
|
|
68
|
+
|
|
69
|
+
adapters.each do |adp|
|
|
70
|
+
skills_root = File.join(target_dir, "adapters", adp, "skills")
|
|
71
|
+
copied = []
|
|
72
|
+
|
|
73
|
+
if dry_run
|
|
74
|
+
puts "[#{adp}] 目标: #{skills_root}"
|
|
75
|
+
puts "[#{adp}] 将清空 skills/ 目录并重新同步"
|
|
76
|
+
elsif Dir.exist?(skills_root)
|
|
77
|
+
FileUtils.rm_rf(Dir.glob("#{skills_root}/*"))
|
|
78
|
+
else
|
|
79
|
+
FileUtils.mkdir_p(skills_root)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sources.each do |src|
|
|
83
|
+
dest_dir = File.join(skills_root, src[:category], src[:name])
|
|
84
|
+
dest_file = File.join(dest_dir, "SKILL.md")
|
|
85
|
+
src_file = File.join(src[:path], "SKILL.md")
|
|
86
|
+
|
|
87
|
+
if dry_run
|
|
88
|
+
puts " #{src[:category]}/#{src[:name]} → #{dest_file}"
|
|
89
|
+
else
|
|
90
|
+
FileUtils.mkdir_p(dest_dir)
|
|
91
|
+
FileUtils.cp(src_file, dest_file)
|
|
92
|
+
end
|
|
93
|
+
copied << "#{src[:category]}/#{src[:name]}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
stats[adp] = { count: copied.size, skills: copied }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
stats
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|