@rfxlamia/skillkit 1.1.0 → 1.2.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/agents/agents/creative-copywriter.md +212 -0
- package/agents/agents/dario-amodei.md +135 -0
- package/agents/agents/doc-simplifier.md +63 -0
- package/agents/agents/kotlin-pro.md +433 -0
- package/agents/agents/red-team.md +136 -0
- package/agents/agents/sam-altman.md +121 -0
- package/agents/agents/seo-manager.md +184 -0
- package/package.json +1 -1
- package/skills/skillkit-help/SKILL.md +81 -0
- package/skills/skillkit-help/knowledge/application/09-case-studies.md +257 -0
- package/skills/skillkit-help/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skillkit-help/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skillkit-help/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skillkit-help/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skillkit-help/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skillkit-help/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skillkit-help/template/SKILL.md +52 -0
- package/skills/skills/adversarial-review/SKILL.md +219 -0
- package/skills/skills/baby-education/SKILL.md +260 -0
- package/skills/skills/baby-education/references/advanced-techniques.md +323 -0
- package/skills/skills/baby-education/references/transformations.md +345 -0
- package/skills/skills/been-there-done-that/SKILL.md +455 -0
- package/skills/skills/been-there-done-that/references/analysis-patterns.md +162 -0
- package/skills/skills/been-there-done-that/references/git-commands.md +132 -0
- package/skills/skills/been-there-done-that/references/tree-insertion-logic.md +145 -0
- package/skills/skills/coolhunter/SKILL.md +270 -0
- package/skills/skills/coolhunter/assets/elicitation-methods.csv +51 -0
- package/skills/skills/coolhunter/knowledge/elicitation-methods.md +312 -0
- package/skills/skills/coolhunter/references/workflow-execution.md +238 -0
- package/skills/skills/coolhunter/workflow-plan-coolhunter.md +232 -0
- package/skills/skills/creative-copywriting/SKILL.md +324 -0
- package/skills/skills/creative-copywriting/databases/README.md +60 -0
- package/skills/skills/creative-copywriting/databases/carousel-structures.csv +16 -0
- package/skills/skills/creative-copywriting/databases/emotional-arcs.csv +11 -0
- package/skills/skills/creative-copywriting/databases/hook-formulas.csv +51 -0
- package/skills/skills/creative-copywriting/databases/power-words.csv +201 -0
- package/skills/skills/creative-copywriting/databases/psychological-triggers.csv +21 -0
- package/skills/skills/creative-copywriting/databases/read-more-patterns.csv +26 -0
- package/skills/skills/creative-copywriting/databases/swipe-triggers.csv +31 -0
- package/skills/skills/creative-copywriting/references/carousel-psychology.md +223 -0
- package/skills/skills/creative-copywriting/references/hook-anatomy.md +169 -0
- package/skills/skills/creative-copywriting/references/power-word-science.md +134 -0
- package/skills/skills/creative-copywriting/references/storytelling-frameworks.md +157 -0
- package/skills/skills/diverse-content-gen/SKILL.md +201 -0
- package/skills/skills/diverse-content-gen/references/advanced-techniques.md +320 -0
- package/skills/skills/diverse-content-gen/references/research-findings.md +379 -0
- package/skills/skills/diverse-content-gen/references/task-workflows.md +241 -0
- package/skills/skills/diverse-content-gen/references/tool-integration.md +419 -0
- package/skills/skills/diverse-content-gen/references/troubleshooting.md +426 -0
- package/skills/skills/diverse-content-gen/references/vs-core-technique.md +240 -0
- package/skills/skills/framework-critical-thinking/SKILL.md +220 -0
- package/skills/skills/framework-critical-thinking/references/bias_detector.md +375 -0
- package/skills/skills/framework-critical-thinking/references/fallback_handler.md +239 -0
- package/skills/skills/framework-critical-thinking/references/memory_curator.md +161 -0
- package/skills/skills/framework-critical-thinking/references/metacognitive_monitor.md +297 -0
- package/skills/skills/framework-critical-thinking/references/producer_critic_orchestrator.md +333 -0
- package/skills/skills/framework-critical-thinking/references/reasoning_router.md +235 -0
- package/skills/skills/framework-critical-thinking/references/reasoning_validator.md +97 -0
- package/skills/skills/framework-critical-thinking/references/reflection_trigger.md +78 -0
- package/skills/skills/framework-critical-thinking/references/self_verification.md +388 -0
- package/skills/skills/framework-critical-thinking/references/uncertainty_quantifier.md +207 -0
- package/skills/skills/framework-initiative/SKILL.md +231 -0
- package/skills/skills/framework-initiative/references/examples.md +150 -0
- package/skills/skills/framework-initiative/references/impact-analysis.md +157 -0
- package/skills/skills/framework-initiative/references/intent-patterns.md +145 -0
- package/skills/skills/framework-initiative/references/star-framework.md +165 -0
- package/skills/skills/humanize-docs/SKILL.md +203 -0
- package/skills/skills/humanize-docs/references/advanced-techniques.md +13 -0
- package/skills/skills/humanize-docs/references/core-transformations.md +368 -0
- package/skills/skills/humanize-docs/references/detection-patterns.md +400 -0
- package/skills/skills/humanize-docs/references/examples-gallery.md +374 -0
- package/skills/skills/imagine/SKILL.md +190 -0
- package/skills/skills/imagine/references/artstyle-corporate-memphis.md +625 -0
- package/skills/skills/imagine/references/artstyle-crewdson-hyperrealism.md +295 -0
- package/skills/skills/imagine/references/artstyle-iphone-social-media.md +426 -0
- package/skills/skills/imagine/references/artstyle-sciencesaru.md +276 -0
- package/skills/skills/pre-deploy-checklist/README.md +26 -0
- package/skills/skills/pre-deploy-checklist/SKILL.md +153 -0
- package/skills/skills/pre-deploy-checklist/references/checklist-categories.md +174 -0
- package/skills/skills/pre-deploy-checklist/references/domain-prompts.md +216 -0
- package/skills/skills/prompt-engineering/SKILL.md +209 -0
- package/skills/skills/prompt-engineering/references/advanced-combinations.md +444 -0
- package/skills/skills/prompt-engineering/references/chain-of-thought.md +140 -0
- package/skills/skills/prompt-engineering/references/decision_matrix.md +220 -0
- package/skills/skills/prompt-engineering/references/few-shot.md +346 -0
- package/skills/skills/prompt-engineering/references/json-format.md +270 -0
- package/skills/skills/prompt-engineering/references/natural-language.md +420 -0
- package/skills/skills/prompt-engineering/references/pitfalls.md +365 -0
- package/skills/skills/prompt-engineering/references/prompt-chaining.md +498 -0
- package/skills/skills/prompt-engineering/references/react.md +108 -0
- package/skills/skills/prompt-engineering/references/self-consistency.md +322 -0
- package/skills/skills/prompt-engineering/references/tree-of-thoughts.md +386 -0
- package/skills/skills/prompt-engineering/references/xml-format.md +220 -0
- package/skills/skills/prompt-engineering/references/yaml-format.md +488 -0
- package/skills/skills/prompt-engineering/references/zero-shot.md +74 -0
- package/skills/skills/quick-spec/SKILL.md +280 -0
- package/skills/skills/quick-spec/assets/tech-spec-template.md +74 -0
- package/skills/skills/quick-spec/references/step-01-understand.md +189 -0
- package/skills/skills/quick-spec/references/step-02-investigate.md +144 -0
- package/skills/skills/quick-spec/references/step-03-generate.md +128 -0
- package/skills/skills/quick-spec/references/step-04-review.md +173 -0
- package/skills/skills/quick-spec/tests/__pycache__/test_skill.cpython-314-pytest-9.0.2.pyc +0 -0
- package/skills/skills/quick-spec/tests/test_scenarios.md +83 -0
- package/skills/skills/quick-spec/tests/test_skill.py +136 -0
- package/skills/skills/readme-expert/SKILL.md +538 -0
- package/skills/skills/readme-expert/knowledge/INDEX.md +192 -0
- package/skills/skills/readme-expert/knowledge/application/quality-standards.md +470 -0
- package/skills/skills/readme-expert/knowledge/application/script-executor.md +604 -0
- package/skills/skills/readme-expert/knowledge/application/template-library.md +822 -0
- package/skills/skills/readme-expert/knowledge/foundation/codebase-scanner.md +361 -0
- package/skills/skills/readme-expert/knowledge/foundation/validation-checklist.md +481 -0
- package/skills/skills/red-teaming/SKILL.md +321 -0
- package/skills/skills/red-teaming/references/ai-llm-redteam.md +517 -0
- package/skills/skills/red-teaming/references/attack-techniques.md +410 -0
- package/skills/skills/red-teaming/references/cybersecurity-redteam.md +383 -0
- package/skills/skills/red-teaming/references/tools-frameworks.md +446 -0
- package/skills/skills/releasing/.skillkit-mode +1 -0
- package/skills/skills/releasing/SKILL.md +225 -0
- package/skills/skills/releasing/references/version-detection.md +108 -0
- package/skills/skills/screenwriter/SKILL.md +273 -0
- package/skills/skills/screenwriter/references/advanced-techniques.md +216 -0
- package/skills/skills/screenwriter/references/pipeline-integration.md +266 -0
- package/skills/skills/skillkit/.claude/settings.local.json +7 -0
- package/skills/skills/skillkit/.claude-plugin/plugin.json +27 -0
- package/skills/skills/skillkit/CHANGELOG.md +484 -0
- package/skills/skills/skillkit/SKILL.md +511 -0
- package/skills/skills/skillkit/commands/skillkit.md +6 -0
- package/skills/skills/skillkit/commands/validate-plan.md +6 -0
- package/skills/skills/skillkit/commands/verify.md +6 -0
- package/skills/skills/skillkit/knowledge/INDEX.md +352 -0
- package/skills/skills/skillkit/knowledge/application/09-case-studies.md +257 -0
- package/skills/skills/skillkit/knowledge/application/10-technical-architecture.md +324 -0
- package/skills/skills/skillkit/knowledge/application/11-adoption-strategy.md +267 -0
- package/skills/skills/skillkit/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skills/skillkit/knowledge/application/13-competitive-landscape.md +198 -0
- package/skills/skills/skillkit/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skills/skillkit/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skills/skillkit/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skills/skillkit/knowledge/foundation/04-hybrid-patterns.md +308 -0
- package/skills/skills/skillkit/knowledge/foundation/05-token-economics.md +275 -0
- package/skills/skills/skillkit/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skills/skillkit/knowledge/foundation/07-security-concerns.md +322 -0
- package/skills/skills/skillkit/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skills/skillkit/knowledge/plugin-guide.md +614 -0
- package/skills/skills/skillkit/knowledge/tools/14-validation-tools-guide.md +150 -0
- package/skills/skills/skillkit/knowledge/tools/15-cost-tools-guide.md +157 -0
- package/skills/skills/skillkit/knowledge/tools/16-security-tools-guide.md +122 -0
- package/skills/skills/skillkit/knowledge/tools/17-pattern-tools-guide.md +161 -0
- package/skills/skills/skillkit/knowledge/tools/18-decision-helper-guide.md +243 -0
- package/skills/skills/skillkit/knowledge/tools/19-test-generator-guide.md +275 -0
- package/skills/skills/skillkit/knowledge/tools/20-split-skill-guide.md +149 -0
- package/skills/skills/skillkit/knowledge/tools/21-quality-scorer-guide.md +226 -0
- package/skills/skills/skillkit/knowledge/tools/22-migration-helper-guide.md +356 -0
- package/skills/skills/skillkit/knowledge/tools/23-subagent-creation-guide.md +448 -0
- package/skills/skills/skillkit/knowledge/tools/24-behavioral-testing-guide.md +122 -0
- package/skills/skills/skillkit/references/proposal-generation.md +982 -0
- package/skills/skills/skillkit/references/rationalization-catalog.md +75 -0
- package/skills/skills/skillkit/references/research-methodology.md +661 -0
- package/skills/skills/skillkit/references/section-2-full-creation-workflow.md +452 -0
- package/skills/skills/skillkit/references/section-3-validation-workflow-existing-skill.md +63 -0
- package/skills/skills/skillkit/references/section-4-decision-workflow-skills-vs-subagents.md +64 -0
- package/skills/skills/skillkit/references/section-5-migration-workflow-doc-to-skill.md +58 -0
- package/skills/skills/skillkit/references/section-6-subagent-creation-workflow.md +499 -0
- package/skills/skills/skillkit/references/section-7-knowledge-reference-map.md +72 -0
- package/skills/skills/skillkit/scripts/__pycache__/decision_helper.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/quick_validate.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/test_generator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/skills/skills/skillkit/scripts/decision_helper.py +799 -0
- package/skills/skills/skillkit/scripts/init_skill.py +400 -0
- package/skills/skills/skillkit/scripts/init_subagent.py +231 -0
- package/skills/skills/skillkit/scripts/migration_helper.py +669 -0
- package/skills/skills/skillkit/scripts/package_skill.py +211 -0
- package/skills/skills/skillkit/scripts/pattern_detector.py +381 -0
- package/skills/skills/skillkit/scripts/pattern_detector_new.py +382 -0
- package/skills/skills/skillkit/scripts/pressure_tester.py +157 -0
- package/skills/skills/skillkit/scripts/quality_scorer.py +999 -0
- package/skills/skills/skillkit/scripts/quick_validate.py +100 -0
- package/skills/skills/skillkit/scripts/security_scanner.py +474 -0
- package/skills/skills/skillkit/scripts/split_skill.py +540 -0
- package/skills/skills/skillkit/scripts/test_generator.py +695 -0
- package/skills/skills/skillkit/scripts/token_estimator.py +493 -0
- package/skills/skills/skillkit/scripts/utils/__init__.py +49 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/budget_tracker.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/budget_tracker.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/output_formatter.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/output_formatter.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/reference_validator.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/reference_validator.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/budget_tracker.py +388 -0
- package/skills/skills/skillkit/scripts/utils/output_formatter.py +263 -0
- package/skills/skills/skillkit/scripts/utils/reference_validator.py +401 -0
- package/skills/skills/skillkit/scripts/validate_skill.py +594 -0
- package/skills/skills/skillkit/tests/test_behavioral.py +39 -0
- package/skills/skills/skillkit/tests/test_scenarios.md +83 -0
- package/skills/skills/skillkit/tests/test_skill.py +136 -0
- package/skills/skills/skillkit-help/SKILL.md +81 -0
- package/skills/skills/skillkit-help/knowledge/application/09-case-studies.md +257 -0
- package/skills/skills/skillkit-help/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skills/skillkit-help/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skills/skillkit-help/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skills/skillkit-help/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skills/skillkit-help/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skills/skillkit-help/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skills/skillkit-help/template/SKILL.md +52 -0
- package/skills/skills/social-media-seo/SKILL.md +278 -0
- package/skills/skills/social-media-seo/databases/caption-styles.csv +31 -0
- package/skills/skills/social-media-seo/databases/engagement-tactics.csv +16 -0
- package/skills/skills/social-media-seo/databases/hashtag-strategies.csv +21 -0
- package/skills/skills/social-media-seo/databases/hook-formulas.csv +26 -0
- package/skills/skills/social-media-seo/databases/keyword-clusters.csv +11 -0
- package/skills/skills/social-media-seo/databases/thread-structures.csv +26 -0
- package/skills/skills/social-media-seo/databases/viral-patterns.csv +21 -0
- package/skills/skills/social-media-seo/references/analytics-guide.md +321 -0
- package/skills/skills/social-media-seo/references/instagram-seo.md +235 -0
- package/skills/skills/social-media-seo/references/threads-seo.md +305 -0
- package/skills/skills/social-media-seo/references/x-twitter-seo.md +337 -0
- package/skills/skills/social-media-seo/scripts/query_database.py +191 -0
- package/skills/skills/storyteller/SKILL.md +241 -0
- package/skills/skills/storyteller/references/transformation-methodology.md +293 -0
- package/skills/skills/storyteller/references/visual-vocabulary.md +177 -0
- package/skills/skills/thread-pro/SKILL.md +162 -0
- package/skills/skills/thread-pro/anti-ai-patterns.md +120 -0
- package/skills/skills/thread-pro/hook-formulas.md +138 -0
- package/skills/skills/thread-pro/references/anti-ai-patterns.md +120 -0
- package/skills/skills/thread-pro/references/hook-formulas.md +138 -0
- package/skills/skills/thread-pro/references/thread-structures.md +240 -0
- package/skills/skills/thread-pro/references/voice-injection.md +130 -0
- package/skills/skills/thread-pro/thread-structures.md +240 -0
- package/skills/skills/thread-pro/voice-injection.md +130 -0
- package/skills/skills/tinkering/SKILL.md +251 -0
- package/skills/skills/tinkering/references/graduation-checklist.md +100 -0
- package/skills/skills/validate-plan/.skillkit-mode +1 -0
- package/skills/skills/validate-plan/SKILL.md +406 -0
- package/skills/skills/validate-plan/references/dry-principles.md +251 -0
- package/skills/skills/validate-plan/references/gap-analysis-guide.md +320 -0
- package/skills/skills/validate-plan/references/tdd-patterns.md +413 -0
- package/skills/skills/validate-plan/references/yagni-checklist.md +330 -0
- package/skills/skills/verify-before-ship/.skillkit-mode +1 -0
- package/skills/skills/verify-before-ship/SKILL.md +116 -0
- package/skills/skills/verify-before-ship/references/anti-rationalization.md +212 -0
- package/skills/skills/verify-before-ship/references/verification-gates.md +305 -0
- package/skills-manifest.json +8 -2
- package/src/picker.js +11 -5
- package/src/picker.test.js +36 -1
|
@@ -0,0 +1,999 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Score skill quality against best practices.
|
|
4
|
+
Based on comprehensive checklist from Files 01-13.
|
|
5
|
+
|
|
6
|
+
AGENT-LAYER TOOL: Called by Claude via bash_tool, outputs JSON for parsing.
|
|
7
|
+
|
|
8
|
+
This script evaluates skills across 5 categories:
|
|
9
|
+
- Structure (20 pts): YAML, organization, progressive disclosure
|
|
10
|
+
- Content (30 pts): Description, triggers, writing style, examples
|
|
11
|
+
- Efficiency (20 pts): Line count, token estimate, bloat detection
|
|
12
|
+
- Security (15 pts): No secrets, safe patterns, input validation
|
|
13
|
+
- Style (15 pts): Imperative form, conciseness, clear headers
|
|
14
|
+
|
|
15
|
+
Total: 100 points scoring system
|
|
16
|
+
|
|
17
|
+
References:
|
|
18
|
+
File 01: Why Skills Exist
|
|
19
|
+
File 02: Skills vs Subagents comparison
|
|
20
|
+
File 05: Token economics
|
|
21
|
+
File 07: Security concerns
|
|
22
|
+
File 10: Technical architecture
|
|
23
|
+
Files 01-13: Comprehensive best practices
|
|
24
|
+
|
|
25
|
+
Usage (Agent-Layer):
|
|
26
|
+
python quality_scorer.py /path/to/skill --format json
|
|
27
|
+
# Returns JSON: {"status": "success", "overall": {...}, "categories": {...}}
|
|
28
|
+
|
|
29
|
+
Usage (Human-Readable):
|
|
30
|
+
python quality_scorer.py /path/to/skill
|
|
31
|
+
python quality_scorer.py /path/to/skill --detailed
|
|
32
|
+
python quality_scorer.py /path/to/skill --export report.json
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import argparse
|
|
36
|
+
import json
|
|
37
|
+
import re
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Dict, List, Optional
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class QualityScorer:
|
|
43
|
+
"""Score skill against best practices."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, skill_path: Optional[str] = None, detailed: bool = False):
|
|
46
|
+
"""
|
|
47
|
+
Initialize quality scorer.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
skill_path: Path to skill directory
|
|
51
|
+
detailed: If True, show detailed category breakdown
|
|
52
|
+
|
|
53
|
+
References: Files 01-13 (all best practices)
|
|
54
|
+
"""
|
|
55
|
+
self.skill_path = Path(skill_path) if skill_path is not None else None
|
|
56
|
+
self.detailed = detailed
|
|
57
|
+
self.scores = {}
|
|
58
|
+
self.recommendations = []
|
|
59
|
+
|
|
60
|
+
# Validate skill path
|
|
61
|
+
if self.skill_path is None:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if not self.skill_path.exists():
|
|
65
|
+
raise FileNotFoundError(f"Skill directory not found: {skill_path}")
|
|
66
|
+
|
|
67
|
+
if not (self.skill_path / "SKILL.md").exists():
|
|
68
|
+
raise FileNotFoundError(f"SKILL.md not found in {skill_path}")
|
|
69
|
+
|
|
70
|
+
def calculate_final_score(
|
|
71
|
+
self,
|
|
72
|
+
structural_score: float,
|
|
73
|
+
behavioral_score: Optional[float] = None,
|
|
74
|
+
) -> Dict:
|
|
75
|
+
"""
|
|
76
|
+
Calculate final quality score with optional behavioral component.
|
|
77
|
+
|
|
78
|
+
Formula:
|
|
79
|
+
- fast mode: 100% structural
|
|
80
|
+
- full mode: 60% structural + 40% behavioral
|
|
81
|
+
"""
|
|
82
|
+
if behavioral_score is None:
|
|
83
|
+
return {
|
|
84
|
+
"final_score": structural_score,
|
|
85
|
+
"structural_score": structural_score,
|
|
86
|
+
"behavioral_score": None,
|
|
87
|
+
"weights": {"structural": 1.0, "behavioral": 0.0},
|
|
88
|
+
"mode": "fast",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
final = (structural_score * 0.6) + (behavioral_score * 0.4)
|
|
92
|
+
return {
|
|
93
|
+
"final_score": round(final, 2),
|
|
94
|
+
"structural_score": structural_score,
|
|
95
|
+
"behavioral_score": behavioral_score,
|
|
96
|
+
"weights": {"structural": 0.6, "behavioral": 0.4},
|
|
97
|
+
"mode": "full",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def run_behavioral_tests(self, skill_path: str, skill_type: str = None) -> float:
|
|
101
|
+
"""
|
|
102
|
+
DEPRECATED in v2.1: Behavioral testing is now done via subagent dispatch.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
NotImplementedError: Always. Use Full Mode Behavioral Testing Protocol instead.
|
|
106
|
+
"""
|
|
107
|
+
raise NotImplementedError(
|
|
108
|
+
"Behavioral scoring via pressure_tester.py is deprecated in v2.1.\n"
|
|
109
|
+
"Use the Full Mode Behavioral Testing Protocol instead:\n"
|
|
110
|
+
"Load: skills/skillkit/references/section-2-full-creation-workflow.md\n"
|
|
111
|
+
"Section: 'Full Mode Behavioral Testing Protocol'"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# ========== SCORING CATEGORIES ==========
|
|
115
|
+
|
|
116
|
+
def score_structure(self) -> Dict:
|
|
117
|
+
"""
|
|
118
|
+
Score skill structure.
|
|
119
|
+
|
|
120
|
+
Criteria (20 points):
|
|
121
|
+
- YAML frontmatter valid (5 pts)
|
|
122
|
+
- File organization correct (5 pts)
|
|
123
|
+
- Progressive disclosure implemented (5 pts)
|
|
124
|
+
- Reference files properly organized (5 pts)
|
|
125
|
+
|
|
126
|
+
References: File 10 (architecture)
|
|
127
|
+
"""
|
|
128
|
+
score = 0
|
|
129
|
+
issues = []
|
|
130
|
+
|
|
131
|
+
# Check YAML frontmatter
|
|
132
|
+
if self._has_valid_yaml():
|
|
133
|
+
score += 5
|
|
134
|
+
else:
|
|
135
|
+
issues.append("YAML frontmatter invalid or missing")
|
|
136
|
+
|
|
137
|
+
# Check file organization
|
|
138
|
+
if self._has_proper_structure():
|
|
139
|
+
score += 5
|
|
140
|
+
else:
|
|
141
|
+
issues.append("File structure not optimal")
|
|
142
|
+
|
|
143
|
+
# Check progressive disclosure
|
|
144
|
+
if self._uses_progressive_disclosure():
|
|
145
|
+
score += 5
|
|
146
|
+
else:
|
|
147
|
+
issues.append("Progressive disclosure not implemented")
|
|
148
|
+
|
|
149
|
+
# Check references
|
|
150
|
+
if self._references_organized():
|
|
151
|
+
score += 5
|
|
152
|
+
else:
|
|
153
|
+
issues.append("References not properly organized")
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
'score': score,
|
|
157
|
+
'max': 20,
|
|
158
|
+
'percentage': (score / 20) * 100,
|
|
159
|
+
'issues': issues
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def score_content(self) -> Dict:
|
|
163
|
+
"""
|
|
164
|
+
Score content quality.
|
|
165
|
+
|
|
166
|
+
Criteria (30 points):
|
|
167
|
+
- Description includes WHAT + WHEN (10 pts)
|
|
168
|
+
- Clear trigger conditions (5 pts)
|
|
169
|
+
- Agent-layer writing style (10 pts)
|
|
170
|
+
- Examples inline and relevant (5 pts)
|
|
171
|
+
|
|
172
|
+
References: File 02 (description best practices)
|
|
173
|
+
"""
|
|
174
|
+
score = 0
|
|
175
|
+
issues = []
|
|
176
|
+
|
|
177
|
+
# Check description quality
|
|
178
|
+
desc_score = self._score_description()
|
|
179
|
+
score += desc_score
|
|
180
|
+
if desc_score < 10:
|
|
181
|
+
issues.append("Description missing WHAT or WHEN")
|
|
182
|
+
|
|
183
|
+
# Check trigger conditions
|
|
184
|
+
if self._has_clear_triggers():
|
|
185
|
+
score += 5
|
|
186
|
+
else:
|
|
187
|
+
issues.append("Trigger conditions unclear")
|
|
188
|
+
|
|
189
|
+
# Check writing style
|
|
190
|
+
style_score = self._score_writing_style()
|
|
191
|
+
score += style_score
|
|
192
|
+
if style_score < 10:
|
|
193
|
+
issues.append("Writing style not agent-optimized")
|
|
194
|
+
|
|
195
|
+
# Check examples
|
|
196
|
+
if self._has_inline_examples():
|
|
197
|
+
score += 5
|
|
198
|
+
else:
|
|
199
|
+
issues.append("Examples missing or in separate section")
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
'score': score,
|
|
203
|
+
'max': 30,
|
|
204
|
+
'percentage': (score / 30) * 100,
|
|
205
|
+
'issues': issues
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
def score_efficiency(self) -> Dict:
|
|
209
|
+
"""
|
|
210
|
+
Score token efficiency.
|
|
211
|
+
|
|
212
|
+
Criteria (20 points):
|
|
213
|
+
- SKILL.md under 500 lines (10 pts)
|
|
214
|
+
- Estimated tokens < 5,000 (5 pts)
|
|
215
|
+
- No bloat detected (5 pts)
|
|
216
|
+
|
|
217
|
+
References: File 05 (token economics)
|
|
218
|
+
"""
|
|
219
|
+
score = 0
|
|
220
|
+
issues = []
|
|
221
|
+
|
|
222
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
223
|
+
with open(skill_md) as f:
|
|
224
|
+
lines = f.readlines()
|
|
225
|
+
line_count = len(lines)
|
|
226
|
+
content = ''.join(lines)
|
|
227
|
+
|
|
228
|
+
# Line count
|
|
229
|
+
if line_count < 500:
|
|
230
|
+
score += 10
|
|
231
|
+
elif line_count < 800:
|
|
232
|
+
score += 5
|
|
233
|
+
issues.append(f"SKILL.md longer than ideal ({line_count} lines)")
|
|
234
|
+
else:
|
|
235
|
+
issues.append(f"SKILL.md too long ({line_count} lines)")
|
|
236
|
+
|
|
237
|
+
# Token estimate (simplified: ~4 chars per token)
|
|
238
|
+
estimated_tokens = len(content) // 4
|
|
239
|
+
if estimated_tokens < 5000:
|
|
240
|
+
score += 5
|
|
241
|
+
else:
|
|
242
|
+
issues.append(f"Estimated tokens high (~{estimated_tokens})")
|
|
243
|
+
|
|
244
|
+
# Bloat detection (check for repetition)
|
|
245
|
+
if not self._detect_bloat(content):
|
|
246
|
+
score += 5
|
|
247
|
+
else:
|
|
248
|
+
issues.append("Content bloat detected")
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
'score': score,
|
|
252
|
+
'max': 20,
|
|
253
|
+
'percentage': (score / 20) * 100,
|
|
254
|
+
'issues': issues
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
def score_security(self) -> Dict:
|
|
258
|
+
"""
|
|
259
|
+
Score security practices.
|
|
260
|
+
|
|
261
|
+
Criteria (15 points):
|
|
262
|
+
- No hardcoded secrets (5 pts)
|
|
263
|
+
- No dangerous patterns (5 pts)
|
|
264
|
+
- Input validation present (5 pts)
|
|
265
|
+
|
|
266
|
+
References: File 07 (security concerns)
|
|
267
|
+
"""
|
|
268
|
+
score = 0
|
|
269
|
+
issues = []
|
|
270
|
+
|
|
271
|
+
# Check for hardcoded secrets
|
|
272
|
+
if not self._has_hardcoded_secrets():
|
|
273
|
+
score += 5
|
|
274
|
+
else:
|
|
275
|
+
issues.append("Hardcoded secrets detected")
|
|
276
|
+
|
|
277
|
+
# Check for dangerous patterns
|
|
278
|
+
if not self._has_dangerous_patterns():
|
|
279
|
+
score += 5
|
|
280
|
+
else:
|
|
281
|
+
issues.append("Dangerous code patterns detected")
|
|
282
|
+
|
|
283
|
+
# Check input validation
|
|
284
|
+
if self._has_input_validation():
|
|
285
|
+
score += 5
|
|
286
|
+
else:
|
|
287
|
+
issues.append("Input validation missing or weak")
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
'score': score,
|
|
291
|
+
'max': 15,
|
|
292
|
+
'percentage': (score / 15) * 100,
|
|
293
|
+
'issues': issues
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
def score_style(self) -> Dict:
|
|
297
|
+
"""
|
|
298
|
+
Score writing style.
|
|
299
|
+
|
|
300
|
+
Criteria (15 points):
|
|
301
|
+
- Imperative form used (5 pts)
|
|
302
|
+
- Concise instructions (5 pts)
|
|
303
|
+
- Clear section headers (5 pts)
|
|
304
|
+
|
|
305
|
+
References: All micro-modules (agent-layer style)
|
|
306
|
+
"""
|
|
307
|
+
score = 0
|
|
308
|
+
issues = []
|
|
309
|
+
|
|
310
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
311
|
+
with open(skill_md) as f:
|
|
312
|
+
content = f.read()
|
|
313
|
+
|
|
314
|
+
# Check imperative form
|
|
315
|
+
imperative_ratio = self._count_imperative_sentences(content)
|
|
316
|
+
if imperative_ratio > 0.5:
|
|
317
|
+
score += 5
|
|
318
|
+
elif imperative_ratio > 0.3:
|
|
319
|
+
score += 3
|
|
320
|
+
issues.append("More imperative voice recommended")
|
|
321
|
+
else:
|
|
322
|
+
issues.append("Not using imperative voice")
|
|
323
|
+
|
|
324
|
+
# Check conciseness (sentence length)
|
|
325
|
+
avg_sentence_length = self._calculate_avg_sentence_length(content)
|
|
326
|
+
if avg_sentence_length < 20:
|
|
327
|
+
score += 5
|
|
328
|
+
elif avg_sentence_length < 30:
|
|
329
|
+
score += 3
|
|
330
|
+
issues.append("Sentences could be more concise")
|
|
331
|
+
else:
|
|
332
|
+
issues.append("Sentences too verbose")
|
|
333
|
+
|
|
334
|
+
# Check headers
|
|
335
|
+
if self._has_clear_headers(content):
|
|
336
|
+
score += 5
|
|
337
|
+
else:
|
|
338
|
+
issues.append("Headers not descriptive enough")
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
'score': score,
|
|
342
|
+
'max': 15,
|
|
343
|
+
'percentage': (score / 15) * 100,
|
|
344
|
+
'issues': issues
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# ========== HELPER METHODS ==========
|
|
348
|
+
|
|
349
|
+
def _has_valid_yaml(self) -> bool:
|
|
350
|
+
"""Check if YAML frontmatter is valid."""
|
|
351
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
352
|
+
with open(skill_md) as f:
|
|
353
|
+
content = f.read()
|
|
354
|
+
|
|
355
|
+
# Check for frontmatter delimiters
|
|
356
|
+
if not content.startswith('---\n'):
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
# Find end of frontmatter
|
|
360
|
+
end_idx = content.find('\n---\n', 4)
|
|
361
|
+
if end_idx == -1:
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
frontmatter = content[4:end_idx]
|
|
365
|
+
|
|
366
|
+
# Check for required fields
|
|
367
|
+
has_name = 'name:' in frontmatter
|
|
368
|
+
has_description = 'description:' in frontmatter
|
|
369
|
+
|
|
370
|
+
return has_name and has_description
|
|
371
|
+
|
|
372
|
+
def _has_proper_structure(self) -> bool:
|
|
373
|
+
"""Check if file structure is proper."""
|
|
374
|
+
# Check for SKILL.md
|
|
375
|
+
has_skill_md = (self.skill_path / "SKILL.md").exists()
|
|
376
|
+
|
|
377
|
+
# Check if references directory exists and is used properly
|
|
378
|
+
refs_dir = self.skill_path / "references"
|
|
379
|
+
if refs_dir.exists():
|
|
380
|
+
# Should have .md files
|
|
381
|
+
has_ref_files = any(refs_dir.glob('*.md'))
|
|
382
|
+
return has_skill_md and has_ref_files
|
|
383
|
+
|
|
384
|
+
return has_skill_md
|
|
385
|
+
|
|
386
|
+
def _uses_progressive_disclosure(self) -> bool:
|
|
387
|
+
"""Check if progressive disclosure is implemented."""
|
|
388
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
389
|
+
with open(skill_md) as f:
|
|
390
|
+
lines = f.readlines()
|
|
391
|
+
|
|
392
|
+
# Check if SKILL.md is reasonably sized
|
|
393
|
+
refs_dir = self.skill_path / "references"
|
|
394
|
+
has_refs = refs_dir.exists() and any(refs_dir.glob('*.md'))
|
|
395
|
+
|
|
396
|
+
# If file is short, progressive disclosure not needed
|
|
397
|
+
# If file is long, should have references
|
|
398
|
+
return len(lines) < 500 or has_refs
|
|
399
|
+
|
|
400
|
+
def _references_organized(self) -> bool:
|
|
401
|
+
"""Check if references are organized."""
|
|
402
|
+
refs_dir = self.skill_path / "references"
|
|
403
|
+
if not refs_dir.exists():
|
|
404
|
+
return True # OK if no references needed
|
|
405
|
+
|
|
406
|
+
# Check for proper filenames (lowercase, no spaces)
|
|
407
|
+
for ref_file in refs_dir.glob('*.md'):
|
|
408
|
+
if not ref_file.stem.replace('-', '').replace('_', '').isalnum():
|
|
409
|
+
return False
|
|
410
|
+
if ' ' in ref_file.stem:
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
def _score_description(self) -> int:
|
|
416
|
+
"""Score description quality (0-10)."""
|
|
417
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
418
|
+
with open(skill_md) as f:
|
|
419
|
+
content = f.read()
|
|
420
|
+
|
|
421
|
+
# Get frontmatter and first 500 chars
|
|
422
|
+
relevant_section = content[:1000].lower()
|
|
423
|
+
|
|
424
|
+
# Check for WHAT (task/functionality description)
|
|
425
|
+
what_keywords = ['comprehensive', 'provides', 'enables', 'supports', 'tools for',
|
|
426
|
+
'guide', 'system', 'framework', 'utility']
|
|
427
|
+
has_what = any(kw in relevant_section for kw in what_keywords)
|
|
428
|
+
|
|
429
|
+
# Check for WHEN (trigger conditions)
|
|
430
|
+
when_keywords = ['when', 'use when', 'trigger', 'for tasks', 'invoke',
|
|
431
|
+
'if', 'needs to', 'requires', 'working with']
|
|
432
|
+
has_when = any(kw in relevant_section for kw in when_keywords)
|
|
433
|
+
|
|
434
|
+
if has_what and has_when:
|
|
435
|
+
return 10
|
|
436
|
+
elif has_what or has_when:
|
|
437
|
+
return 5
|
|
438
|
+
else:
|
|
439
|
+
return 0
|
|
440
|
+
|
|
441
|
+
def _has_clear_triggers(self) -> bool:
|
|
442
|
+
"""Check for clear trigger conditions."""
|
|
443
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
444
|
+
with open(skill_md) as f:
|
|
445
|
+
content = f.read()[:1500] # Check first 1500 chars
|
|
446
|
+
|
|
447
|
+
trigger_keywords = ['use when', 'trigger', 'invoke', 'activate',
|
|
448
|
+
'when claude needs', 'use this skill']
|
|
449
|
+
return any(kw in content.lower() for kw in trigger_keywords)
|
|
450
|
+
|
|
451
|
+
def _score_writing_style(self) -> int:
|
|
452
|
+
"""Score writing style (0-10)."""
|
|
453
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
454
|
+
with open(skill_md) as f:
|
|
455
|
+
content = f.read()
|
|
456
|
+
|
|
457
|
+
score = 0
|
|
458
|
+
|
|
459
|
+
# Check for structured formatting (lists, code blocks, tables)
|
|
460
|
+
has_code_blocks = '```' in content
|
|
461
|
+
has_lists = bool(re.search(r'^[\-\*]\s', content, re.MULTILINE))
|
|
462
|
+
has_tables = '|' in content
|
|
463
|
+
|
|
464
|
+
if has_code_blocks or has_lists:
|
|
465
|
+
score += 3
|
|
466
|
+
if has_tables:
|
|
467
|
+
score += 2
|
|
468
|
+
|
|
469
|
+
# Check for direct, actionable language
|
|
470
|
+
action_verbs = ['use', 'run', 'execute', 'create', 'configure',
|
|
471
|
+
'install', 'check', 'validate', 'ensure']
|
|
472
|
+
verb_count = sum(content.lower().count(verb) for verb in action_verbs)
|
|
473
|
+
if verb_count > 10:
|
|
474
|
+
score += 3
|
|
475
|
+
elif verb_count > 5:
|
|
476
|
+
score += 2
|
|
477
|
+
|
|
478
|
+
# Check for proper section organization
|
|
479
|
+
section_headers = re.findall(r'^##\s+(.+)$', content, re.MULTILINE)
|
|
480
|
+
if len(section_headers) >= 3:
|
|
481
|
+
score += 2
|
|
482
|
+
|
|
483
|
+
return min(score, 10)
|
|
484
|
+
|
|
485
|
+
def _has_inline_examples(self) -> bool:
|
|
486
|
+
"""Check if examples are inline."""
|
|
487
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
488
|
+
with open(skill_md) as f:
|
|
489
|
+
content = f.read()
|
|
490
|
+
|
|
491
|
+
# Check for code blocks (indicates examples present)
|
|
492
|
+
has_code_blocks = '```' in content
|
|
493
|
+
|
|
494
|
+
# Check if "Examples" is a separate section (bad practice)
|
|
495
|
+
has_separate_examples = bool(re.search(r'^##\s+Examples?\s*$', content, re.MULTILINE))
|
|
496
|
+
|
|
497
|
+
return has_code_blocks and not has_separate_examples
|
|
498
|
+
|
|
499
|
+
def _detect_bloat(self, content: str) -> bool:
|
|
500
|
+
"""Detect content bloat."""
|
|
501
|
+
# Check for very long sections
|
|
502
|
+
sections = content.split('\n## ')
|
|
503
|
+
for section in sections:
|
|
504
|
+
section_lines = section.split('\n')
|
|
505
|
+
if len(section_lines) > 150:
|
|
506
|
+
return True # Section too long
|
|
507
|
+
|
|
508
|
+
# Check for excessive repetition
|
|
509
|
+
lines = content.split('\n')
|
|
510
|
+
if len(lines) > 100:
|
|
511
|
+
# Sample check: look for repeated patterns
|
|
512
|
+
line_set = set(lines)
|
|
513
|
+
repetition_ratio = len(line_set) / len(lines)
|
|
514
|
+
if repetition_ratio < 0.7: # Less than 70% unique lines
|
|
515
|
+
return True
|
|
516
|
+
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
def _has_hardcoded_secrets(self) -> bool:
|
|
520
|
+
"""Check for hardcoded secrets."""
|
|
521
|
+
patterns = [
|
|
522
|
+
r'api[_-]?key\s*=\s*["\'][^"\']+["\']',
|
|
523
|
+
r'password\s*=\s*["\'][^"\']+["\']',
|
|
524
|
+
r'secret\s*=\s*["\'][^"\']+["\']',
|
|
525
|
+
r'token\s*=\s*["\'][^"\']+["\']',
|
|
526
|
+
r'bearer\s+[A-Za-z0-9\-._~+/]+=*',
|
|
527
|
+
]
|
|
528
|
+
|
|
529
|
+
# Scan all files in skill directory
|
|
530
|
+
for file in self.skill_path.rglob('*'):
|
|
531
|
+
if file.is_file() and file.suffix in ['.md', '.py', '.sh', '.yml', '.yaml']:
|
|
532
|
+
try:
|
|
533
|
+
with open(file, encoding='utf-8') as f:
|
|
534
|
+
content = f.read()
|
|
535
|
+
for pattern in patterns:
|
|
536
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
537
|
+
return True
|
|
538
|
+
except:
|
|
539
|
+
continue
|
|
540
|
+
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
def _has_dangerous_patterns(self) -> bool:
|
|
544
|
+
"""Check for dangerous code patterns."""
|
|
545
|
+
dangerous_patterns = [
|
|
546
|
+
r'\beval\s*\(',
|
|
547
|
+
r'\bexec\s*\(',
|
|
548
|
+
r'shell\s*=\s*True',
|
|
549
|
+
r'os\.system\(',
|
|
550
|
+
r'subprocess\..*shell=True',
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
# Scan Python scripts
|
|
554
|
+
for script in self.skill_path.rglob('*.py'):
|
|
555
|
+
try:
|
|
556
|
+
with open(script, encoding='utf-8') as f:
|
|
557
|
+
content = f.read()
|
|
558
|
+
for pattern in dangerous_patterns:
|
|
559
|
+
if re.search(pattern, content):
|
|
560
|
+
return True
|
|
561
|
+
except:
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
return False
|
|
565
|
+
|
|
566
|
+
def _has_input_validation(self) -> bool:
|
|
567
|
+
"""Check if input validation exists."""
|
|
568
|
+
validation_keywords = ['validate', 'check', 'verify', 'assert', 'raise',
|
|
569
|
+
'isinstance', 'try:', 'except:', 'if not']
|
|
570
|
+
|
|
571
|
+
# Check SKILL.md for validation mentions
|
|
572
|
+
skill_md = self.skill_path / "SKILL.md"
|
|
573
|
+
with open(skill_md) as f:
|
|
574
|
+
skill_content = f.read()
|
|
575
|
+
has_validation_docs = any(kw in skill_content.lower() for kw in validation_keywords[:3])
|
|
576
|
+
|
|
577
|
+
# Check Python scripts for validation code
|
|
578
|
+
has_validation_code = False
|
|
579
|
+
for script in self.skill_path.rglob('*.py'):
|
|
580
|
+
try:
|
|
581
|
+
with open(script, encoding='utf-8') as f:
|
|
582
|
+
content = f.read()
|
|
583
|
+
if any(kw in content for kw in validation_keywords):
|
|
584
|
+
has_validation_code = True
|
|
585
|
+
break
|
|
586
|
+
except:
|
|
587
|
+
continue
|
|
588
|
+
|
|
589
|
+
return has_validation_docs or has_validation_code
|
|
590
|
+
|
|
591
|
+
def _count_imperative_sentences(self, content: str) -> float:
|
|
592
|
+
"""Calculate ratio of imperative sentences."""
|
|
593
|
+
# Imperative verbs commonly used in documentation
|
|
594
|
+
imperative_verbs = [
|
|
595
|
+
'use', 'run', 'execute', 'create', 'configure', 'install',
|
|
596
|
+
'check', 'validate', 'ensure', 'verify', 'set', 'define',
|
|
597
|
+
'specify', 'provide', 'include', 'add', 'remove', 'update',
|
|
598
|
+
'follow', 'read', 'write', 'call', 'invoke', 'load', 'scan',
|
|
599
|
+
'extract', 'detect', 'discover', 'generate', 'implement'
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
# Remove YAML frontmatter
|
|
603
|
+
processed_content = content
|
|
604
|
+
if content.startswith('---\n'):
|
|
605
|
+
end_idx = content.find('\n---\n', 4)
|
|
606
|
+
if end_idx != -1:
|
|
607
|
+
processed_content = content[end_idx + 5:]
|
|
608
|
+
|
|
609
|
+
# Remove code blocks to avoid counting code as sentences
|
|
610
|
+
processed_content = re.sub(r'```.*?```', '', processed_content, flags=re.DOTALL)
|
|
611
|
+
|
|
612
|
+
# Split into sentences (approximate)
|
|
613
|
+
sentences = re.split(r'[.!?]\n', processed_content)
|
|
614
|
+
sentences = [s.strip() for s in sentences if len(s.strip()) > 10]
|
|
615
|
+
|
|
616
|
+
if not sentences:
|
|
617
|
+
return 0.0
|
|
618
|
+
|
|
619
|
+
# Count sentences with imperative verbs
|
|
620
|
+
imperative_count = 0
|
|
621
|
+
for sentence in sentences:
|
|
622
|
+
# Strip markdown formatting (bold, italic, inline code)
|
|
623
|
+
clean_sentence = re.sub(r'\*\*([^*]+)\*\*', r'\1', sentence)
|
|
624
|
+
clean_sentence = re.sub(r'\*([^*]+)\*', r'\1', clean_sentence)
|
|
625
|
+
clean_sentence = re.sub(r'`([^`]+)`', r'\1', clean_sentence)
|
|
626
|
+
clean_sentence = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', clean_sentence)
|
|
627
|
+
|
|
628
|
+
# Remove special markers and colons at start
|
|
629
|
+
clean_sentence = re.sub(r'^[\-\*\+]\s+', '', clean_sentence)
|
|
630
|
+
clean_sentence = clean_sentence.strip()
|
|
631
|
+
|
|
632
|
+
if not clean_sentence:
|
|
633
|
+
continue
|
|
634
|
+
|
|
635
|
+
# Get first 3 words to check for imperative
|
|
636
|
+
words = clean_sentence.lower().split()
|
|
637
|
+
first_words = words[:3] if len(words) >= 3 else words
|
|
638
|
+
|
|
639
|
+
# Check if any of first 3 words is imperative verb
|
|
640
|
+
has_imperative = any(
|
|
641
|
+
any(word.startswith(verb) for verb in imperative_verbs)
|
|
642
|
+
for word in first_words
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
if has_imperative:
|
|
646
|
+
imperative_count += 1
|
|
647
|
+
|
|
648
|
+
return imperative_count / len(sentences)
|
|
649
|
+
|
|
650
|
+
def _calculate_avg_sentence_length(self, content: str) -> float:
|
|
651
|
+
"""Calculate average sentence length in words."""
|
|
652
|
+
# Remove code blocks to avoid skewing results
|
|
653
|
+
content_no_code = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
|
|
654
|
+
|
|
655
|
+
sentences = re.split(r'[.!?]\s+', content_no_code)
|
|
656
|
+
sentences = [s.strip() for s in sentences if len(s.strip()) > 10]
|
|
657
|
+
|
|
658
|
+
if not sentences:
|
|
659
|
+
return 0.0
|
|
660
|
+
|
|
661
|
+
total_words = sum(len(s.split()) for s in sentences)
|
|
662
|
+
return total_words / len(sentences)
|
|
663
|
+
|
|
664
|
+
def _has_clear_headers(self, content: str) -> bool:
|
|
665
|
+
"""Check if headers are clear and descriptive."""
|
|
666
|
+
headers = re.findall(r'^##\s+(.+)$', content, re.MULTILINE)
|
|
667
|
+
|
|
668
|
+
if not headers:
|
|
669
|
+
return False
|
|
670
|
+
|
|
671
|
+
# Check if headers are descriptive (more than 2 words or technical term)
|
|
672
|
+
descriptive_count = 0
|
|
673
|
+
for header in headers:
|
|
674
|
+
header_clean = header.strip()
|
|
675
|
+
words = header_clean.split()
|
|
676
|
+
|
|
677
|
+
# Consider descriptive if: >2 words OR has technical indicators
|
|
678
|
+
is_descriptive = (
|
|
679
|
+
len(words) > 2 or
|
|
680
|
+
any(char.isupper() for char in header_clean[1:]) or # CamelCase
|
|
681
|
+
'-' in header_clean or '_' in header_clean # technical terms
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if is_descriptive:
|
|
685
|
+
descriptive_count += 1
|
|
686
|
+
|
|
687
|
+
# At least 70% of headers should be descriptive
|
|
688
|
+
return descriptive_count / len(headers) >= 0.7
|
|
689
|
+
|
|
690
|
+
# ========== MAIN SCORING ==========
|
|
691
|
+
|
|
692
|
+
def calculate_overall_score(self) -> Dict:
|
|
693
|
+
"""
|
|
694
|
+
Calculate overall quality score.
|
|
695
|
+
|
|
696
|
+
Combines all category scores into overall rating.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Dict with overall score and breakdown
|
|
700
|
+
"""
|
|
701
|
+
# Score each category
|
|
702
|
+
self.scores['structure'] = self.score_structure()
|
|
703
|
+
self.scores['content'] = self.score_content()
|
|
704
|
+
self.scores['efficiency'] = self.score_efficiency()
|
|
705
|
+
self.scores['security'] = self.score_security()
|
|
706
|
+
self.scores['style'] = self.score_style()
|
|
707
|
+
|
|
708
|
+
# Calculate total
|
|
709
|
+
total_score = sum(s['score'] for s in self.scores.values())
|
|
710
|
+
total_max = sum(s['max'] for s in self.scores.values())
|
|
711
|
+
overall_percentage = (total_score / total_max) * 100
|
|
712
|
+
|
|
713
|
+
# Collect all issues
|
|
714
|
+
all_issues = []
|
|
715
|
+
for category, data in self.scores.items():
|
|
716
|
+
for issue in data['issues']:
|
|
717
|
+
all_issues.append(f"{category.capitalize()}: {issue}")
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
'score': total_score,
|
|
721
|
+
'max': total_max,
|
|
722
|
+
'percentage': overall_percentage,
|
|
723
|
+
'grade': self._get_grade(overall_percentage),
|
|
724
|
+
'issues': all_issues,
|
|
725
|
+
'categories': self.scores
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
def _get_grade(self, percentage: float) -> str:
|
|
729
|
+
"""Convert percentage to letter grade."""
|
|
730
|
+
if percentage >= 90:
|
|
731
|
+
return 'A (Excellent)'
|
|
732
|
+
elif percentage >= 80:
|
|
733
|
+
return 'B (Good)'
|
|
734
|
+
elif percentage >= 70:
|
|
735
|
+
return 'C (Fair)'
|
|
736
|
+
elif percentage >= 60:
|
|
737
|
+
return 'D (Needs Improvement)'
|
|
738
|
+
else:
|
|
739
|
+
return 'F (Poor)'
|
|
740
|
+
|
|
741
|
+
def display_report(self, overall: Dict):
|
|
742
|
+
"""Display quality report to console."""
|
|
743
|
+
print("\n" + "="*60)
|
|
744
|
+
print("SKILL QUALITY REPORT")
|
|
745
|
+
print("="*60)
|
|
746
|
+
print(f"\nSkill: {self.skill_path.name}")
|
|
747
|
+
print(f"Overall Score: {overall['score']}/{overall['max']} ({overall['percentage']:.1f}%)")
|
|
748
|
+
print(f"Grade: {overall['grade']}")
|
|
749
|
+
|
|
750
|
+
if self.detailed:
|
|
751
|
+
print("\nCategory Breakdown:")
|
|
752
|
+
for category, data in self.scores.items():
|
|
753
|
+
print(f"\n {category.capitalize():12} {data['score']:2}/{data['max']:2} ({data['percentage']:5.1f}%)")
|
|
754
|
+
if data['issues']:
|
|
755
|
+
for issue in data['issues']:
|
|
756
|
+
print(f" âš ï¸ {issue}")
|
|
757
|
+
|
|
758
|
+
if overall['issues']:
|
|
759
|
+
print("\n" + "-"*60)
|
|
760
|
+
print("Recommendations:")
|
|
761
|
+
for i, issue in enumerate(overall['issues'], 1):
|
|
762
|
+
print(f" {i}. {issue}")
|
|
763
|
+
|
|
764
|
+
print("\n" + "="*60)
|
|
765
|
+
print("References:")
|
|
766
|
+
print(" • Files 01-13: Best practices documentation")
|
|
767
|
+
print(" • File 05: Token optimization")
|
|
768
|
+
print(" • File 07: Security guidelines")
|
|
769
|
+
print(" • File 10: Architecture standards")
|
|
770
|
+
print("="*60 + "\n")
|
|
771
|
+
|
|
772
|
+
def export_json(self, filepath: str, overall: Dict):
|
|
773
|
+
"""Export report as JSON."""
|
|
774
|
+
report = {
|
|
775
|
+
'skill_path': str(self.skill_path),
|
|
776
|
+
'skill_name': self.skill_path.name,
|
|
777
|
+
'overall': {
|
|
778
|
+
'score': overall['score'],
|
|
779
|
+
'max': overall['max'],
|
|
780
|
+
'percentage': round(overall['percentage'], 2),
|
|
781
|
+
'grade': overall['grade']
|
|
782
|
+
},
|
|
783
|
+
'categories': {
|
|
784
|
+
category: {
|
|
785
|
+
'score': data['score'],
|
|
786
|
+
'max': data['max'],
|
|
787
|
+
'percentage': round(data['percentage'], 2),
|
|
788
|
+
'issues': data['issues']
|
|
789
|
+
}
|
|
790
|
+
for category, data in overall['categories'].items()
|
|
791
|
+
},
|
|
792
|
+
'recommendations': overall['issues']
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
with open(filepath, 'w') as f:
|
|
796
|
+
json.dump(report, f, indent=2)
|
|
797
|
+
|
|
798
|
+
def export_markdown(self, filepath: str, overall: Dict):
|
|
799
|
+
"""Export report as Markdown."""
|
|
800
|
+
lines = []
|
|
801
|
+
|
|
802
|
+
lines.append("# Skill Quality Report")
|
|
803
|
+
lines.append("")
|
|
804
|
+
lines.append(f"**Skill:** {self.skill_path.name}")
|
|
805
|
+
lines.append(f"**Path:** `{self.skill_path}`")
|
|
806
|
+
lines.append("")
|
|
807
|
+
|
|
808
|
+
lines.append("## Overall Score")
|
|
809
|
+
lines.append("")
|
|
810
|
+
lines.append(f"- **Score:** {overall['score']}/{overall['max']} ({overall['percentage']:.1f}%)")
|
|
811
|
+
lines.append(f"- **Grade:** {overall['grade']}")
|
|
812
|
+
lines.append("")
|
|
813
|
+
|
|
814
|
+
lines.append("## Category Breakdown")
|
|
815
|
+
lines.append("")
|
|
816
|
+
lines.append("| Category | Score | Percentage | Status |")
|
|
817
|
+
lines.append("|----------|-------|------------|--------|")
|
|
818
|
+
|
|
819
|
+
for category, data in overall['categories'].items():
|
|
820
|
+
status = "✅" if data['percentage'] >= 80 else "âš ï¸" if data['percentage'] >= 60 else "âŒ"
|
|
821
|
+
lines.append(f"| {category.capitalize()} | {data['score']}/{data['max']} | {data['percentage']:.1f}% | {status} |")
|
|
822
|
+
|
|
823
|
+
lines.append("")
|
|
824
|
+
|
|
825
|
+
# Detailed issues per category
|
|
826
|
+
if any(data['issues'] for data in overall['categories'].values()):
|
|
827
|
+
lines.append("## Issues by Category")
|
|
828
|
+
lines.append("")
|
|
829
|
+
|
|
830
|
+
for category, data in overall['categories'].items():
|
|
831
|
+
if data['issues']:
|
|
832
|
+
lines.append(f"### {category.capitalize()}")
|
|
833
|
+
lines.append("")
|
|
834
|
+
for issue in data['issues']:
|
|
835
|
+
lines.append(f"- âš ï¸ {issue}")
|
|
836
|
+
lines.append("")
|
|
837
|
+
|
|
838
|
+
# Recommendations
|
|
839
|
+
if overall['issues']:
|
|
840
|
+
lines.append("## Recommendations")
|
|
841
|
+
lines.append("")
|
|
842
|
+
for i, issue in enumerate(overall['issues'], 1):
|
|
843
|
+
lines.append(f"{i}. {issue}")
|
|
844
|
+
lines.append("")
|
|
845
|
+
|
|
846
|
+
lines.append("---")
|
|
847
|
+
lines.append("")
|
|
848
|
+
lines.append("**References:**")
|
|
849
|
+
lines.append("- Files 01-13: Best practices documentation")
|
|
850
|
+
lines.append("- File 05: Token optimization")
|
|
851
|
+
lines.append("- File 07: Security guidelines")
|
|
852
|
+
lines.append("- File 10: Architecture standards")
|
|
853
|
+
|
|
854
|
+
with open(filepath, 'w') as f:
|
|
855
|
+
f.write('\n'.join(lines))
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def main():
|
|
859
|
+
"""CLI entry point."""
|
|
860
|
+
parser = argparse.ArgumentParser(
|
|
861
|
+
description="Score skill quality against best practices",
|
|
862
|
+
epilog="References: Files 01-13 for comprehensive best practices"
|
|
863
|
+
)
|
|
864
|
+
parser.add_argument(
|
|
865
|
+
'skill_path',
|
|
866
|
+
type=str,
|
|
867
|
+
help='Path to skill directory'
|
|
868
|
+
)
|
|
869
|
+
parser.add_argument(
|
|
870
|
+
'--format',
|
|
871
|
+
type=str,
|
|
872
|
+
choices=['text', 'json'],
|
|
873
|
+
default='text',
|
|
874
|
+
help='Output format: text (human-readable) or json (agent-layer)'
|
|
875
|
+
)
|
|
876
|
+
parser.add_argument(
|
|
877
|
+
'--detailed',
|
|
878
|
+
action='store_true',
|
|
879
|
+
help='Show detailed category breakdown (text format only)'
|
|
880
|
+
)
|
|
881
|
+
parser.add_argument(
|
|
882
|
+
'--export',
|
|
883
|
+
type=str,
|
|
884
|
+
help='Export report to file (JSON or MD format based on extension)'
|
|
885
|
+
)
|
|
886
|
+
parser.add_argument(
|
|
887
|
+
'--behavioral',
|
|
888
|
+
action='store_true',
|
|
889
|
+
help='Include behavioral pressure testing (full mode)'
|
|
890
|
+
)
|
|
891
|
+
parser.add_argument(
|
|
892
|
+
'--skill-type',
|
|
893
|
+
choices=['discipline', 'technique', 'pattern', 'reference'],
|
|
894
|
+
default='discipline',
|
|
895
|
+
help='Skill type for behavioral testing'
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
args = parser.parse_args()
|
|
899
|
+
|
|
900
|
+
try:
|
|
901
|
+
scorer = QualityScorer(args.skill_path, detailed=args.detailed)
|
|
902
|
+
overall = scorer.calculate_overall_score()
|
|
903
|
+
structural_score = round(overall['percentage'] / 10.0, 2)
|
|
904
|
+
behavioral_score = None
|
|
905
|
+
if args.behavioral:
|
|
906
|
+
import warnings
|
|
907
|
+
warnings.warn(
|
|
908
|
+
"DEPRECATED: --behavioral flag is deprecated in v2.1. "
|
|
909
|
+
"Behavioral scoring via pressure_tester.py has been removed. "
|
|
910
|
+
"Use the Full Mode Behavioral Testing Protocol instead: "
|
|
911
|
+
"skills/skillkit/references/section-2-full-creation-workflow.md",
|
|
912
|
+
DeprecationWarning,
|
|
913
|
+
stacklevel=2,
|
|
914
|
+
)
|
|
915
|
+
behavioral_score = scorer.run_behavioral_tests(args.skill_path, args.skill_type)
|
|
916
|
+
final_score = scorer.calculate_final_score(structural_score, behavioral_score)
|
|
917
|
+
|
|
918
|
+
# Agent-layer JSON output (stdout)
|
|
919
|
+
if args.format == 'json':
|
|
920
|
+
import sys
|
|
921
|
+
report = {
|
|
922
|
+
'status': 'success',
|
|
923
|
+
'skill_path': str(scorer.skill_path),
|
|
924
|
+
'skill_name': scorer.skill_path.name,
|
|
925
|
+
'mode': final_score['mode'],
|
|
926
|
+
'final_score': final_score['final_score'],
|
|
927
|
+
'structural_score': final_score['structural_score'],
|
|
928
|
+
'behavioral_score': final_score['behavioral_score'],
|
|
929
|
+
'weights': final_score['weights'],
|
|
930
|
+
'overall': {
|
|
931
|
+
'score': overall['score'],
|
|
932
|
+
'max': overall['max'],
|
|
933
|
+
'percentage': round(overall['percentage'], 2),
|
|
934
|
+
'grade': overall['grade']
|
|
935
|
+
},
|
|
936
|
+
'categories': {
|
|
937
|
+
category: {
|
|
938
|
+
'score': data['score'],
|
|
939
|
+
'max': data['max'],
|
|
940
|
+
'percentage': round(data['percentage'], 2),
|
|
941
|
+
'issues': data['issues']
|
|
942
|
+
}
|
|
943
|
+
for category, data in overall['categories'].items()
|
|
944
|
+
},
|
|
945
|
+
'recommendations': overall['issues']
|
|
946
|
+
}
|
|
947
|
+
print(json.dumps(report, indent=2))
|
|
948
|
+
return 0
|
|
949
|
+
|
|
950
|
+
# Human-readable text output (console)
|
|
951
|
+
scorer.display_report(overall)
|
|
952
|
+
|
|
953
|
+
if args.export:
|
|
954
|
+
export_path = Path(args.export)
|
|
955
|
+
|
|
956
|
+
if export_path.suffix.lower() == '.json':
|
|
957
|
+
scorer.export_json(args.export, overall)
|
|
958
|
+
print(f"✅ Report exported to {args.export}")
|
|
959
|
+
elif export_path.suffix.lower() in ['.md', '.markdown']:
|
|
960
|
+
scorer.export_markdown(args.export, overall)
|
|
961
|
+
print(f"✅ Report exported to {args.export}")
|
|
962
|
+
else:
|
|
963
|
+
print(f"âš ï¸ Unknown export format. Use .json or .md extension.")
|
|
964
|
+
return 1
|
|
965
|
+
|
|
966
|
+
# Return exit code based on score
|
|
967
|
+
if final_score['final_score'] >= 7.0:
|
|
968
|
+
return 0
|
|
969
|
+
else:
|
|
970
|
+
return 1
|
|
971
|
+
|
|
972
|
+
except FileNotFoundError as e:
|
|
973
|
+
if args.format == 'json':
|
|
974
|
+
error_report = {
|
|
975
|
+
'status': 'error',
|
|
976
|
+
'error_type': 'FileNotFound',
|
|
977
|
+
'message': str(e),
|
|
978
|
+
'help': 'Ensure skill directory exists and contains SKILL.md'
|
|
979
|
+
}
|
|
980
|
+
print(json.dumps(error_report, indent=2))
|
|
981
|
+
else:
|
|
982
|
+
print(f"⌠Error: {e}")
|
|
983
|
+
return 1
|
|
984
|
+
except Exception as e:
|
|
985
|
+
if args.format == 'json':
|
|
986
|
+
error_report = {
|
|
987
|
+
'status': 'error',
|
|
988
|
+
'error_type': 'UnexpectedError',
|
|
989
|
+
'message': str(e),
|
|
990
|
+
'help': 'Check skill structure and permissions'
|
|
991
|
+
}
|
|
992
|
+
print(json.dumps(error_report, indent=2))
|
|
993
|
+
else:
|
|
994
|
+
print(f"⌠Unexpected error: {e}")
|
|
995
|
+
return 2
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
if __name__ == "__main__":
|
|
999
|
+
exit(main())
|