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.
Files changed (79) hide show
  1. skill_seekers/__init__.py +22 -0
  2. skill_seekers/cli/__init__.py +39 -0
  3. skill_seekers/cli/adaptors/__init__.py +120 -0
  4. skill_seekers/cli/adaptors/base.py +221 -0
  5. skill_seekers/cli/adaptors/claude.py +485 -0
  6. skill_seekers/cli/adaptors/gemini.py +453 -0
  7. skill_seekers/cli/adaptors/markdown.py +269 -0
  8. skill_seekers/cli/adaptors/openai.py +503 -0
  9. skill_seekers/cli/ai_enhancer.py +310 -0
  10. skill_seekers/cli/api_reference_builder.py +373 -0
  11. skill_seekers/cli/architectural_pattern_detector.py +525 -0
  12. skill_seekers/cli/code_analyzer.py +1462 -0
  13. skill_seekers/cli/codebase_scraper.py +1225 -0
  14. skill_seekers/cli/config_command.py +563 -0
  15. skill_seekers/cli/config_enhancer.py +431 -0
  16. skill_seekers/cli/config_extractor.py +871 -0
  17. skill_seekers/cli/config_manager.py +452 -0
  18. skill_seekers/cli/config_validator.py +394 -0
  19. skill_seekers/cli/conflict_detector.py +528 -0
  20. skill_seekers/cli/constants.py +72 -0
  21. skill_seekers/cli/dependency_analyzer.py +757 -0
  22. skill_seekers/cli/doc_scraper.py +2332 -0
  23. skill_seekers/cli/enhance_skill.py +488 -0
  24. skill_seekers/cli/enhance_skill_local.py +1096 -0
  25. skill_seekers/cli/enhance_status.py +194 -0
  26. skill_seekers/cli/estimate_pages.py +433 -0
  27. skill_seekers/cli/generate_router.py +1209 -0
  28. skill_seekers/cli/github_fetcher.py +534 -0
  29. skill_seekers/cli/github_scraper.py +1466 -0
  30. skill_seekers/cli/guide_enhancer.py +723 -0
  31. skill_seekers/cli/how_to_guide_builder.py +1267 -0
  32. skill_seekers/cli/install_agent.py +461 -0
  33. skill_seekers/cli/install_skill.py +178 -0
  34. skill_seekers/cli/language_detector.py +614 -0
  35. skill_seekers/cli/llms_txt_detector.py +60 -0
  36. skill_seekers/cli/llms_txt_downloader.py +104 -0
  37. skill_seekers/cli/llms_txt_parser.py +150 -0
  38. skill_seekers/cli/main.py +558 -0
  39. skill_seekers/cli/markdown_cleaner.py +132 -0
  40. skill_seekers/cli/merge_sources.py +806 -0
  41. skill_seekers/cli/package_multi.py +77 -0
  42. skill_seekers/cli/package_skill.py +241 -0
  43. skill_seekers/cli/pattern_recognizer.py +1825 -0
  44. skill_seekers/cli/pdf_extractor_poc.py +1166 -0
  45. skill_seekers/cli/pdf_scraper.py +617 -0
  46. skill_seekers/cli/quality_checker.py +519 -0
  47. skill_seekers/cli/rate_limit_handler.py +438 -0
  48. skill_seekers/cli/resume_command.py +160 -0
  49. skill_seekers/cli/run_tests.py +230 -0
  50. skill_seekers/cli/setup_wizard.py +93 -0
  51. skill_seekers/cli/split_config.py +390 -0
  52. skill_seekers/cli/swift_patterns.py +560 -0
  53. skill_seekers/cli/test_example_extractor.py +1081 -0
  54. skill_seekers/cli/test_unified_simple.py +179 -0
  55. skill_seekers/cli/unified_codebase_analyzer.py +572 -0
  56. skill_seekers/cli/unified_scraper.py +932 -0
  57. skill_seekers/cli/unified_skill_builder.py +1605 -0
  58. skill_seekers/cli/upload_skill.py +162 -0
  59. skill_seekers/cli/utils.py +432 -0
  60. skill_seekers/mcp/__init__.py +33 -0
  61. skill_seekers/mcp/agent_detector.py +316 -0
  62. skill_seekers/mcp/git_repo.py +273 -0
  63. skill_seekers/mcp/server.py +231 -0
  64. skill_seekers/mcp/server_fastmcp.py +1249 -0
  65. skill_seekers/mcp/server_legacy.py +2302 -0
  66. skill_seekers/mcp/source_manager.py +285 -0
  67. skill_seekers/mcp/tools/__init__.py +115 -0
  68. skill_seekers/mcp/tools/config_tools.py +251 -0
  69. skill_seekers/mcp/tools/packaging_tools.py +826 -0
  70. skill_seekers/mcp/tools/scraping_tools.py +842 -0
  71. skill_seekers/mcp/tools/source_tools.py +828 -0
  72. skill_seekers/mcp/tools/splitting_tools.py +212 -0
  73. skill_seekers/py.typed +0 -0
  74. skill_seekers-2.7.3.dist-info/METADATA +2027 -0
  75. skill_seekers-2.7.3.dist-info/RECORD +79 -0
  76. skill_seekers-2.7.3.dist-info/WHEEL +5 -0
  77. skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
  78. skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
  79. 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()