skill-seekers 2.7.3__py3-none-any.whl
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.
- skill_seekers/__init__.py +22 -0
- skill_seekers/cli/__init__.py +39 -0
- skill_seekers/cli/adaptors/__init__.py +120 -0
- skill_seekers/cli/adaptors/base.py +221 -0
- skill_seekers/cli/adaptors/claude.py +485 -0
- skill_seekers/cli/adaptors/gemini.py +453 -0
- skill_seekers/cli/adaptors/markdown.py +269 -0
- skill_seekers/cli/adaptors/openai.py +503 -0
- skill_seekers/cli/ai_enhancer.py +310 -0
- skill_seekers/cli/api_reference_builder.py +373 -0
- skill_seekers/cli/architectural_pattern_detector.py +525 -0
- skill_seekers/cli/code_analyzer.py +1462 -0
- skill_seekers/cli/codebase_scraper.py +1225 -0
- skill_seekers/cli/config_command.py +563 -0
- skill_seekers/cli/config_enhancer.py +431 -0
- skill_seekers/cli/config_extractor.py +871 -0
- skill_seekers/cli/config_manager.py +452 -0
- skill_seekers/cli/config_validator.py +394 -0
- skill_seekers/cli/conflict_detector.py +528 -0
- skill_seekers/cli/constants.py +72 -0
- skill_seekers/cli/dependency_analyzer.py +757 -0
- skill_seekers/cli/doc_scraper.py +2332 -0
- skill_seekers/cli/enhance_skill.py +488 -0
- skill_seekers/cli/enhance_skill_local.py +1096 -0
- skill_seekers/cli/enhance_status.py +194 -0
- skill_seekers/cli/estimate_pages.py +433 -0
- skill_seekers/cli/generate_router.py +1209 -0
- skill_seekers/cli/github_fetcher.py +534 -0
- skill_seekers/cli/github_scraper.py +1466 -0
- skill_seekers/cli/guide_enhancer.py +723 -0
- skill_seekers/cli/how_to_guide_builder.py +1267 -0
- skill_seekers/cli/install_agent.py +461 -0
- skill_seekers/cli/install_skill.py +178 -0
- skill_seekers/cli/language_detector.py +614 -0
- skill_seekers/cli/llms_txt_detector.py +60 -0
- skill_seekers/cli/llms_txt_downloader.py +104 -0
- skill_seekers/cli/llms_txt_parser.py +150 -0
- skill_seekers/cli/main.py +558 -0
- skill_seekers/cli/markdown_cleaner.py +132 -0
- skill_seekers/cli/merge_sources.py +806 -0
- skill_seekers/cli/package_multi.py +77 -0
- skill_seekers/cli/package_skill.py +241 -0
- skill_seekers/cli/pattern_recognizer.py +1825 -0
- skill_seekers/cli/pdf_extractor_poc.py +1166 -0
- skill_seekers/cli/pdf_scraper.py +617 -0
- skill_seekers/cli/quality_checker.py +519 -0
- skill_seekers/cli/rate_limit_handler.py +438 -0
- skill_seekers/cli/resume_command.py +160 -0
- skill_seekers/cli/run_tests.py +230 -0
- skill_seekers/cli/setup_wizard.py +93 -0
- skill_seekers/cli/split_config.py +390 -0
- skill_seekers/cli/swift_patterns.py +560 -0
- skill_seekers/cli/test_example_extractor.py +1081 -0
- skill_seekers/cli/test_unified_simple.py +179 -0
- skill_seekers/cli/unified_codebase_analyzer.py +572 -0
- skill_seekers/cli/unified_scraper.py +932 -0
- skill_seekers/cli/unified_skill_builder.py +1605 -0
- skill_seekers/cli/upload_skill.py +162 -0
- skill_seekers/cli/utils.py +432 -0
- skill_seekers/mcp/__init__.py +33 -0
- skill_seekers/mcp/agent_detector.py +316 -0
- skill_seekers/mcp/git_repo.py +273 -0
- skill_seekers/mcp/server.py +231 -0
- skill_seekers/mcp/server_fastmcp.py +1249 -0
- skill_seekers/mcp/server_legacy.py +2302 -0
- skill_seekers/mcp/source_manager.py +285 -0
- skill_seekers/mcp/tools/__init__.py +115 -0
- skill_seekers/mcp/tools/config_tools.py +251 -0
- skill_seekers/mcp/tools/packaging_tools.py +826 -0
- skill_seekers/mcp/tools/scraping_tools.py +842 -0
- skill_seekers/mcp/tools/source_tools.py +828 -0
- skill_seekers/mcp/tools/splitting_tools.py +212 -0
- skill_seekers/py.typed +0 -0
- skill_seekers-2.7.3.dist-info/METADATA +2027 -0
- skill_seekers-2.7.3.dist-info/RECORD +79 -0
- skill_seekers-2.7.3.dist-info/WHEEL +5 -0
- skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
- skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
- skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quality Checker for Claude Skills
|
|
4
|
+
Validates skill quality, checks links, and generates quality reports.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 quality_checker.py output/react/
|
|
8
|
+
python3 quality_checker.py output/godot/ --verbose
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class QualityIssue:
|
|
19
|
+
"""Represents a quality issue found during validation."""
|
|
20
|
+
|
|
21
|
+
level: str # 'error', 'warning', 'info'
|
|
22
|
+
category: str # 'enhancement', 'content', 'links', 'structure'
|
|
23
|
+
message: str
|
|
24
|
+
file: str | None = None
|
|
25
|
+
line: int | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class QualityReport:
|
|
30
|
+
"""Complete quality report for a skill."""
|
|
31
|
+
|
|
32
|
+
skill_name: str
|
|
33
|
+
skill_path: Path
|
|
34
|
+
errors: list[QualityIssue] = field(default_factory=list)
|
|
35
|
+
warnings: list[QualityIssue] = field(default_factory=list)
|
|
36
|
+
info: list[QualityIssue] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
def add_error(self, category: str, message: str, file: str = None, line: int = None):
|
|
39
|
+
"""Add an error to the report."""
|
|
40
|
+
self.errors.append(QualityIssue("error", category, message, file, line))
|
|
41
|
+
|
|
42
|
+
def add_warning(self, category: str, message: str, file: str = None, line: int = None):
|
|
43
|
+
"""Add a warning to the report."""
|
|
44
|
+
self.warnings.append(QualityIssue("warning", category, message, file, line))
|
|
45
|
+
|
|
46
|
+
def add_info(self, category: str, message: str, file: str = None, line: int = None):
|
|
47
|
+
"""Add info to the report."""
|
|
48
|
+
self.info.append(QualityIssue("info", category, message, file, line))
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def has_errors(self) -> bool:
|
|
52
|
+
"""Check if there are any errors."""
|
|
53
|
+
return len(self.errors) > 0
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def has_warnings(self) -> bool:
|
|
57
|
+
"""Check if there are any warnings."""
|
|
58
|
+
return len(self.warnings) > 0
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def is_excellent(self) -> bool:
|
|
62
|
+
"""Check if quality is excellent (no errors, no warnings)."""
|
|
63
|
+
return not self.has_errors and not self.has_warnings
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def quality_score(self) -> float:
|
|
67
|
+
"""Calculate quality score (0-100)."""
|
|
68
|
+
# Start with perfect score
|
|
69
|
+
score = 100.0
|
|
70
|
+
|
|
71
|
+
# Deduct points for issues
|
|
72
|
+
score -= len(self.errors) * 15 # -15 per error
|
|
73
|
+
score -= len(self.warnings) * 5 # -5 per warning
|
|
74
|
+
|
|
75
|
+
# Never go below 0
|
|
76
|
+
return max(0.0, score)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def quality_grade(self) -> str:
|
|
80
|
+
"""Get quality grade (A-F)."""
|
|
81
|
+
score = self.quality_score
|
|
82
|
+
if score >= 90:
|
|
83
|
+
return "A"
|
|
84
|
+
elif score >= 80:
|
|
85
|
+
return "B"
|
|
86
|
+
elif score >= 70:
|
|
87
|
+
return "C"
|
|
88
|
+
elif score >= 60:
|
|
89
|
+
return "D"
|
|
90
|
+
else:
|
|
91
|
+
return "F"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SkillQualityChecker:
|
|
95
|
+
"""Validates skill quality and generates reports."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, skill_dir: Path):
|
|
98
|
+
"""Initialize quality checker.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
skill_dir: Path to skill directory
|
|
102
|
+
"""
|
|
103
|
+
self.skill_dir = Path(skill_dir)
|
|
104
|
+
self.skill_md_path = self.skill_dir / "SKILL.md"
|
|
105
|
+
self.references_dir = self.skill_dir / "references"
|
|
106
|
+
self.report = QualityReport(skill_name=self.skill_dir.name, skill_path=self.skill_dir)
|
|
107
|
+
|
|
108
|
+
def check_all(self) -> QualityReport:
|
|
109
|
+
"""Run all quality checks and return report.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
QualityReport: Complete quality report
|
|
113
|
+
"""
|
|
114
|
+
# Basic structure checks
|
|
115
|
+
self._check_skill_structure()
|
|
116
|
+
|
|
117
|
+
# Enhancement verification
|
|
118
|
+
self._check_enhancement_quality()
|
|
119
|
+
|
|
120
|
+
# Content quality checks
|
|
121
|
+
self._check_content_quality()
|
|
122
|
+
|
|
123
|
+
# Link validation
|
|
124
|
+
self._check_links()
|
|
125
|
+
|
|
126
|
+
# Completeness checks
|
|
127
|
+
self._check_skill_completeness()
|
|
128
|
+
|
|
129
|
+
return self.report
|
|
130
|
+
|
|
131
|
+
def _check_skill_structure(self):
|
|
132
|
+
"""Check basic skill structure."""
|
|
133
|
+
# Check SKILL.md exists
|
|
134
|
+
if not self.skill_md_path.exists():
|
|
135
|
+
self.report.add_error("structure", "SKILL.md file not found", str(self.skill_md_path))
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Check references directory exists
|
|
139
|
+
if not self.references_dir.exists():
|
|
140
|
+
self.report.add_warning(
|
|
141
|
+
"structure",
|
|
142
|
+
"references/ directory not found - skill may be incomplete",
|
|
143
|
+
str(self.references_dir),
|
|
144
|
+
)
|
|
145
|
+
elif not list(self.references_dir.rglob("*.md")):
|
|
146
|
+
self.report.add_warning(
|
|
147
|
+
"structure",
|
|
148
|
+
"references/ directory is empty - no reference documentation found",
|
|
149
|
+
str(self.references_dir),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def _check_enhancement_quality(self):
|
|
153
|
+
"""Check if SKILL.md was properly enhanced."""
|
|
154
|
+
if not self.skill_md_path.exists():
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
content = self.skill_md_path.read_text(encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
# Check for template indicators (signs it wasn't enhanced)
|
|
160
|
+
template_indicators = [
|
|
161
|
+
"TODO:",
|
|
162
|
+
"[Add description]",
|
|
163
|
+
"[Framework specific tips]",
|
|
164
|
+
"coming soon",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
for indicator in template_indicators:
|
|
168
|
+
if indicator.lower() in content.lower():
|
|
169
|
+
self.report.add_warning(
|
|
170
|
+
"enhancement",
|
|
171
|
+
f'Found template placeholder: "{indicator}" - SKILL.md may not be enhanced',
|
|
172
|
+
"SKILL.md",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Check for good signs of enhancement
|
|
176
|
+
enhancement_indicators = {
|
|
177
|
+
"code_examples": re.compile(r"```[\w-]+\n", re.MULTILINE),
|
|
178
|
+
"real_examples": re.compile(r"Example:", re.IGNORECASE),
|
|
179
|
+
"sections": re.compile(r"^## .+", re.MULTILINE),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
code_blocks = len(enhancement_indicators["code_examples"].findall(content))
|
|
183
|
+
_real_examples = len(enhancement_indicators["real_examples"].findall(content))
|
|
184
|
+
sections = len(enhancement_indicators["sections"].findall(content))
|
|
185
|
+
|
|
186
|
+
# Quality thresholds
|
|
187
|
+
if code_blocks == 0:
|
|
188
|
+
self.report.add_warning(
|
|
189
|
+
"enhancement", "No code examples found in SKILL.md - consider enhancing", "SKILL.md"
|
|
190
|
+
)
|
|
191
|
+
elif code_blocks < 3:
|
|
192
|
+
self.report.add_info(
|
|
193
|
+
"enhancement",
|
|
194
|
+
f"Only {code_blocks} code examples found - more examples would improve quality",
|
|
195
|
+
"SKILL.md",
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
self.report.add_info("enhancement", f"✓ Found {code_blocks} code examples", "SKILL.md")
|
|
199
|
+
|
|
200
|
+
if sections < 4:
|
|
201
|
+
self.report.add_warning(
|
|
202
|
+
"enhancement",
|
|
203
|
+
f"Only {sections} sections found - SKILL.md may be too basic",
|
|
204
|
+
"SKILL.md",
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
self.report.add_info("enhancement", f"✓ Found {sections} sections", "SKILL.md")
|
|
208
|
+
|
|
209
|
+
def _check_content_quality(self):
|
|
210
|
+
"""Check content quality."""
|
|
211
|
+
if not self.skill_md_path.exists():
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
content = self.skill_md_path.read_text(encoding="utf-8")
|
|
215
|
+
|
|
216
|
+
# Check YAML frontmatter
|
|
217
|
+
if not content.startswith("---"):
|
|
218
|
+
self.report.add_error(
|
|
219
|
+
"content", "Missing YAML frontmatter - SKILL.md must start with ---", "SKILL.md", 1
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
# Extract frontmatter
|
|
223
|
+
try:
|
|
224
|
+
frontmatter_match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
225
|
+
if frontmatter_match:
|
|
226
|
+
frontmatter = frontmatter_match.group(1)
|
|
227
|
+
|
|
228
|
+
# Check for required fields
|
|
229
|
+
if "name:" not in frontmatter:
|
|
230
|
+
self.report.add_error(
|
|
231
|
+
"content", 'Missing "name:" field in YAML frontmatter', "SKILL.md", 2
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Check for description
|
|
235
|
+
if "description:" in frontmatter:
|
|
236
|
+
self.report.add_info(
|
|
237
|
+
"content", "✓ YAML frontmatter includes description", "SKILL.md"
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
self.report.add_error(
|
|
241
|
+
"content", "Invalid YAML frontmatter format", "SKILL.md", 1
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
self.report.add_error(
|
|
245
|
+
"content", f"Error parsing YAML frontmatter: {e}", "SKILL.md", 1
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Check code block language tags
|
|
249
|
+
code_blocks_without_lang = re.findall(r"```\n[^`]", content)
|
|
250
|
+
if code_blocks_without_lang:
|
|
251
|
+
self.report.add_warning(
|
|
252
|
+
"content",
|
|
253
|
+
f"Found {len(code_blocks_without_lang)} code blocks without language tags",
|
|
254
|
+
"SKILL.md",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Check for "When to Use" section
|
|
258
|
+
if "when to use" not in content.lower():
|
|
259
|
+
self.report.add_warning(
|
|
260
|
+
"content", 'Missing "When to Use This Skill" section', "SKILL.md"
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
self.report.add_info("content", '✓ Found "When to Use" section', "SKILL.md")
|
|
264
|
+
|
|
265
|
+
# Check reference files
|
|
266
|
+
if self.references_dir.exists():
|
|
267
|
+
ref_files = list(self.references_dir.rglob("*.md"))
|
|
268
|
+
if ref_files:
|
|
269
|
+
self.report.add_info(
|
|
270
|
+
"content", f"✓ Found {len(ref_files)} reference files", "references/"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Check if references are mentioned in SKILL.md
|
|
274
|
+
mentioned_refs = 0
|
|
275
|
+
for ref_file in ref_files:
|
|
276
|
+
if ref_file.name in content:
|
|
277
|
+
mentioned_refs += 1
|
|
278
|
+
|
|
279
|
+
if mentioned_refs == 0:
|
|
280
|
+
self.report.add_warning(
|
|
281
|
+
"content",
|
|
282
|
+
"Reference files exist but none are mentioned in SKILL.md",
|
|
283
|
+
"SKILL.md",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def _check_links(self):
|
|
287
|
+
"""Check internal markdown links."""
|
|
288
|
+
if not self.skill_md_path.exists():
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
content = self.skill_md_path.read_text(encoding="utf-8")
|
|
292
|
+
|
|
293
|
+
# Find all markdown links [text](path)
|
|
294
|
+
link_pattern = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
295
|
+
links = link_pattern.findall(content)
|
|
296
|
+
|
|
297
|
+
broken_links = []
|
|
298
|
+
|
|
299
|
+
for text, link in links:
|
|
300
|
+
# Skip external links (http/https)
|
|
301
|
+
if link.startswith("http://") or link.startswith("https://"):
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
# Skip anchor links
|
|
305
|
+
if link.startswith("#"):
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
# Check if file exists (relative to SKILL.md)
|
|
309
|
+
link_path = self.skill_dir / link
|
|
310
|
+
if not link_path.exists():
|
|
311
|
+
broken_links.append((text, link))
|
|
312
|
+
|
|
313
|
+
if broken_links:
|
|
314
|
+
for text, link in broken_links:
|
|
315
|
+
self.report.add_warning("links", f"Broken link: [{text}]({link})", "SKILL.md")
|
|
316
|
+
else:
|
|
317
|
+
if links:
|
|
318
|
+
internal_links = [link for t, link in links if not link.startswith("http")]
|
|
319
|
+
if internal_links:
|
|
320
|
+
self.report.add_info(
|
|
321
|
+
"links", f"✓ All {len(internal_links)} internal links are valid", "SKILL.md"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def _check_skill_completeness(self):
|
|
325
|
+
"""Check skill completeness based on best practices.
|
|
326
|
+
|
|
327
|
+
Validates that skills include verification/prerequisites sections,
|
|
328
|
+
error handling guidance, and clear workflow steps.
|
|
329
|
+
"""
|
|
330
|
+
if not self.skill_md_path.exists():
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
content = self.skill_md_path.read_text(encoding="utf-8")
|
|
334
|
+
|
|
335
|
+
# Check for grounding/verification section (prerequisites)
|
|
336
|
+
grounding_patterns = [
|
|
337
|
+
r"before\s+(executing|running|proceeding|you\s+start)",
|
|
338
|
+
r"verify\s+that",
|
|
339
|
+
r"prerequisites?",
|
|
340
|
+
r"requirements?:",
|
|
341
|
+
r"make\s+sure\s+you\s+have",
|
|
342
|
+
]
|
|
343
|
+
has_grounding = any(
|
|
344
|
+
re.search(pattern, content, re.IGNORECASE) for pattern in grounding_patterns
|
|
345
|
+
)
|
|
346
|
+
if has_grounding:
|
|
347
|
+
self.report.add_info(
|
|
348
|
+
"completeness", "✓ Found verification/prerequisites section", "SKILL.md"
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
self.report.add_info(
|
|
352
|
+
"completeness",
|
|
353
|
+
"Consider adding prerequisites section - helps Claude verify conditions first",
|
|
354
|
+
"SKILL.md",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Check for error handling/troubleshooting guidance
|
|
358
|
+
error_patterns = [
|
|
359
|
+
r"if\s+.*\s+(fails?|errors?)",
|
|
360
|
+
r"troubleshoot",
|
|
361
|
+
r"common\s+(issues?|problems?)",
|
|
362
|
+
r"error\s+handling",
|
|
363
|
+
r"when\s+things\s+go\s+wrong",
|
|
364
|
+
]
|
|
365
|
+
has_error_handling = any(
|
|
366
|
+
re.search(pattern, content, re.IGNORECASE) for pattern in error_patterns
|
|
367
|
+
)
|
|
368
|
+
if has_error_handling:
|
|
369
|
+
self.report.add_info(
|
|
370
|
+
"completeness", "✓ Found error handling/troubleshooting guidance", "SKILL.md"
|
|
371
|
+
)
|
|
372
|
+
else:
|
|
373
|
+
self.report.add_info(
|
|
374
|
+
"completeness",
|
|
375
|
+
"Consider adding troubleshooting section for common issues",
|
|
376
|
+
"SKILL.md",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Check for workflow steps (numbered or sequential indicators)
|
|
380
|
+
step_patterns = [
|
|
381
|
+
r"step\s+\d",
|
|
382
|
+
r"##\s+\d\.",
|
|
383
|
+
r"first,?\s+",
|
|
384
|
+
r"then,?\s+",
|
|
385
|
+
r"finally,?\s+",
|
|
386
|
+
r"next,?\s+",
|
|
387
|
+
]
|
|
388
|
+
steps_found = sum(
|
|
389
|
+
1 for pattern in step_patterns if re.search(pattern, content, re.IGNORECASE)
|
|
390
|
+
)
|
|
391
|
+
if steps_found >= 3:
|
|
392
|
+
self.report.add_info(
|
|
393
|
+
"completeness",
|
|
394
|
+
f"✓ Found clear workflow indicators ({steps_found} step markers)",
|
|
395
|
+
"SKILL.md",
|
|
396
|
+
)
|
|
397
|
+
elif steps_found > 0:
|
|
398
|
+
self.report.add_info(
|
|
399
|
+
"completeness",
|
|
400
|
+
f"Some workflow guidance found ({steps_found} markers) - consider adding numbered steps for clarity",
|
|
401
|
+
"SKILL.md",
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def print_report(report: QualityReport, verbose: bool = False):
|
|
406
|
+
"""Print quality report to console.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
report: Quality report to print
|
|
410
|
+
verbose: Show all info messages
|
|
411
|
+
"""
|
|
412
|
+
print("\n" + "=" * 60)
|
|
413
|
+
print(f"QUALITY REPORT: {report.skill_name}")
|
|
414
|
+
print("=" * 60)
|
|
415
|
+
print()
|
|
416
|
+
|
|
417
|
+
# Quality score
|
|
418
|
+
print(f"Quality Score: {report.quality_score:.1f}/100 (Grade: {report.quality_grade})")
|
|
419
|
+
print()
|
|
420
|
+
|
|
421
|
+
# Errors
|
|
422
|
+
if report.errors:
|
|
423
|
+
print(f"❌ ERRORS ({len(report.errors)}):")
|
|
424
|
+
for issue in report.errors:
|
|
425
|
+
location = (
|
|
426
|
+
f" ({issue.file}:{issue.line})"
|
|
427
|
+
if issue.file and issue.line
|
|
428
|
+
else f" ({issue.file})"
|
|
429
|
+
if issue.file
|
|
430
|
+
else ""
|
|
431
|
+
)
|
|
432
|
+
print(f" [{issue.category}] {issue.message}{location}")
|
|
433
|
+
print()
|
|
434
|
+
|
|
435
|
+
# Warnings
|
|
436
|
+
if report.warnings:
|
|
437
|
+
print(f"⚠️ WARNINGS ({len(report.warnings)}):")
|
|
438
|
+
for issue in report.warnings:
|
|
439
|
+
location = (
|
|
440
|
+
f" ({issue.file}:{issue.line})"
|
|
441
|
+
if issue.file and issue.line
|
|
442
|
+
else f" ({issue.file})"
|
|
443
|
+
if issue.file
|
|
444
|
+
else ""
|
|
445
|
+
)
|
|
446
|
+
print(f" [{issue.category}] {issue.message}{location}")
|
|
447
|
+
print()
|
|
448
|
+
|
|
449
|
+
# Info (only in verbose mode)
|
|
450
|
+
if verbose and report.info:
|
|
451
|
+
print(f"ℹ️ INFO ({len(report.info)}):")
|
|
452
|
+
for issue in report.info:
|
|
453
|
+
location = f" ({issue.file})" if issue.file else ""
|
|
454
|
+
print(f" [{issue.category}] {issue.message}{location}")
|
|
455
|
+
print()
|
|
456
|
+
|
|
457
|
+
# Summary
|
|
458
|
+
if report.is_excellent:
|
|
459
|
+
print("✅ EXCELLENT! No issues found.")
|
|
460
|
+
elif not report.has_errors:
|
|
461
|
+
print("✓ GOOD! No errors, but some warnings to review.")
|
|
462
|
+
else:
|
|
463
|
+
print("❌ NEEDS IMPROVEMENT! Please fix errors before packaging.")
|
|
464
|
+
|
|
465
|
+
print()
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def main():
|
|
469
|
+
"""Main entry point."""
|
|
470
|
+
import argparse
|
|
471
|
+
|
|
472
|
+
parser = argparse.ArgumentParser(
|
|
473
|
+
description="Check skill quality and generate report",
|
|
474
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
475
|
+
epilog="""
|
|
476
|
+
Examples:
|
|
477
|
+
# Basic quality check
|
|
478
|
+
python3 quality_checker.py output/react/
|
|
479
|
+
|
|
480
|
+
# Verbose mode (show all info)
|
|
481
|
+
python3 quality_checker.py output/godot/ --verbose
|
|
482
|
+
|
|
483
|
+
# Exit with error code if issues found
|
|
484
|
+
python3 quality_checker.py output/django/ --strict
|
|
485
|
+
""",
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
parser.add_argument("skill_directory", help="Path to skill directory (e.g., output/react/)")
|
|
489
|
+
|
|
490
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Show all info messages")
|
|
491
|
+
|
|
492
|
+
parser.add_argument(
|
|
493
|
+
"--strict", action="store_true", help="Exit with error code if any warnings or errors found"
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
args = parser.parse_args()
|
|
497
|
+
|
|
498
|
+
# Check if directory exists
|
|
499
|
+
skill_dir = Path(args.skill_directory)
|
|
500
|
+
if not skill_dir.exists():
|
|
501
|
+
print(f"❌ Directory not found: {skill_dir}")
|
|
502
|
+
sys.exit(1)
|
|
503
|
+
|
|
504
|
+
# Run quality checks
|
|
505
|
+
checker = SkillQualityChecker(skill_dir)
|
|
506
|
+
report = checker.check_all()
|
|
507
|
+
|
|
508
|
+
# Print report
|
|
509
|
+
print_report(report, verbose=args.verbose)
|
|
510
|
+
|
|
511
|
+
# Exit code
|
|
512
|
+
if args.strict and (report.has_errors or report.has_warnings) or report.has_errors:
|
|
513
|
+
sys.exit(1)
|
|
514
|
+
else:
|
|
515
|
+
sys.exit(0)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
if __name__ == "__main__":
|
|
519
|
+
main()
|