@phuc1403/musketeer 0.7.0 → 0.9.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.
- package/README.md +49 -49
- package/manifest.json +333 -301
- package/package.json +1 -1
- package/template/.claude/agents/code-reviewer.md +182 -166
- package/template/.claude/hooks/git-skill-reminder.cjs +53 -0
- package/template/.claude/hooks/inject-design-docs.cjs +13 -13
- package/template/.claude/hooks/inject-ubiquitous-language.cjs +52 -0
- package/template/.claude/hooks/lib/colors.cjs +180 -122
- package/template/.claude/hooks/lib/transcript-parser.cjs +300 -277
- package/template/.claude/skills/code-review/SKILL.md +201 -54
- package/template/.claude/skills/code-review/references/checklist-workflow.md +96 -0
- package/template/.claude/skills/code-review/references/checklists/api.md +52 -52
- package/template/.claude/skills/code-review/references/checklists/base.md +100 -100
- package/template/.claude/skills/code-review/references/checklists/web-app.md +54 -54
- package/template/.claude/skills/code-review/references/code-review-reception.md +113 -0
- package/template/.claude/skills/code-review/references/codebase-scan-workflow.md +30 -0
- package/template/.claude/skills/code-review/references/edge-case-scouting.md +119 -0
- package/template/.claude/skills/code-review/references/input-mode-resolution.md +135 -0
- package/template/.claude/skills/code-review/references/parallel-review-workflow.md +76 -0
- package/template/.claude/skills/code-review/references/requesting-code-review.md +116 -0
- package/template/.claude/skills/code-review/references/spec-compliance-review.md +43 -0
- package/template/.claude/skills/code-review/references/task-management-reviews.md +140 -0
- package/template/.claude/skills/code-review/references/verification-before-completion.md +139 -0
- package/template/.claude/skills/context-map/SKILL.md +1 -1
- package/template/.claude/skills/git/SKILL.md +131 -115
- package/template/.claude/skills/git/references/branch-management.md +88 -88
- package/template/.claude/skills/git/references/commit-standards.md +46 -46
- package/template/.claude/skills/git/references/context-efficiency.md +54 -0
- package/template/.claude/skills/git/references/gh-cli-guide.md +109 -109
- package/template/.claude/skills/git/references/safety-protocols.md +69 -69
- package/template/.claude/skills/git/references/workflow-commit.md +58 -58
- package/template/.claude/skills/git/references/workflow-merge-pr.md +136 -0
- package/template/.claude/skills/git/references/workflow-merge.md +48 -48
- package/template/.claude/skills/git/references/workflow-pr.md +58 -58
- package/template/.claude/skills/git/references/workflow-push.md +52 -52
- package/template/.claude/skills/knowledge-crunching/SKILL.md +56 -92
- package/template/.claude/skills/knowledge-crunching/assets/ubiquitous-language.template.md +3 -0
- package/template/.claude/skills/skill-creator/LICENSE.txt +201 -201
- package/template/.claude/skills/skill-creator/SKILL.md +154 -149
- package/template/.claude/skills/skill-creator/agents/analyzer.md +274 -274
- package/template/.claude/skills/skill-creator/agents/comparator.md +202 -202
- package/template/.claude/skills/skill-creator/agents/grader.md +223 -223
- package/template/.claude/skills/skill-creator/assets/eval_review.html +146 -146
- package/template/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -471
- package/template/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -1325
- package/template/.claude/skills/skill-creator/references/benchmark-optimization-guide.md +86 -86
- package/template/.claude/skills/skill-creator/references/distribution-guide.md +79 -79
- package/template/.claude/skills/skill-creator/references/eval-infrastructure-guide.md +129 -129
- package/template/.claude/skills/skill-creator/references/eval-schemas.md +121 -121
- package/template/.claude/skills/skill-creator/references/mcp-skills-integration.md +71 -71
- package/template/.claude/skills/skill-creator/references/metadata-quality-criteria.md +94 -94
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-hosting.md +104 -104
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-overview.md +89 -89
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-schema.md +93 -93
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-sources.md +103 -103
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-troubleshooting.md +76 -76
- package/template/.claude/skills/skill-creator/references/script-quality-criteria.md +106 -106
- package/template/.claude/skills/skill-creator/references/skill-anatomy-and-requirements.md +77 -77
- package/template/.claude/skills/skill-creator/references/skill-creation-workflow.md +152 -151
- package/template/.claude/skills/skill-creator/references/skill-design-patterns.md +75 -75
- package/template/.claude/skills/skill-creator/references/skillmark-benchmark-criteria.md +102 -102
- package/template/.claude/skills/skill-creator/references/structure-organization-criteria.md +114 -114
- package/template/.claude/skills/skill-creator/references/testing-and-iteration.md +78 -78
- package/template/.claude/skills/skill-creator/references/token-efficiency-criteria.md +74 -74
- package/template/.claude/skills/skill-creator/references/troubleshooting-guide.md +81 -81
- package/template/.claude/skills/skill-creator/references/validation-checklist.md +83 -83
- package/template/.claude/skills/skill-creator/references/writing-effective-instructions.md +88 -88
- package/template/.claude/skills/skill-creator/references/yaml-frontmatter-reference.md +92 -92
- package/template/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -401
- package/template/.claude/skills/skill-creator/scripts/encoding_utils.py +36 -36
- package/template/.claude/skills/skill-creator/scripts/generate_report.py +326 -326
- package/template/.claude/skills/skill-creator/scripts/improve_description.py +248 -248
- package/template/.claude/skills/skill-creator/scripts/init_skill.py +360 -360
- package/template/.claude/skills/skill-creator/scripts/package_skill.py +143 -143
- package/template/.claude/skills/skill-creator/scripts/quick_validate.py +110 -110
- package/template/.claude/skills/skill-creator/scripts/run_eval.py +310 -310
- package/template/.claude/skills/skill-creator/scripts/run_loop.py +332 -332
- package/template/.claude/skills/skill-creator/scripts/utils.py +47 -47
- package/template/.claude/statusline.cjs +0 -0
- package/template/.claude/hooks/inject-context.cjs +0 -52
- package/template/.claude/skills/code-review/references/adversarial-review.md +0 -223
- package/template/.claude/skills/knowledge-crunching/assets/context.template.md +0 -59
- package/template/.claude/skills/knowledge-crunching/references/crunching-dialogue.md +0 -113
- /package/template/.claude/hooks/{usage-context-awareness.cjs → usage-quota-cache-refresh.cjs} +0 -0
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Skill Packager - Creates a distributable zip file of a skill folder
|
|
4
|
-
|
|
5
|
-
Usage:
|
|
6
|
-
python utils/package_skill.py <path/to/skill-folder> [output-directory]
|
|
7
|
-
|
|
8
|
-
Example:
|
|
9
|
-
python utils/package_skill.py skills/public/my-skill
|
|
10
|
-
python utils/package_skill.py skills/public/my-skill ./dist
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import fnmatch
|
|
14
|
-
import sys
|
|
15
|
-
import zipfile
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
from encoding_utils import configure_utf8_console
|
|
19
|
-
from quick_validate import validate_skill
|
|
20
|
-
|
|
21
|
-
# Fix Windows console encoding for Unicode output (emojis, arrows)
|
|
22
|
-
configure_utf8_console()
|
|
23
|
-
|
|
24
|
-
# Exclusion patterns (from official Anthropic skill-creator)
|
|
25
|
-
EXCLUDE_DIRS = {'__pycache__', 'node_modules', '.git', '.DS_Store'}
|
|
26
|
-
EXCLUDE_GLOBS = {'*.pyc', '*.pyo', '.DS_Store', '*.egg-info'}
|
|
27
|
-
ROOT_EXCLUDE_DIRS = {'evals'} # Only excluded at skill root level
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def package_skill(skill_path, output_dir=None):
|
|
31
|
-
"""
|
|
32
|
-
Package a skill folder into a zip file.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
skill_path: Path to the skill folder
|
|
36
|
-
output_dir: Optional output directory for the zip file (defaults to current directory)
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Path to the created zip file, or None if error
|
|
40
|
-
"""
|
|
41
|
-
skill_path = Path(skill_path).resolve()
|
|
42
|
-
|
|
43
|
-
# Validate skill folder exists
|
|
44
|
-
if not skill_path.exists():
|
|
45
|
-
print(f"❌ Error: Skill folder not found: {skill_path}")
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
|
-
if not skill_path.is_dir():
|
|
49
|
-
print(f"❌ Error: Path is not a directory: {skill_path}")
|
|
50
|
-
return None
|
|
51
|
-
|
|
52
|
-
# Validate SKILL.md exists
|
|
53
|
-
skill_md = skill_path / "SKILL.md"
|
|
54
|
-
if not skill_md.exists():
|
|
55
|
-
print(f"❌ Error: SKILL.md not found in {skill_path}")
|
|
56
|
-
return None
|
|
57
|
-
|
|
58
|
-
# Run validation before packaging
|
|
59
|
-
print("🔍 Validating skill...")
|
|
60
|
-
valid, message = validate_skill(skill_path)
|
|
61
|
-
if not valid:
|
|
62
|
-
print(f"❌ Validation failed: {message}")
|
|
63
|
-
print(" Please fix the validation errors before packaging.")
|
|
64
|
-
return None
|
|
65
|
-
print(f"✅ {message}\n")
|
|
66
|
-
|
|
67
|
-
# Determine output location
|
|
68
|
-
skill_name = skill_path.name
|
|
69
|
-
if output_dir:
|
|
70
|
-
output_path = Path(output_dir).resolve()
|
|
71
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
72
|
-
else:
|
|
73
|
-
output_path = Path.cwd()
|
|
74
|
-
|
|
75
|
-
zip_filename = output_path / f"{skill_name}.zip"
|
|
76
|
-
|
|
77
|
-
# Create the zip file
|
|
78
|
-
try:
|
|
79
|
-
with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
80
|
-
# Walk through the skill directory, excluding unwanted files
|
|
81
|
-
skipped = []
|
|
82
|
-
for file_path in skill_path.rglob('*'):
|
|
83
|
-
if file_path.is_file():
|
|
84
|
-
rel = file_path.relative_to(skill_path)
|
|
85
|
-
parts = rel.parts
|
|
86
|
-
|
|
87
|
-
# Skip excluded directories
|
|
88
|
-
if any(p in EXCLUDE_DIRS for p in parts):
|
|
89
|
-
skipped.append(str(rel))
|
|
90
|
-
continue
|
|
91
|
-
|
|
92
|
-
# Skip root-only excluded dirs (e.g., evals/)
|
|
93
|
-
if parts[0] in ROOT_EXCLUDE_DIRS:
|
|
94
|
-
skipped.append(str(rel))
|
|
95
|
-
continue
|
|
96
|
-
|
|
97
|
-
# Skip excluded file patterns
|
|
98
|
-
if any(fnmatch.fnmatch(file_path.name, g) for g in EXCLUDE_GLOBS):
|
|
99
|
-
skipped.append(str(rel))
|
|
100
|
-
continue
|
|
101
|
-
|
|
102
|
-
arcname = file_path.relative_to(skill_path.parent)
|
|
103
|
-
zipf.write(file_path, arcname)
|
|
104
|
-
print(f" Added: {arcname}")
|
|
105
|
-
|
|
106
|
-
if skipped:
|
|
107
|
-
print(f"\n Skipped {len(skipped)} file(s): {', '.join(skipped[:5])}"
|
|
108
|
-
+ ("..." if len(skipped) > 5 else ""))
|
|
109
|
-
|
|
110
|
-
print(f"\n✅ Successfully packaged skill to: {zip_filename}")
|
|
111
|
-
return zip_filename
|
|
112
|
-
|
|
113
|
-
except Exception as e:
|
|
114
|
-
print(f"❌ Error creating zip file: {e}")
|
|
115
|
-
return None
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def main():
|
|
119
|
-
if len(sys.argv) < 2:
|
|
120
|
-
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
|
|
121
|
-
print("\nExample:")
|
|
122
|
-
print(" python utils/package_skill.py skills/public/my-skill")
|
|
123
|
-
print(" python utils/package_skill.py skills/public/my-skill ./dist")
|
|
124
|
-
sys.exit(1)
|
|
125
|
-
|
|
126
|
-
skill_path = sys.argv[1]
|
|
127
|
-
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
|
128
|
-
|
|
129
|
-
print(f"📦 Packaging skill: {skill_path}")
|
|
130
|
-
if output_dir:
|
|
131
|
-
print(f" Output directory: {output_dir}")
|
|
132
|
-
print()
|
|
133
|
-
|
|
134
|
-
result = package_skill(skill_path, output_dir)
|
|
135
|
-
|
|
136
|
-
if result:
|
|
137
|
-
sys.exit(0)
|
|
138
|
-
else:
|
|
139
|
-
sys.exit(1)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if __name__ == "__main__":
|
|
143
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Packager - Creates a distributable zip file of a skill folder
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python utils/package_skill.py <path/to/skill-folder> [output-directory]
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
python utils/package_skill.py skills/public/my-skill
|
|
10
|
+
python utils/package_skill.py skills/public/my-skill ./dist
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import fnmatch
|
|
14
|
+
import sys
|
|
15
|
+
import zipfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from encoding_utils import configure_utf8_console
|
|
19
|
+
from quick_validate import validate_skill
|
|
20
|
+
|
|
21
|
+
# Fix Windows console encoding for Unicode output (emojis, arrows)
|
|
22
|
+
configure_utf8_console()
|
|
23
|
+
|
|
24
|
+
# Exclusion patterns (from official Anthropic skill-creator)
|
|
25
|
+
EXCLUDE_DIRS = {'__pycache__', 'node_modules', '.git', '.DS_Store'}
|
|
26
|
+
EXCLUDE_GLOBS = {'*.pyc', '*.pyo', '.DS_Store', '*.egg-info'}
|
|
27
|
+
ROOT_EXCLUDE_DIRS = {'evals'} # Only excluded at skill root level
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def package_skill(skill_path, output_dir=None):
|
|
31
|
+
"""
|
|
32
|
+
Package a skill folder into a zip file.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
skill_path: Path to the skill folder
|
|
36
|
+
output_dir: Optional output directory for the zip file (defaults to current directory)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Path to the created zip file, or None if error
|
|
40
|
+
"""
|
|
41
|
+
skill_path = Path(skill_path).resolve()
|
|
42
|
+
|
|
43
|
+
# Validate skill folder exists
|
|
44
|
+
if not skill_path.exists():
|
|
45
|
+
print(f"❌ Error: Skill folder not found: {skill_path}")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if not skill_path.is_dir():
|
|
49
|
+
print(f"❌ Error: Path is not a directory: {skill_path}")
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
# Validate SKILL.md exists
|
|
53
|
+
skill_md = skill_path / "SKILL.md"
|
|
54
|
+
if not skill_md.exists():
|
|
55
|
+
print(f"❌ Error: SKILL.md not found in {skill_path}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# Run validation before packaging
|
|
59
|
+
print("🔍 Validating skill...")
|
|
60
|
+
valid, message = validate_skill(skill_path)
|
|
61
|
+
if not valid:
|
|
62
|
+
print(f"❌ Validation failed: {message}")
|
|
63
|
+
print(" Please fix the validation errors before packaging.")
|
|
64
|
+
return None
|
|
65
|
+
print(f"✅ {message}\n")
|
|
66
|
+
|
|
67
|
+
# Determine output location
|
|
68
|
+
skill_name = skill_path.name
|
|
69
|
+
if output_dir:
|
|
70
|
+
output_path = Path(output_dir).resolve()
|
|
71
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
else:
|
|
73
|
+
output_path = Path.cwd()
|
|
74
|
+
|
|
75
|
+
zip_filename = output_path / f"{skill_name}.zip"
|
|
76
|
+
|
|
77
|
+
# Create the zip file
|
|
78
|
+
try:
|
|
79
|
+
with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
80
|
+
# Walk through the skill directory, excluding unwanted files
|
|
81
|
+
skipped = []
|
|
82
|
+
for file_path in skill_path.rglob('*'):
|
|
83
|
+
if file_path.is_file():
|
|
84
|
+
rel = file_path.relative_to(skill_path)
|
|
85
|
+
parts = rel.parts
|
|
86
|
+
|
|
87
|
+
# Skip excluded directories
|
|
88
|
+
if any(p in EXCLUDE_DIRS for p in parts):
|
|
89
|
+
skipped.append(str(rel))
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Skip root-only excluded dirs (e.g., evals/)
|
|
93
|
+
if parts[0] in ROOT_EXCLUDE_DIRS:
|
|
94
|
+
skipped.append(str(rel))
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Skip excluded file patterns
|
|
98
|
+
if any(fnmatch.fnmatch(file_path.name, g) for g in EXCLUDE_GLOBS):
|
|
99
|
+
skipped.append(str(rel))
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
arcname = file_path.relative_to(skill_path.parent)
|
|
103
|
+
zipf.write(file_path, arcname)
|
|
104
|
+
print(f" Added: {arcname}")
|
|
105
|
+
|
|
106
|
+
if skipped:
|
|
107
|
+
print(f"\n Skipped {len(skipped)} file(s): {', '.join(skipped[:5])}"
|
|
108
|
+
+ ("..." if len(skipped) > 5 else ""))
|
|
109
|
+
|
|
110
|
+
print(f"\n✅ Successfully packaged skill to: {zip_filename}")
|
|
111
|
+
return zip_filename
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"❌ Error creating zip file: {e}")
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def main():
|
|
119
|
+
if len(sys.argv) < 2:
|
|
120
|
+
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
|
|
121
|
+
print("\nExample:")
|
|
122
|
+
print(" python utils/package_skill.py skills/public/my-skill")
|
|
123
|
+
print(" python utils/package_skill.py skills/public/my-skill ./dist")
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
skill_path = sys.argv[1]
|
|
127
|
+
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
|
128
|
+
|
|
129
|
+
print(f"📦 Packaging skill: {skill_path}")
|
|
130
|
+
if output_dir:
|
|
131
|
+
print(f" Output directory: {output_dir}")
|
|
132
|
+
print()
|
|
133
|
+
|
|
134
|
+
result = package_skill(skill_path, output_dir)
|
|
135
|
+
|
|
136
|
+
if result:
|
|
137
|
+
sys.exit(0)
|
|
138
|
+
else:
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Quick validation script for skills - minimal version
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import sys
|
|
7
|
-
import re
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from encoding_utils import configure_utf8_console, read_text_utf8
|
|
11
|
-
|
|
12
|
-
# Fix Windows console encoding for Unicode output
|
|
13
|
-
configure_utf8_console()
|
|
14
|
-
|
|
15
|
-
def validate_skill(skill_path):
|
|
16
|
-
"""Basic validation of a skill"""
|
|
17
|
-
skill_path = Path(skill_path)
|
|
18
|
-
|
|
19
|
-
# Check SKILL.md exists
|
|
20
|
-
skill_md = skill_path / 'SKILL.md'
|
|
21
|
-
if not skill_md.exists():
|
|
22
|
-
return False, "SKILL.md not found"
|
|
23
|
-
|
|
24
|
-
# Read and validate frontmatter
|
|
25
|
-
content = read_text_utf8(skill_md)
|
|
26
|
-
if not content.startswith('---'):
|
|
27
|
-
return False, "No YAML frontmatter found"
|
|
28
|
-
|
|
29
|
-
# Extract frontmatter
|
|
30
|
-
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
31
|
-
if not match:
|
|
32
|
-
return False, "Invalid frontmatter format"
|
|
33
|
-
|
|
34
|
-
frontmatter = match.group(1)
|
|
35
|
-
|
|
36
|
-
# Check required fields
|
|
37
|
-
if 'name:' not in frontmatter:
|
|
38
|
-
return False, "Missing 'name' in frontmatter"
|
|
39
|
-
if 'description:' not in frontmatter:
|
|
40
|
-
return False, "Missing 'description' in frontmatter"
|
|
41
|
-
|
|
42
|
-
# Extract name for validation
|
|
43
|
-
name_match = re.search(r'name:\s*(.+)', frontmatter)
|
|
44
|
-
if name_match:
|
|
45
|
-
name = name_match.group(1).strip().strip('"').strip("'")
|
|
46
|
-
|
|
47
|
-
# Support namespaced identifiers: ck:skill-name (single namespace segment)
|
|
48
|
-
if name.count(':') > 1:
|
|
49
|
-
return False, (
|
|
50
|
-
f"Name '{name}' is invalid. Use either 'skill-name' or "
|
|
51
|
-
"'namespace:skill-name' with a single colon."
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
namespace = None
|
|
55
|
-
skill_id = name
|
|
56
|
-
if ':' in name:
|
|
57
|
-
namespace, skill_id = name.split(':', 1)
|
|
58
|
-
|
|
59
|
-
id_pattern = r'^[a-z0-9-]+$'
|
|
60
|
-
if namespace and not re.match(id_pattern, namespace):
|
|
61
|
-
return False, (
|
|
62
|
-
f"Namespace '{namespace}' must be lowercase letters, digits, and hyphens only"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
if not re.match(id_pattern, skill_id):
|
|
66
|
-
return False, (
|
|
67
|
-
f"Skill id '{skill_id}' must be lowercase letters, digits, and hyphens only"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
for segment_name, segment in [("namespace", namespace), ("skill id", skill_id)]:
|
|
71
|
-
if segment and (segment.startswith('-') or segment.endswith('-') or '--' in segment):
|
|
72
|
-
return False, (
|
|
73
|
-
f"{segment_name.capitalize()} '{segment}' cannot start/end with hyphen "
|
|
74
|
-
"or contain consecutive hyphens"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# Validate name length (official max: 64 chars)
|
|
78
|
-
if name_match:
|
|
79
|
-
if len(skill_id) > 64:
|
|
80
|
-
return False, f"Skill id '{skill_id}' exceeds 64 characters ({len(skill_id)})"
|
|
81
|
-
if namespace and len(namespace) > 64:
|
|
82
|
-
return False, f"Namespace '{namespace}' exceeds 64 characters ({len(namespace)})"
|
|
83
|
-
|
|
84
|
-
# Extract and validate description
|
|
85
|
-
desc_match = re.search(r'description:\s*(.+)', frontmatter)
|
|
86
|
-
if desc_match:
|
|
87
|
-
description = desc_match.group(1).strip().strip('"').strip("'")
|
|
88
|
-
|
|
89
|
-
# YAML block scalar indicators are valid (e.g. description: >-)
|
|
90
|
-
if description in {'>', '>-', '|', '|-'}:
|
|
91
|
-
description = ''
|
|
92
|
-
|
|
93
|
-
# Check for angle brackets
|
|
94
|
-
if '<' in description or '>' in description:
|
|
95
|
-
return False, "Description cannot contain angle brackets (< or >)"
|
|
96
|
-
|
|
97
|
-
# Check description length (official max: 1024 chars)
|
|
98
|
-
if len(description) > 1024:
|
|
99
|
-
return False, f"Description exceeds 1024 characters ({len(description)})"
|
|
100
|
-
|
|
101
|
-
return True, "Skill is valid!"
|
|
102
|
-
|
|
103
|
-
if __name__ == "__main__":
|
|
104
|
-
if len(sys.argv) != 2:
|
|
105
|
-
print("Usage: python quick_validate.py <skill_directory>")
|
|
106
|
-
sys.exit(1)
|
|
107
|
-
|
|
108
|
-
valid, message = validate_skill(sys.argv[1])
|
|
109
|
-
print(message)
|
|
110
|
-
sys.exit(0 if valid else 1)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick validation script for skills - minimal version
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from encoding_utils import configure_utf8_console, read_text_utf8
|
|
11
|
+
|
|
12
|
+
# Fix Windows console encoding for Unicode output
|
|
13
|
+
configure_utf8_console()
|
|
14
|
+
|
|
15
|
+
def validate_skill(skill_path):
|
|
16
|
+
"""Basic validation of a skill"""
|
|
17
|
+
skill_path = Path(skill_path)
|
|
18
|
+
|
|
19
|
+
# Check SKILL.md exists
|
|
20
|
+
skill_md = skill_path / 'SKILL.md'
|
|
21
|
+
if not skill_md.exists():
|
|
22
|
+
return False, "SKILL.md not found"
|
|
23
|
+
|
|
24
|
+
# Read and validate frontmatter
|
|
25
|
+
content = read_text_utf8(skill_md)
|
|
26
|
+
if not content.startswith('---'):
|
|
27
|
+
return False, "No YAML frontmatter found"
|
|
28
|
+
|
|
29
|
+
# Extract frontmatter
|
|
30
|
+
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
31
|
+
if not match:
|
|
32
|
+
return False, "Invalid frontmatter format"
|
|
33
|
+
|
|
34
|
+
frontmatter = match.group(1)
|
|
35
|
+
|
|
36
|
+
# Check required fields
|
|
37
|
+
if 'name:' not in frontmatter:
|
|
38
|
+
return False, "Missing 'name' in frontmatter"
|
|
39
|
+
if 'description:' not in frontmatter:
|
|
40
|
+
return False, "Missing 'description' in frontmatter"
|
|
41
|
+
|
|
42
|
+
# Extract name for validation
|
|
43
|
+
name_match = re.search(r'name:\s*(.+)', frontmatter)
|
|
44
|
+
if name_match:
|
|
45
|
+
name = name_match.group(1).strip().strip('"').strip("'")
|
|
46
|
+
|
|
47
|
+
# Support namespaced identifiers: ck:skill-name (single namespace segment)
|
|
48
|
+
if name.count(':') > 1:
|
|
49
|
+
return False, (
|
|
50
|
+
f"Name '{name}' is invalid. Use either 'skill-name' or "
|
|
51
|
+
"'namespace:skill-name' with a single colon."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
namespace = None
|
|
55
|
+
skill_id = name
|
|
56
|
+
if ':' in name:
|
|
57
|
+
namespace, skill_id = name.split(':', 1)
|
|
58
|
+
|
|
59
|
+
id_pattern = r'^[a-z0-9-]+$'
|
|
60
|
+
if namespace and not re.match(id_pattern, namespace):
|
|
61
|
+
return False, (
|
|
62
|
+
f"Namespace '{namespace}' must be lowercase letters, digits, and hyphens only"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not re.match(id_pattern, skill_id):
|
|
66
|
+
return False, (
|
|
67
|
+
f"Skill id '{skill_id}' must be lowercase letters, digits, and hyphens only"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for segment_name, segment in [("namespace", namespace), ("skill id", skill_id)]:
|
|
71
|
+
if segment and (segment.startswith('-') or segment.endswith('-') or '--' in segment):
|
|
72
|
+
return False, (
|
|
73
|
+
f"{segment_name.capitalize()} '{segment}' cannot start/end with hyphen "
|
|
74
|
+
"or contain consecutive hyphens"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Validate name length (official max: 64 chars)
|
|
78
|
+
if name_match:
|
|
79
|
+
if len(skill_id) > 64:
|
|
80
|
+
return False, f"Skill id '{skill_id}' exceeds 64 characters ({len(skill_id)})"
|
|
81
|
+
if namespace and len(namespace) > 64:
|
|
82
|
+
return False, f"Namespace '{namespace}' exceeds 64 characters ({len(namespace)})"
|
|
83
|
+
|
|
84
|
+
# Extract and validate description
|
|
85
|
+
desc_match = re.search(r'description:\s*(.+)', frontmatter)
|
|
86
|
+
if desc_match:
|
|
87
|
+
description = desc_match.group(1).strip().strip('"').strip("'")
|
|
88
|
+
|
|
89
|
+
# YAML block scalar indicators are valid (e.g. description: >-)
|
|
90
|
+
if description in {'>', '>-', '|', '|-'}:
|
|
91
|
+
description = ''
|
|
92
|
+
|
|
93
|
+
# Check for angle brackets
|
|
94
|
+
if '<' in description or '>' in description:
|
|
95
|
+
return False, "Description cannot contain angle brackets (< or >)"
|
|
96
|
+
|
|
97
|
+
# Check description length (official max: 1024 chars)
|
|
98
|
+
if len(description) > 1024:
|
|
99
|
+
return False, f"Description exceeds 1024 characters ({len(description)})"
|
|
100
|
+
|
|
101
|
+
return True, "Skill is valid!"
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
if len(sys.argv) != 2:
|
|
105
|
+
print("Usage: python quick_validate.py <skill_directory>")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
valid, message = validate_skill(sys.argv[1])
|
|
109
|
+
print(message)
|
|
110
|
+
sys.exit(0 if valid else 1)
|