neo-skill 0.1.11
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.
- package/.shared/skill-creator/data/domains.json +56 -0
- package/.shared/skill-creator/data/output-patterns.csv +8 -0
- package/.shared/skill-creator/data/resource-patterns.csv +8 -0
- package/.shared/skill-creator/data/skill-reasoning.csv +11 -0
- package/.shared/skill-creator/data/trigger-patterns.csv +8 -0
- package/.shared/skill-creator/data/validation-rules.csv +11 -0
- package/.shared/skill-creator/data/workflow-patterns.csv +6 -0
- package/.shared/skill-creator/scripts/generate.py +300 -0
- package/.shared/skill-creator/scripts/package.py +140 -0
- package/.shared/skill-creator/scripts/search.py +231 -0
- package/.shared/skill-creator/scripts/validate.py +213 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/bin/omni-skill.js +55 -0
- package/bin/skill-creator.js +55 -0
- package/package.json +25 -0
- package/skills/review-gate/references/review-gate.md +228 -0
- package/skills/review-gate/skillspec.json +131 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/pre-delivery-checklist.md +70 -0
- package/skills/skill-creator/references/requirement-collection.md +80 -0
- package/skills/skill-creator/references/skill-system-design.md +112 -0
- package/skills/skill-creator/references/sources.md +5 -0
- package/skills/skill-creator/references/workflow-step-editing.md +103 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/init_skill.py +34 -0
- package/skills/skill-creator/scripts/package_skill.py +34 -0
- package/skills/skill-creator/scripts/validate_skill.py +35 -0
- package/skills/skill-creator/skillspec.json +117 -0
- package/src/omni_skill/__init__.py +1 -0
- package/src/omni_skill/cli.py +270 -0
- package/src/skill_creator/__init__.py +1 -0
- package/src/skill_creator/cli.py +278 -0
- package/src/skill_creator/packaging/package.py +30 -0
- package/src/skill_creator/packaging/ziputil.py +26 -0
- package/src/skill_creator/spec/model.py +111 -0
- package/src/skill_creator/spec/render.py +108 -0
- package/src/skill_creator/spec/validate.py +18 -0
- package/src/skill_creator/targets/claude.py +53 -0
- package/src/skill_creator/targets/common.py +46 -0
- package/src/skill_creator/targets/cursor.py +34 -0
- package/src/skill_creator/targets/github_skills.py +40 -0
- package/src/skill_creator/targets/windsurf.py +123 -0
- package/src/skill_creator/util/frontmatter.py +24 -0
- package/src/skill_creator/util/fs.py +32 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domains": {
|
|
3
|
+
"workflow": {
|
|
4
|
+
"description": "Workflow patterns: sequential, conditional, parallel, gate-based",
|
|
5
|
+
"keywords": ["sequential", "conditional", "parallel", "gate", "branch", "loop", "retry"],
|
|
6
|
+
"data_file": "workflow-patterns.csv"
|
|
7
|
+
},
|
|
8
|
+
"output": {
|
|
9
|
+
"description": "Output patterns: template, examples, structured, report",
|
|
10
|
+
"keywords": ["template", "example", "structured", "report", "code", "patch", "checklist"],
|
|
11
|
+
"data_file": "output-patterns.csv"
|
|
12
|
+
},
|
|
13
|
+
"validation": {
|
|
14
|
+
"description": "Validation rules: spec, frontmatter, structure, naming",
|
|
15
|
+
"keywords": ["validate", "check", "gate", "lint", "schema", "strict", "frontmatter"],
|
|
16
|
+
"data_file": "validation-rules.csv"
|
|
17
|
+
},
|
|
18
|
+
"resource": {
|
|
19
|
+
"description": "Resource organization: references, scripts, assets",
|
|
20
|
+
"keywords": ["reference", "script", "asset", "data", "template", "shared", "progressive"],
|
|
21
|
+
"data_file": "resource-patterns.csv"
|
|
22
|
+
},
|
|
23
|
+
"trigger": {
|
|
24
|
+
"description": "Trigger patterns: action verbs, context keywords, intent signals",
|
|
25
|
+
"keywords": ["create", "build", "fix", "review", "generate", "update", "help"],
|
|
26
|
+
"data_file": "trigger-patterns.csv"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"stacks": {
|
|
30
|
+
"windsurf": {
|
|
31
|
+
"description": "Windsurf workflow format (PRIMARY)",
|
|
32
|
+
"format": "yaml-frontmatter + markdown",
|
|
33
|
+
"output_path": ".windsurf/workflows/{name}.md",
|
|
34
|
+
"priority": 1
|
|
35
|
+
},
|
|
36
|
+
"claude": {
|
|
37
|
+
"description": "Claude SKILL.md format (backward compat)",
|
|
38
|
+
"format": "strict yaml-frontmatter (name+description only)",
|
|
39
|
+
"output_path": ".claude/skills/{name}/SKILL.md",
|
|
40
|
+
"priority": 2
|
|
41
|
+
},
|
|
42
|
+
"cursor": {
|
|
43
|
+
"description": "Cursor command format (backward compat)",
|
|
44
|
+
"format": "markdown with bash blocks",
|
|
45
|
+
"output_path": ".cursor/commands/{name}.md",
|
|
46
|
+
"priority": 3
|
|
47
|
+
},
|
|
48
|
+
"github": {
|
|
49
|
+
"description": "GitHub Skills format (backward compat)",
|
|
50
|
+
"format": "extended yaml-frontmatter",
|
|
51
|
+
"output_path": ".github/skills/{name}/SKILL.md",
|
|
52
|
+
"priority": 4
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"reasoning_file": "skill-reasoning.csv"
|
|
56
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id,name,description,when_to_use,template_type,example
|
|
2
|
+
template,"Template Pattern","Structured output with placeholders","Reports, documents, code generation","strict or flexible","# [Title]\n## Summary\n[content]"
|
|
3
|
+
examples,"Examples Pattern","Input/output pairs for style learning","Style-sensitive output, commit messages","input_output_pairs","Input: X -> Output: Y"
|
|
4
|
+
structured,"Structured Data","JSON/YAML/CSV machine-readable output","API responses, data exports, configs","schema_based","{ key: value }"
|
|
5
|
+
checklist,"Checklist Pattern","Actionable items with status","Reviews, audits, validation","checkbox_list","- [ ] Item 1\n- [ ] Item 2"
|
|
6
|
+
code,"Code Generation","Source code with imports and tests","Implementation tasks","language_specific","```python\ndef func():\n pass\n```"
|
|
7
|
+
patch,"Patch/Diff Pattern","Changes to existing code","Bug fixes, refactoring","unified_diff","+added\n-removed"
|
|
8
|
+
tree,"Directory Tree","File/folder structure","Project scaffolding, organization","ascii_tree","src/\n index.ts\n utils/"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id,name,location,content_type,when_to_use,size_limit
|
|
2
|
+
reference,"Reference Documents","references/","markdown rules, specs, templates","Static knowledge, rules, guidelines that AI reads","<5KB per file"
|
|
3
|
+
script,"Deterministic Scripts","scripts/","python, bash, node scripts","Reproducible operations, validation, packaging","<500 lines"
|
|
4
|
+
asset,"Structured Data","assets/","csv, json, yaml data files","Searchable data, templates, configs","<100KB"
|
|
5
|
+
shared,"Shared Resources",".shared/{skill}/","cross-skill data and scripts","Reusable across multiple skills","per-file limits apply"
|
|
6
|
+
index,"Index Notes","workflow.steps[].notes","short pointers to resources","Keep steps lightweight, point to details","<200 chars"
|
|
7
|
+
master,"Master Config","skillspec.json","single source of truth","All skill metadata and workflow definition","<10KB"
|
|
8
|
+
override,"Page/Step Override","{context}.md","context-specific deviations","When specific context needs different rules","<2KB"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
condition,recommendation,priority,rationale
|
|
2
|
+
"deterministic_output",use_scripts,high,"When output must be reproducible, use scripts/ instead of heuristics"
|
|
3
|
+
"large_context",progressive_disclosure,high,"When context >2000 tokens, split into references/ and use index-style notes"
|
|
4
|
+
"reusable_rules",shared_data,medium,"When rules apply to multiple skills, move to .shared/"
|
|
5
|
+
"multi_step_workflow",sequential_with_gates,high,"For complex workflows, add gate steps for validation between phases"
|
|
6
|
+
"user_input_required",collect_step_first,high,"When user input is needed, always start with a collect requirements step"
|
|
7
|
+
"code_output",template_pattern,medium,"When generating code, provide templates in references/ for consistency"
|
|
8
|
+
"review_task",checklist_output,high,"For review/audit tasks, output structured checklists"
|
|
9
|
+
"api_integration",scripts_with_validation,high,"For API calls, use scripts/ with built-in validation"
|
|
10
|
+
"error_prone_step",add_gate,high,"For steps that commonly fail, add a gate with clear error messages"
|
|
11
|
+
"shared_across_skills",move_to_shared,medium,"Data/scripts used by multiple skills should live in .shared/"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id,verb,context_keywords,intent,example_triggers
|
|
2
|
+
create,"create, build, make, generate, scaffold","new, skill, workflow, project","Create new artifact from scratch","创建一个skill, build a new workflow, make a skill for X"
|
|
3
|
+
update,"update, modify, edit, change, refactor","existing, current, improve, add to","Modify existing artifact","更新已有skill, add steps to workflow, refactor the skill"
|
|
4
|
+
review,"review, check, audit, validate, verify","quality, compliance, standards","Inspect for quality/compliance","审查这个skill, validate the spec, check for issues"
|
|
5
|
+
fix,"fix, repair, debug, resolve, patch","bug, issue, error, broken","Resolve a problem","修复这个问题, fix the validation error, resolve the conflict"
|
|
6
|
+
explain,"explain, describe, document, clarify","how, why, what, understand","Provide understanding","解释这个workflow, how does this work, document the process"
|
|
7
|
+
convert,"convert, transform, migrate, port","from, to, into, format","Change format/structure","把prompt转成skill, convert to Claude format, migrate to Windsurf"
|
|
8
|
+
package,"package, bundle, zip, export, distribute","deploy, share, publish","Prepare for distribution","打包成.skill, export for Claude, prepare for distribution"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
id,rule,target,severity,message,fix_hint
|
|
2
|
+
name_kebab,"name must be kebab-case","skillspec.name",error,"Name '{value}' is not kebab-case","Use lowercase letters, digits, and hyphens only"
|
|
3
|
+
description_required,"description is required","skillspec.description",error,"Description is empty","Add a one-sentence description of when to use this skill"
|
|
4
|
+
questions_max_10,"questions must be <= 10","skillspec.questions",error,"Too many questions ({count})","Consolidate or prioritize to 10 or fewer"
|
|
5
|
+
triggers_min_3,"at least 3 triggers recommended","skillspec.triggers",warning,"Only {count} triggers defined","Add more example phrases users might say"
|
|
6
|
+
steps_not_empty,"workflow must have steps","skillspec.workflow.steps",error,"No workflow steps defined","Add at least one step to the workflow"
|
|
7
|
+
step_id_required,"each step needs an id","workflow.steps[].id",error,"Step {index} missing id","Add a unique kebab-case id"
|
|
8
|
+
step_title_required,"each step needs a title","workflow.steps[].title",error,"Step {index} missing title","Add a descriptive title"
|
|
9
|
+
claude_frontmatter_strict,"Claude frontmatter only name+description","claude/SKILL.md",error,"Extra frontmatter keys: {keys}","Remove all keys except name and description"
|
|
10
|
+
no_banned_files,"no README/CHANGELOG in skill bundle","skill bundle",error,"Banned file found: {file}","Remove or move to project root"
|
|
11
|
+
notes_max_length,"step notes should be concise","workflow.steps[].notes",warning,"Notes too long ({chars} chars)","Move details to references/ and use index-style notes"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
id,name,description,when_to_use,structure,example_steps
|
|
2
|
+
sequential,"Sequential Workflow","Steps execute in order, each depends on previous","Simple linear tasks, clear dependencies","step1 -> step2 -> step3","collect -> plan -> generate -> validate -> package"
|
|
3
|
+
conditional,"Conditional Workflow","Branches based on input or intermediate results","Tasks with multiple paths, feature flags","if condition: path_a else: path_b","analyze -> (new_skill: create, existing: update) -> validate"
|
|
4
|
+
gated,"Gated Workflow","Checkpoints that must pass before proceeding","Quality-critical tasks, multi-phase projects","step -> gate -> step -> gate","generate -> lint_gate -> test_gate -> deploy"
|
|
5
|
+
parallel,"Parallel Workflow","Independent steps that can run concurrently","Performance-critical, independent subtasks","[step_a, step_b, step_c] -> merge","[search_style, search_color, search_font] -> synthesize"
|
|
6
|
+
iterative,"Iterative Workflow","Repeat steps until condition met","Refinement tasks, user feedback loops","while !done: step -> check","draft -> review -> (approved: done, rejected: revise)"
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Generator - Generate multi-assistant outputs from skillspec.json
|
|
4
|
+
|
|
5
|
+
Primary target: Windsurf (with backward compatibility for Claude/Cursor/GitHub)
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python3 .shared/skill-creator/scripts/generate.py skills/<skill>/skillspec.json
|
|
9
|
+
python3 .shared/skill-creator/scripts/generate.py skills/<skill>/skillspec.json --target windsurf
|
|
10
|
+
python3 .shared/skill-creator/scripts/generate.py skills/<skill>/skillspec.json --all
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import shutil
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Dict, List
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
23
|
+
REPO_ROOT = SCRIPT_DIR.parent.parent.parent # .shared/skill-creator/scripts -> repo root
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_spec(spec_path: Path) -> Dict[str, Any]:
|
|
27
|
+
return json.loads(spec_path.read_text(encoding="utf-8"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ensure_dir(path: Path) -> None:
|
|
31
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def copytree_safe(src: Path, dst: Path) -> None:
|
|
35
|
+
"""Copy directory tree, skipping if source doesn't exist."""
|
|
36
|
+
if not src.exists():
|
|
37
|
+
return
|
|
38
|
+
if dst.exists():
|
|
39
|
+
shutil.rmtree(dst)
|
|
40
|
+
shutil.copytree(src, dst)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def render_windsurf(spec: Dict[str, Any], spec_dir: Path) -> str:
|
|
44
|
+
"""Render Windsurf workflow markdown (PRIMARY target)."""
|
|
45
|
+
name = spec["name"]
|
|
46
|
+
desc = spec.get("description", "")
|
|
47
|
+
|
|
48
|
+
lines = [
|
|
49
|
+
"---",
|
|
50
|
+
f"description: {desc}",
|
|
51
|
+
"auto_execution_mode: 3",
|
|
52
|
+
"---",
|
|
53
|
+
"",
|
|
54
|
+
f"# {name}",
|
|
55
|
+
"",
|
|
56
|
+
f"{desc}",
|
|
57
|
+
"",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# Triggers
|
|
61
|
+
triggers = spec.get("triggers", [])
|
|
62
|
+
if triggers:
|
|
63
|
+
lines.append("## Triggers")
|
|
64
|
+
lines.append("")
|
|
65
|
+
for t in triggers:
|
|
66
|
+
lines.append(f"- {t}")
|
|
67
|
+
lines.append("")
|
|
68
|
+
|
|
69
|
+
# Prerequisites (check for scripts)
|
|
70
|
+
scripts = spec.get("scripts", [])
|
|
71
|
+
if scripts:
|
|
72
|
+
lines.append("## Prerequisites")
|
|
73
|
+
lines.append("")
|
|
74
|
+
lines.append("```bash")
|
|
75
|
+
lines.append("python3 --version || python --version")
|
|
76
|
+
lines.append("```")
|
|
77
|
+
lines.append("")
|
|
78
|
+
|
|
79
|
+
# Workflow
|
|
80
|
+
lines.append("## Workflow")
|
|
81
|
+
lines.append("")
|
|
82
|
+
workflow = spec.get("workflow", {})
|
|
83
|
+
workflow_type = workflow.get("type", "sequential")
|
|
84
|
+
lines.append(f"Mode: `{workflow_type}`")
|
|
85
|
+
lines.append("")
|
|
86
|
+
|
|
87
|
+
steps = workflow.get("steps", [])
|
|
88
|
+
for i, step in enumerate(steps, 1):
|
|
89
|
+
title = step.get("title", f"Step {i}")
|
|
90
|
+
kind = step.get("kind", "action")
|
|
91
|
+
kind_badge = "🚦 GATE" if kind == "gate" else ""
|
|
92
|
+
|
|
93
|
+
lines.append(f"### Step {i}: {title} {kind_badge}")
|
|
94
|
+
lines.append("")
|
|
95
|
+
|
|
96
|
+
notes = step.get("notes", "")
|
|
97
|
+
if notes:
|
|
98
|
+
# Expand references paths
|
|
99
|
+
notes = notes.replace("`references/", f"`skills/{name}/references/")
|
|
100
|
+
lines.append(notes)
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
commands = step.get("commands", [])
|
|
104
|
+
if commands:
|
|
105
|
+
lines.append("```bash")
|
|
106
|
+
for cmd in commands:
|
|
107
|
+
lines.append(cmd)
|
|
108
|
+
lines.append("```")
|
|
109
|
+
lines.append("")
|
|
110
|
+
|
|
111
|
+
# Resources
|
|
112
|
+
refs = spec.get("references", [])
|
|
113
|
+
if refs:
|
|
114
|
+
lines.append("## References")
|
|
115
|
+
lines.append("")
|
|
116
|
+
for ref in refs:
|
|
117
|
+
ref_path = f"skills/{name}/{ref}"
|
|
118
|
+
lines.append(f"- `{ref_path}`")
|
|
119
|
+
lines.append("")
|
|
120
|
+
|
|
121
|
+
# Footer
|
|
122
|
+
lines.append("---")
|
|
123
|
+
lines.append("")
|
|
124
|
+
lines.append("*Generated by skill-creator. Source: `skills/{}/skillspec.json`*".format(name))
|
|
125
|
+
|
|
126
|
+
return "\n".join(lines)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def render_claude(spec: Dict[str, Any], spec_dir: Path) -> str:
|
|
130
|
+
"""Render Claude SKILL.md (backward compat, strict frontmatter)."""
|
|
131
|
+
name = spec["name"]
|
|
132
|
+
desc = spec.get("description", "")
|
|
133
|
+
|
|
134
|
+
lines = [
|
|
135
|
+
"---",
|
|
136
|
+
f"name: {name}",
|
|
137
|
+
f"description: {desc}",
|
|
138
|
+
"---",
|
|
139
|
+
"",
|
|
140
|
+
f"# {name}",
|
|
141
|
+
"",
|
|
142
|
+
"## What this skill does",
|
|
143
|
+
"",
|
|
144
|
+
desc,
|
|
145
|
+
"",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
triggers = spec.get("triggers", [])
|
|
149
|
+
if triggers:
|
|
150
|
+
lines.append("## When to use")
|
|
151
|
+
lines.append("")
|
|
152
|
+
for t in triggers:
|
|
153
|
+
lines.append(f"- {t}")
|
|
154
|
+
lines.append("")
|
|
155
|
+
|
|
156
|
+
questions = spec.get("questions", [])
|
|
157
|
+
if questions:
|
|
158
|
+
lines.append("## Requirement collection (at most 10 questions)")
|
|
159
|
+
lines.append("")
|
|
160
|
+
for i, q in enumerate(questions, 1):
|
|
161
|
+
lines.append(f"{i}. {q}")
|
|
162
|
+
lines.append("")
|
|
163
|
+
|
|
164
|
+
lines.append("## Workflow")
|
|
165
|
+
lines.append("")
|
|
166
|
+
workflow = spec.get("workflow", {})
|
|
167
|
+
for i, step in enumerate(workflow.get("steps", []), 1):
|
|
168
|
+
title = step.get("title", f"Step {i}")
|
|
169
|
+
kind = step.get("kind", "action")
|
|
170
|
+
lines.append(f"### {i}. {title}" + (" (GATE)" if kind == "gate" else ""))
|
|
171
|
+
notes = step.get("notes", "")
|
|
172
|
+
if notes:
|
|
173
|
+
lines.append(notes)
|
|
174
|
+
commands = step.get("commands", [])
|
|
175
|
+
if commands:
|
|
176
|
+
lines.append("```bash")
|
|
177
|
+
lines.extend(commands)
|
|
178
|
+
lines.append("```")
|
|
179
|
+
lines.append("")
|
|
180
|
+
|
|
181
|
+
return "\n".join(lines)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def render_cursor(spec: Dict[str, Any], spec_dir: Path) -> str:
|
|
185
|
+
"""Render Cursor command markdown (backward compat)."""
|
|
186
|
+
name = spec["name"]
|
|
187
|
+
desc = spec.get("description", "")
|
|
188
|
+
|
|
189
|
+
lines = [f"# {name}", "", desc, ""]
|
|
190
|
+
|
|
191
|
+
triggers = spec.get("triggers", [])
|
|
192
|
+
if triggers:
|
|
193
|
+
lines.append("## When to use")
|
|
194
|
+
for t in triggers:
|
|
195
|
+
lines.append(f"- {t}")
|
|
196
|
+
lines.append("")
|
|
197
|
+
|
|
198
|
+
lines.append("## Steps")
|
|
199
|
+
lines.append("")
|
|
200
|
+
workflow = spec.get("workflow", {})
|
|
201
|
+
for i, step in enumerate(workflow.get("steps", []), 1):
|
|
202
|
+
lines.append(f"{i}. {step.get('title', 'Step')}")
|
|
203
|
+
|
|
204
|
+
return "\n".join(lines)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def render_github(spec: Dict[str, Any], spec_dir: Path) -> str:
|
|
208
|
+
"""Render GitHub Skills SKILL.md (backward compat)."""
|
|
209
|
+
name = spec["name"]
|
|
210
|
+
desc = spec.get("description", "")
|
|
211
|
+
|
|
212
|
+
lines = [
|
|
213
|
+
"---",
|
|
214
|
+
f"name: {name}",
|
|
215
|
+
f"description: {desc}",
|
|
216
|
+
"compatibility: Multi-assistant (Claude/Windsurf/Cursor/GitHub)",
|
|
217
|
+
"---",
|
|
218
|
+
"",
|
|
219
|
+
f"# {name}",
|
|
220
|
+
"",
|
|
221
|
+
desc,
|
|
222
|
+
"",
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
workflow = spec.get("workflow", {})
|
|
226
|
+
lines.append("## Workflow")
|
|
227
|
+
lines.append("")
|
|
228
|
+
for i, step in enumerate(workflow.get("steps", []), 1):
|
|
229
|
+
lines.append(f"### {i}. {step.get('title', 'Step')}")
|
|
230
|
+
if step.get("notes"):
|
|
231
|
+
lines.append(step["notes"])
|
|
232
|
+
lines.append("")
|
|
233
|
+
|
|
234
|
+
return "\n".join(lines)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def generate_target(spec: Dict[str, Any], spec_dir: Path, target: str, repo_root: Path) -> Path:
|
|
238
|
+
"""Generate output for a specific target."""
|
|
239
|
+
name = spec["name"]
|
|
240
|
+
|
|
241
|
+
if target == "windsurf":
|
|
242
|
+
content = render_windsurf(spec, spec_dir)
|
|
243
|
+
out_path = repo_root / ".windsurf" / "workflows" / f"{name}.md"
|
|
244
|
+
elif target == "claude":
|
|
245
|
+
content = render_claude(spec, spec_dir)
|
|
246
|
+
out_path = repo_root / ".claude" / "skills" / name / "SKILL.md"
|
|
247
|
+
elif target == "cursor":
|
|
248
|
+
content = render_cursor(spec, spec_dir)
|
|
249
|
+
out_path = repo_root / ".cursor" / "commands" / f"{name}.md"
|
|
250
|
+
elif target == "github":
|
|
251
|
+
content = render_github(spec, spec_dir)
|
|
252
|
+
out_path = repo_root / ".github" / "skills" / name / "SKILL.md"
|
|
253
|
+
else:
|
|
254
|
+
raise ValueError(f"Unknown target: {target}")
|
|
255
|
+
|
|
256
|
+
ensure_dir(out_path.parent)
|
|
257
|
+
out_path.write_text(content, encoding="utf-8")
|
|
258
|
+
|
|
259
|
+
# Copy resources for Claude and GitHub (they need local copies)
|
|
260
|
+
if target in ("claude", "github"):
|
|
261
|
+
res_dst = out_path.parent / "resources"
|
|
262
|
+
copytree_safe(spec_dir / "references", res_dst / "references")
|
|
263
|
+
copytree_safe(spec_dir / "scripts", res_dst / "scripts")
|
|
264
|
+
copytree_safe(spec_dir / "assets", res_dst / "assets")
|
|
265
|
+
|
|
266
|
+
return out_path
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def main() -> None:
|
|
270
|
+
parser = argparse.ArgumentParser(description="Generate multi-assistant outputs from skillspec.json")
|
|
271
|
+
parser.add_argument("spec", help="Path to skillspec.json")
|
|
272
|
+
parser.add_argument("--target", "-t", choices=["windsurf", "claude", "cursor", "github"],
|
|
273
|
+
help="Generate for specific target (default: windsurf)")
|
|
274
|
+
parser.add_argument("--all", "-a", action="store_true", help="Generate for all targets")
|
|
275
|
+
parser.add_argument("--repo-root", "-r", default=str(REPO_ROOT), help="Repository root")
|
|
276
|
+
|
|
277
|
+
args = parser.parse_args()
|
|
278
|
+
|
|
279
|
+
spec_path = Path(args.spec).resolve()
|
|
280
|
+
repo_root = Path(args.repo_root).resolve()
|
|
281
|
+
|
|
282
|
+
if not spec_path.exists():
|
|
283
|
+
print(f"Spec not found: {spec_path}", file=sys.stderr)
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
spec = load_spec(spec_path)
|
|
287
|
+
spec_dir = spec_path.parent
|
|
288
|
+
|
|
289
|
+
if args.all:
|
|
290
|
+
targets = ["windsurf", "claude", "cursor", "github"]
|
|
291
|
+
else:
|
|
292
|
+
targets = [args.target or "windsurf"]
|
|
293
|
+
|
|
294
|
+
for target in targets:
|
|
295
|
+
out_path = generate_target(spec, spec_dir, target, repo_root)
|
|
296
|
+
print(f"Generated: {out_path}")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
if __name__ == "__main__":
|
|
300
|
+
main()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Packager - Package skills for distribution
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python3 .shared/skill-creator/scripts/package.py --target claude --skill <skill>
|
|
7
|
+
python3 .shared/skill-creator/scripts/package.py --target repo
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import zipfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
20
|
+
REPO_ROOT = SCRIPT_DIR.parent.parent.parent
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ensure_dir(path: Path) -> None:
|
|
24
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def package_claude_skill(skill_name: str, repo_root: Path, out_dir: Path) -> Path:
|
|
28
|
+
"""
|
|
29
|
+
Package a Claude skill as .skill zip file.
|
|
30
|
+
|
|
31
|
+
Claude .skill format:
|
|
32
|
+
- Zip root must be the skill folder
|
|
33
|
+
- Must contain SKILL.md with strict frontmatter
|
|
34
|
+
- Resources in resources/ subfolder
|
|
35
|
+
"""
|
|
36
|
+
skill_dir = repo_root / ".claude" / "skills" / skill_name
|
|
37
|
+
|
|
38
|
+
if not skill_dir.exists():
|
|
39
|
+
print(f"ERROR: Claude skill not found: {skill_dir}", file=sys.stderr)
|
|
40
|
+
print("Run generate.py first to create Claude output", file=sys.stderr)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
# Validate before packaging
|
|
44
|
+
validate_script = SCRIPT_DIR / "validate.py"
|
|
45
|
+
spec_path = repo_root / "skills" / skill_name / "skillspec.json"
|
|
46
|
+
|
|
47
|
+
if validate_script.exists() and spec_path.exists():
|
|
48
|
+
result = subprocess.run(
|
|
49
|
+
[sys.executable, str(validate_script), str(spec_path)],
|
|
50
|
+
capture_output=True,
|
|
51
|
+
text=True
|
|
52
|
+
)
|
|
53
|
+
if result.returncode != 0:
|
|
54
|
+
print("Validation failed:", file=sys.stderr)
|
|
55
|
+
print(result.stdout, file=sys.stderr)
|
|
56
|
+
print(result.stderr, file=sys.stderr)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
ensure_dir(out_dir)
|
|
60
|
+
out_path = out_dir / f"{skill_name}.skill"
|
|
61
|
+
|
|
62
|
+
with zipfile.ZipFile(out_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
63
|
+
for path in skill_dir.rglob("*"):
|
|
64
|
+
if path.is_file():
|
|
65
|
+
arcname = path.relative_to(skill_dir.parent)
|
|
66
|
+
zf.write(path, arcname)
|
|
67
|
+
|
|
68
|
+
return out_path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def package_repo_zip(repo_root: Path, out_dir: Path) -> Path:
|
|
72
|
+
"""Package entire repo as zip for easy sharing."""
|
|
73
|
+
ensure_dir(out_dir)
|
|
74
|
+
out_path = out_dir / f"{repo_root.name}.zip"
|
|
75
|
+
|
|
76
|
+
# Use git archive if available
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["git", "archive", "--format=zip", "--output", str(out_path), "HEAD"],
|
|
80
|
+
cwd=repo_root,
|
|
81
|
+
capture_output=True
|
|
82
|
+
)
|
|
83
|
+
if result.returncode == 0:
|
|
84
|
+
return out_path
|
|
85
|
+
except FileNotFoundError:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Fallback: manual zip (respecting gitignore patterns)
|
|
89
|
+
gitignore = repo_root / ".gitignore"
|
|
90
|
+
ignore_patterns = set()
|
|
91
|
+
if gitignore.exists():
|
|
92
|
+
for line in gitignore.read_text(encoding="utf-8").splitlines():
|
|
93
|
+
line = line.strip()
|
|
94
|
+
if line and not line.startswith("#"):
|
|
95
|
+
ignore_patterns.add(line.strip("/"))
|
|
96
|
+
|
|
97
|
+
with zipfile.ZipFile(out_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
98
|
+
for path in repo_root.rglob("*"):
|
|
99
|
+
if path.is_file():
|
|
100
|
+
rel = path.relative_to(repo_root)
|
|
101
|
+
|
|
102
|
+
# Skip common ignored patterns
|
|
103
|
+
skip = False
|
|
104
|
+
for part in rel.parts:
|
|
105
|
+
if part in ignore_patterns or part.startswith("."):
|
|
106
|
+
skip = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if not skip:
|
|
110
|
+
zf.write(path, rel)
|
|
111
|
+
|
|
112
|
+
return out_path
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main() -> None:
|
|
116
|
+
parser = argparse.ArgumentParser(description="Package skills for distribution")
|
|
117
|
+
parser.add_argument("--target", "-t", choices=["claude", "repo"], required=True,
|
|
118
|
+
help="Package target")
|
|
119
|
+
parser.add_argument("--skill", "-s", help="Skill name (required for --target claude)")
|
|
120
|
+
parser.add_argument("--repo-root", "-r", default=str(REPO_ROOT), help="Repository root")
|
|
121
|
+
parser.add_argument("--out-dir", "-o", help="Output directory (default: dist/)")
|
|
122
|
+
|
|
123
|
+
args = parser.parse_args()
|
|
124
|
+
|
|
125
|
+
repo_root = Path(args.repo_root).resolve()
|
|
126
|
+
out_dir = Path(args.out_dir) if args.out_dir else repo_root / "dist"
|
|
127
|
+
|
|
128
|
+
if args.target == "claude":
|
|
129
|
+
if not args.skill:
|
|
130
|
+
print("ERROR: --skill required for --target claude", file=sys.stderr)
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
out_path = package_claude_skill(args.skill, repo_root, out_dir)
|
|
133
|
+
else:
|
|
134
|
+
out_path = package_repo_zip(repo_root, out_dir)
|
|
135
|
+
|
|
136
|
+
print(f"Packaged: {out_path}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
main()
|