neo-skill 0.1.18 → 0.1.20

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.
@@ -43,17 +43,22 @@
43
43
  }
44
44
  ```
45
45
 
46
- ## 添加新 Tool
47
-
48
- 1. 创建 `{tool_id}.json` 文件
49
- 2. 更新 `index.json`,添加索引条目:
50
- ```json
51
- {
52
- "tool_id": {
53
- "file": "tool_id.json",
54
- "tags": ["..."],
55
- "keywords": ["..."]
56
- }
46
+ ## 如何添加新工具
47
+
48
+ 1. 创建工具详情文件 `{tool-name}.json`
49
+ 2. `index.json` 中添加索引条目
50
+ 3. 确保 tags 与 capability_tags 设计一致
51
+
52
+ ## 自动推荐机制
53
+
54
+ skill-creator 会**自动推荐**合适的第三方库,无需用户手动指定:
55
+
56
+ - **自动触发**:基于 `task_type` 和 `capability_tags` 自动匹配
57
+ - **智能排序**:返回 top-5 最匹配的工具库
58
+ - **多处展示**:推荐信息会在 Plan、Workflow Steps 和最终输出中展示
59
+ - **安全降级**:如果没有匹配的库,不影响 skill 生成
60
+
61
+ 详见:`docs/skill-creator-refactoring/LIBRARY_RECOMMENDATION.md`
57
62
  }
58
63
  ```
59
64
  3. 无需修改代码,系统会自动检索
package/README.md CHANGED
@@ -85,7 +85,10 @@ omni-skill init --ai all # 初始化所有支持的 AI 助手
85
85
 
86
86
  这会:
87
87
  - 从 npm 包同步 skills/ 和 .shared/ 到当前目录
88
- - 为所有 skills 生成 AI 助手的输出文件(.windsurf, .claude, .cursor, .github)
88
+ - 为所有 skills 生成**指定 AI 助手**的输出文件
89
+ - `--ai windsurf` → 只生成 `.windsurf/workflows/`
90
+ - `--ai claude` → 只生成 `.claude/skills/`
91
+ - `--ai all` → 生成所有目标
89
92
  - 保存初始化状态到 .neo-skill.json
90
93
 
91
94
  **2. 使用 skill-creator 创建新 skill**
@@ -103,7 +106,9 @@ omni-skill install ./skills
103
106
 
104
107
  这会:
105
108
  - 复制 skill 到当前目录的 skills/ 文件夹
106
- - 为该 skill 生成所有 AI 助手的输出文件
109
+ - 为该 skill 生成**所有 AI 助手**的输出文件(.windsurf, .claude, .cursor, .github)
110
+
111
+ **注意**:与 `init` 不同,`install` 命令总是生成所有 AI 目标的输出,以确保最大兼容性。
107
112
 
108
113
  **4. 更新 npm 包**
109
114
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-skill",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "A multi-assistant skill generator (Claude/Windsurf/Cursor/GitHub Skills) driven by a canonical SkillSpec.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -0,0 +1,204 @@
1
+ # Skill Creator 架构规则(强制约束)
2
+
3
+ ## 重要提示:AI 必须遵守以下规则
4
+
5
+ ---
6
+
7
+ ## 1. 禁止单文件脚本(Monolith)
8
+
9
+ ### 规则
10
+
11
+ **严格禁止**生成任何"单文件可执行脚本"作为最终 skill 产物。
12
+
13
+ ### 原因
14
+
15
+ - 违反分层索引原则
16
+ - 破坏可扩展性
17
+ - 难以维护和复用
18
+ - 无法利用数据包体系
19
+
20
+ ### 允许的形态
21
+
22
+ ✅ **Structured Skill**(唯一允许):
23
+ - 分层索引文件(index.json)
24
+ - 被索引的数据文件(items/*.json)
25
+ - Skill 主文档({{skill-id}}.md)
26
+ - 运行时依赖的最小闭包
27
+
28
+ ❌ **Monolith Script**(严格禁止):
29
+ - 单个 Python/Shell 脚本包含所有逻辑
30
+ - 不使用索引系统
31
+ - 硬编码数据和逻辑
32
+
33
+ ### AI 指令
34
+
35
+ **不得输出 Monolith**
36
+ **不得建议 Monolith 作为解决方案**
37
+ **即使更简单也必须使用分层索引结构**
38
+
39
+ ---
40
+
41
+ ## 2. Install Space 路径规范
42
+
43
+ ### 路径空间定义
44
+
45
+ #### Source Space(源文件空间)
46
+ - 仅用于构建/安装阶段
47
+ - 位置:`skills/`, `.shared/`, `src/`
48
+ - **禁止**:运行时依赖
49
+
50
+ #### Install Space(安装后运行空间)
51
+ - 仅用于运行时
52
+ - 位置:`.windsurf/`, `.claude/`, `.cursor/`, `.github/`
53
+ - **要求**:所有索引路径必须指向此空间
54
+
55
+ ### 路径写法规范
56
+
57
+ ✅ **正确**:使用 Install Space 占位符
58
+ ```markdown
59
+ - Data: `{{install_root}}/.windsurf/workflows/data/{{skill-id}}/`
60
+ - Index: `{{install_root}}/.windsurf/workflows/data/{{skill-id}}/index.json`
61
+ ```
62
+
63
+ ❌ **错误**:引用 Source Space 路径
64
+ ```markdown
65
+ - Data: `skills/{{skill-id}}/data/`
66
+ - Index: `.shared/skill-creator/data_packs/`
67
+ - Relative: `../data/`, `../../skills/`
68
+ ```
69
+
70
+ ### 验证
71
+
72
+ 使用 `omni-skill doctor --skill <skill-id>` 验证路径正确性。
73
+
74
+ ---
75
+
76
+ ## 3. 三方库规则
77
+
78
+ ### 自动采用,无需确认
79
+
80
+ **规则**:
81
+ - ✅ 自动判断并采用三方库
82
+ - ❌ 不要求用户确认
83
+ - ❌ 不要求用户选择 A/B/C
84
+
85
+ ### 结果汇报(强制)
86
+
87
+ **必须在最终输出中列出**:
88
+
89
+ ```markdown
90
+ ## 使用的第三方库
91
+
92
+ ### 1. requests
93
+ - **版本策略**:>=2.28.0
94
+ - **用途**:发起 HTTP 请求
95
+ - **安装方式**:`pip install requests>=2.28.0`
96
+ - **文档链接**:
97
+ - PyPI: https://pypi.org/project/requests/
98
+ - Docs: https://requests.readthedocs.io/
99
+
100
+ ### 2. feedparser
101
+ - **版本策略**:>=6.0.0
102
+ - **用途**:解析 RSS/Atom feed
103
+ - **安装方式**:`pip install feedparser>=6.0.0`
104
+ - **文档链接**:
105
+ - PyPI: https://pypi.org/project/feedparser/
106
+ - Docs: https://feedparser.readthedocs.io/
107
+
108
+ ### Fallback 方案(可选)
109
+ - 如果不使用 `requests`,可使用标准库 `urllib`
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 4. 依赖闭包原则
115
+
116
+ ### 最小闭包
117
+
118
+ **要求**:
119
+ - ✅ 只拷贝该 skill 需要的最小闭包
120
+ - ❌ 不原封不动复制整个源目录
121
+ - ❌ 不拷贝未被引用的文件
122
+
123
+ ### 闭包内容
124
+
125
+ 每个 skill 的依赖闭包包括:
126
+ 1. Skill 主文档({{skill-id}}.md)
127
+ 2. 索引文件(index.json 等)
128
+ 3. 索引命中的 items
129
+ 4. 必需不变量(universal schema, output packs, minimal checklists)
130
+ 5. 三方库声明(requirements.txt)
131
+ 6. References/Scripts/Assets
132
+
133
+ ---
134
+
135
+ ## 5. 生成流程
136
+
137
+ ### 步骤
138
+
139
+ 1. **分析需求**:提取任务类型、输出形态、约束条件
140
+ 2. **收集信息**:对话式收集(≤10 问)
141
+ 3. **设计系统**:分层索引 + 文件化数据包
142
+ 4. **生成 SkillSpec**:写入 `skills/<name>/skillspec.json`
143
+ 5. **生成输出**:为所有 AI 目标生成文档
144
+ 6. **验证**:Schema 校验 + Dry-run
145
+
146
+ ### 禁止事项
147
+
148
+ ❌ 不生成单文件脚本
149
+ ❌ 不在文档中引用 Source Space 路径
150
+ ❌ 不要求用户确认三方库选型
151
+ ❌ 不复制整个源目录
152
+
153
+ ---
154
+
155
+ ## 6. 验收标准
156
+
157
+ ### 功能验收
158
+
159
+ ✅ Skill 产物自包含
160
+ ✅ 索引路径只指向 Install Space
161
+ ✅ 使用分层索引结构
162
+ ✅ 只拷贝最小闭包文件
163
+
164
+ ### 质量验收
165
+
166
+ ✅ 可追溯性:Install manifest 记录完整
167
+ ✅ 可复现性:相同输入产生相同输出
168
+ ✅ 可维护性:代码结构清晰,文档完整
169
+
170
+ ---
171
+
172
+ ## 7. 示例
173
+
174
+ ### 正确的 Skill 结构
175
+
176
+ ```
177
+ skills/review-gate/
178
+ ├── skillspec.json # Skill 定义
179
+ ├── references/ # 规则文档
180
+ │ └── checklist-rules.md
181
+ ├── scripts/ # 确定性脚本
182
+ │ └── validate.py
183
+ └── assets/ # 数据文件
184
+ └── templates/
185
+ ```
186
+
187
+ ### 正确的索引引用
188
+
189
+ ```json
190
+ {
191
+ "version": "1.0",
192
+ "items": {
193
+ "checklist-1": {
194
+ "file": "{{install_root}}/.windsurf/workflows/data/review-gate/checklists/checklist-1.json"
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ ---
201
+
202
+ **本规则为强制约束,所有 AI 生成的 skill 必须遵守。**
203
+
204
+ 详见:`docs/ARCHITECTURE_RULES.md`
@@ -25,10 +25,15 @@ omni-skill init --ai all
25
25
 
26
26
  **What it does:**
27
27
  1. Syncs skills/ and .shared/ from npm package to current directory
28
- 2. Installs all skills (generates outputs for all AI targets)
28
+ 2. Installs all skills (generates outputs for **specified AI targets only**)
29
29
  3. Writes VERSION files for each AI target
30
30
  4. Saves init state to .neo-skill.json
31
31
 
32
+ **Example:**
33
+ - `omni-skill init --ai windsurf` → Only generates `.windsurf/workflows/`
34
+ - `omni-skill init --ai claude` → Only generates `.claude/skills/`
35
+ - `omni-skill init --ai all` → Generates all targets (.windsurf, .claude, .cursor, .github)
36
+
32
37
  ### `omni-skill install <path>`
33
38
  Install skill(s) from a local directory.
34
39
 
@@ -43,7 +48,58 @@ omni-skill install ./skills
43
48
 
44
49
  **What it does:**
45
50
  1. Copies skill(s) to current directory's skills/ folder
46
- 2. Generates outputs for all AI targets (.windsurf, .claude, .cursor, .github)
51
+ 2. Generates outputs for **all AI targets** (.windsurf, .claude, .cursor, .github)
52
+
53
+ **Note:** Unlike `init`, the `install` command always generates outputs for all AI targets to ensure maximum compatibility.
54
+
55
+ ### `omni-skill install-skill <skill-id>`
56
+ Install skill with dependency closure (new architecture).
57
+
58
+ **Usage:**
59
+ ```bash
60
+ omni-skill install-skill review-gate --target windsurf
61
+ omni-skill install-skill skill-creator --target claude
62
+ ```
63
+
64
+ **What it does:**
65
+ 1. Resolves skill dependency closure (minimal files)
66
+ 2. Materializes files to Install Space
67
+ 3. Generates install manifest
68
+ 4. Validates paths (no source path leakage)
69
+
70
+ **Architecture:**
71
+ - Uses Source Space (skills/) for build
72
+ - Copies to Install Space (.windsurf/, .claude/, etc.) for runtime
73
+ - Only copies minimal closure (not entire source directory)
74
+
75
+ ### `omni-skill doctor --skill <skill-id>`
76
+ Diagnose skill installation and dependencies.
77
+
78
+ **Usage:**
79
+ ```bash
80
+ omni-skill doctor --skill review-gate --target windsurf
81
+ ```
82
+
83
+ **What it checks:**
84
+ - Install manifest exists
85
+ - All referenced files exist
86
+ - No source path leakage
87
+ - Index files are valid
88
+ - Dependency closure is complete
89
+
90
+ **Output:**
91
+ ```
92
+ === Skill Diagnostic Report ===
93
+ Skill ID: review-gate
94
+ Install Root: .windsurf/workflows/data/review-gate
95
+
96
+ --- Path Validation ---
97
+ ✓ No source path leakage detected
98
+ ✓ All index paths point to Install Space
99
+ ✓ All referenced files exist
100
+
101
+ === Diagnostic Complete ===
102
+ ```
47
103
 
48
104
  ### `omni-skill update`
49
105
  Update npm package and re-initialize skills.
@@ -4,9 +4,11 @@ import argparse
4
4
  import json
5
5
  import shutil
6
6
  from pathlib import Path
7
- from typing import Dict, List, Set
7
+ from typing import Dict, List, Optional, Set
8
8
 
9
9
  from skill_creator.cli import cmd_generate
10
+ from .install import SkillInstaller
11
+ from .doctor import SkillDoctor
10
12
 
11
13
  STATE_FILE = ".neo-skill.json"
12
14
 
@@ -192,10 +194,15 @@ def _generate_outputs_best_effort(pkg_root: Path, cwd: Path) -> None:
192
194
  print(f" Skipping generator for {rel} (exit {e.code})")
193
195
 
194
196
 
195
- def _install_skills_from_dir(skills_dir: Path, cwd: Path) -> int:
197
+ def _install_skills_from_dir(skills_dir: Path, cwd: Path, selected_ais: Optional[List[str]] = None) -> int:
196
198
  """
197
199
  Install skills from a directory (either from npm package or local path).
198
- Generates outputs for all AI targets.
200
+ Generates outputs for specified AI targets.
201
+
202
+ Args:
203
+ skills_dir: Directory containing skills
204
+ cwd: Current working directory
205
+ selected_ais: List of AI targets to generate for. If None, generates for all targets.
199
206
  """
200
207
  if not skills_dir.exists():
201
208
  print(f"Skills directory not found: {skills_dir}")
@@ -206,6 +213,37 @@ def _install_skills_from_dir(skills_dir: Path, cwd: Path) -> int:
206
213
  print(f"No skillspec.json found in: {skills_dir}")
207
214
  return 1
208
215
 
216
+ # Determine target mapping: AI assistant -> skill-creator target
217
+ ai_to_target = {
218
+ "windsurf": "windsurf",
219
+ "claude": "claude",
220
+ "cursor": "cursor",
221
+ "antigravity": "github",
222
+ "copilot": "github",
223
+ "kiro": "github",
224
+ "codex": "github",
225
+ "qoder": "github",
226
+ "roocode": "github",
227
+ "gemini": "github",
228
+ "trae": "github",
229
+ "opencode": "github",
230
+ "continue": "github",
231
+ }
232
+
233
+ # Determine which targets to generate
234
+ if selected_ais is None:
235
+ # Generate all targets
236
+ generate_all_targets = True
237
+ targets_list = None
238
+ else:
239
+ # Map AI assistants to skill-creator targets
240
+ targets_set = set()
241
+ for ai in selected_ais:
242
+ target = ai_to_target.get(ai, "windsurf")
243
+ targets_set.add(target)
244
+ targets_list = sorted(targets_set)
245
+ generate_all_targets = False
246
+
209
247
  print(f"\nInstalling {len(specs)} skill(s) from {skills_dir} ...")
210
248
  for spec_path in specs:
211
249
  skill_name = spec_path.parent.name
@@ -221,11 +259,18 @@ def _install_skills_from_dir(skills_dir: Path, cwd: Path) -> int:
221
259
  shutil.copytree(spec_path.parent, dest_skill_dir)
222
260
  print(f" Copied to: {dest_skill_dir}")
223
261
 
224
- # Generate outputs for all targets
262
+ # Generate outputs for specified targets
225
263
  try:
226
- ns = argparse.Namespace(repo_root=str(cwd), spec=str(cwd / "skills" / skill_name / "skillspec.json"), all=True)
227
- cmd_generate(ns)
228
- print(f" Generated outputs for all targets")
264
+ if generate_all_targets:
265
+ ns = argparse.Namespace(repo_root=str(cwd), spec=str(cwd / "skills" / skill_name / "skillspec.json"), all=True)
266
+ cmd_generate(ns)
267
+ print(f" Generated outputs for all targets")
268
+ else:
269
+ # Generate for each target separately
270
+ for target in targets_list:
271
+ ns = argparse.Namespace(repo_root=str(cwd), spec=str(cwd / "skills" / skill_name / "skillspec.json"), all=False, target=target)
272
+ cmd_generate(ns)
273
+ print(f" Generated outputs for: {', '.join(targets_list)}")
229
274
  except SystemExit as e:
230
275
  print(f" Warning: Generator failed (exit {e.code})")
231
276
 
@@ -242,10 +287,10 @@ def _handle_init(selected_ais: List[str], mode: str) -> int:
242
287
  print("Initializing skills in:", cwd)
243
288
  _perform_sync(pkg_root, cwd, sync_pairs)
244
289
 
245
- # Install all skills from npm package
290
+ # Install all skills from npm package with specified AI targets
246
291
  pkg_skills_dir = pkg_root / "skills"
247
292
  if pkg_skills_dir.exists():
248
- _install_skills_from_dir(pkg_skills_dir, cwd)
293
+ _install_skills_from_dir(pkg_skills_dir, cwd, selected_ais=selected_ais)
249
294
 
250
295
  _write_version_files(cwd, effective_ais, version)
251
296
  print("\nDone! neo-skill initialized.")
@@ -267,10 +312,39 @@ def _cmd_init(args: argparse.Namespace) -> int:
267
312
  return _handle_init(resolved["selected"], "init")
268
313
 
269
314
 
315
+ def _cmd_install_new(args: argparse.Namespace) -> int:
316
+ """
317
+ Install skill with dependency closure (new architecture).
318
+ Usage: omni-skill install <skill-id> [--target <agent>]
319
+ """
320
+ cwd = Path.cwd().resolve()
321
+ pkg_root = _get_pkg_root()
322
+
323
+ skill_id = args.skill_id
324
+ target = getattr(args, 'target', 'windsurf')
325
+
326
+ # 使用新的 SkillInstaller
327
+ installer = SkillInstaller(source_root=pkg_root, install_root=cwd)
328
+
329
+ try:
330
+ manifest = installer.install(skill_id, target)
331
+ print(f"\n✓ Successfully installed {skill_id}")
332
+ print(f" Install root: {manifest.install_root}")
333
+ print(f" Files: {len(manifest.files)}")
334
+ if manifest.dependencies.get('libraries'):
335
+ print(f" Libraries: {', '.join(manifest.dependencies['libraries'])}")
336
+ return 0
337
+ except Exception as e:
338
+ print(f"\n✗ Installation failed: {e}")
339
+ return 1
340
+
341
+
270
342
  def _cmd_install(args: argparse.Namespace) -> int:
271
343
  """
272
- Install skill(s) from a local directory.
344
+ Install skill(s) from a local directory (legacy).
273
345
  Usage: omni-skill install <path-to-skill-or-skills-dir>
346
+
347
+ Note: install command always generates outputs for all AI targets.
274
348
  """
275
349
  cwd = Path.cwd().resolve()
276
350
  skill_path = Path(args.path).resolve()
@@ -280,13 +354,13 @@ def _cmd_install(args: argparse.Namespace) -> int:
280
354
 
281
355
  # Check if it's a single skill directory (contains skillspec.json)
282
356
  if (skill_path / "skillspec.json").exists():
283
- # Single skill
357
+ # Single skill - generate for all targets
284
358
  temp_skills_dir = skill_path.parent
285
- return _install_skills_from_dir(temp_skills_dir, cwd)
359
+ return _install_skills_from_dir(temp_skills_dir, cwd, selected_ais=None)
286
360
 
287
361
  # Check if it's a skills directory (contains subdirs with skillspec.json)
288
362
  elif skill_path.is_dir():
289
- return _install_skills_from_dir(skill_path, cwd)
363
+ return _install_skills_from_dir(skill_path, cwd, selected_ais=None)
290
364
 
291
365
  else:
292
366
  raise SystemExit(f"Invalid path: {skill_path}. Must be a skill directory or skills directory.")
@@ -332,6 +406,28 @@ def _print_init_help() -> None:
332
406
  print(" omni-skill init --ai all")
333
407
 
334
408
 
409
+ def _cmd_doctor(args: argparse.Namespace) -> int:
410
+ """
411
+ Diagnose skill installation and dependencies.
412
+ Usage: omni-skill doctor --skill <skill-id> [--target <agent>]
413
+ """
414
+ cwd = Path.cwd().resolve()
415
+
416
+ skill_id = args.skill_id
417
+ target = getattr(args, 'target', 'windsurf')
418
+
419
+ # 使用 SkillDoctor
420
+ doctor = SkillDoctor(install_root=cwd)
421
+
422
+ report = doctor.diagnose(skill_id, target)
423
+ print(report.format())
424
+
425
+ # 返回错误码
426
+ if report.has_errors():
427
+ return 1
428
+ return 0
429
+
430
+
335
431
  def build_parser() -> argparse.ArgumentParser:
336
432
  p = argparse.ArgumentParser(
337
433
  prog="omni-skill",
@@ -347,6 +443,18 @@ def build_parser() -> argparse.ArgumentParser:
347
443
  p_install = sub.add_parser("install", help="Install skill(s) from a local directory")
348
444
  p_install.add_argument("path", help="Path to skill directory or skills directory")
349
445
  p_install.set_defaults(func=_cmd_install)
446
+
447
+ # 新增:install-skill 命令(使用新架构)
448
+ p_install_new = sub.add_parser("install-skill", help="Install skill with dependency closure (new)")
449
+ p_install_new.add_argument("skill_id", help="Skill ID to install")
450
+ p_install_new.add_argument("--target", default="windsurf", help="Target AI (windsurf/claude/cursor/github)")
451
+ p_install_new.set_defaults(func=_cmd_install_new)
452
+
453
+ # 新增:doctor 命令
454
+ p_doctor = sub.add_parser("doctor", help="Diagnose skill installation")
455
+ p_doctor.add_argument("--skill", dest="skill_id", required=True, help="Skill ID to diagnose")
456
+ p_doctor.add_argument("--target", default="windsurf", help="Target AI (windsurf/claude/cursor/github)")
457
+ p_doctor.set_defaults(func=_cmd_doctor)
350
458
 
351
459
  p_update = sub.add_parser("update", help="Update npm package and re-initialize skills")
352
460
  p_update.set_defaults(func=_cmd_update)