@tinkcarlos/skillora 0.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/.claude/skills/.temp-skill-index.md +245 -0
- package/.claude/skills/SKILL.md +264 -0
- package/.claude/skills/api-scaffolding/SKILL.md +431 -0
- package/.claude/skills/api-scaffolding/agents/backend-architect.md +282 -0
- package/.claude/skills/api-scaffolding/agents/django-pro.md +144 -0
- package/.claude/skills/api-scaffolding/agents/fastapi-pro.md +156 -0
- package/.claude/skills/api-scaffolding/agents/graphql-architect.md +146 -0
- package/.claude/skills/api-scaffolding/skills/fastapi-templates/SKILL.md +171 -0
- package/.claude/skills/api-testing-observability/SKILL.md +583 -0
- package/.claude/skills/api-testing-observability/agents/api-documenter.md +146 -0
- package/.claude/skills/api-testing-observability/commands/api-mock.md +1320 -0
- package/.claude/skills/brainstorming/SKILL.md +283 -0
- package/.claude/skills/bug-fixing/SKILL.md +382 -0
- package/.claude/skills/bug-fixing/references/backend-guide.md +132 -0
- package/.claude/skills/bug-fixing/references/bug-guide.md +354 -0
- package/.claude/skills/bug-fixing/references/bug-record-template.md +134 -0
- package/.claude/skills/bug-fixing/references/bug-records.md +88 -0
- package/.claude/skills/bug-fixing/references/code-review-gate.md +81 -0
- package/.claude/skills/bug-fixing/references/common-bugs.md +140 -0
- package/.claude/skills/bug-fixing/references/complete-workflow.md +361 -0
- package/.claude/skills/bug-fixing/references/config-driven-fixes.md +136 -0
- package/.claude/skills/bug-fixing/references/context-isolation-protocol.md +268 -0
- package/.claude/skills/bug-fixing/references/cross-surface-regression.md +120 -0
- package/.claude/skills/bug-fixing/references/database-investigation.md +129 -0
- package/.claude/skills/bug-fixing/references/dependency-and-integrity-protocol.md +369 -0
- package/.claude/skills/bug-fixing/references/fix-completeness-checklist.md +239 -0
- package/.claude/skills/bug-fixing/references/frontend-guide.md +219 -0
- package/.claude/skills/bug-fixing/references/fullstack-joint-guide.md +123 -0
- package/.claude/skills/bug-fixing/references/functional-breakage.md +117 -0
- package/.claude/skills/bug-fixing/references/ide-lint-errors-guide.md +176 -0
- package/.claude/skills/bug-fixing/references/impact-analysis.md +511 -0
- package/.claude/skills/bug-fixing/references/investigation-checklist.md +263 -0
- package/.claude/skills/bug-fixing/references/knowledge-extraction-guide.md +531 -0
- package/.claude/skills/bug-fixing/references/knowledge-workflow.md +212 -0
- package/.claude/skills/bug-fixing/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/bug-fixing/references/python-env-and-testing.md +126 -0
- package/.claude/skills/bug-fixing/references/rca-guide.md +428 -0
- package/.claude/skills/bug-fixing/references/similar-bug-patterns.md +113 -0
- package/.claude/skills/bug-fixing/references/skill-delegation-guide.md +350 -0
- package/.claude/skills/bug-fixing/references/skill-orchestration.md +155 -0
- package/.claude/skills/bug-fixing/references/testing-strategy.md +350 -0
- package/.claude/skills/bug-fixing/references/tooling-build-scripts.md +162 -0
- package/.claude/skills/bug-fixing/references/user-input-validation.md +77 -0
- package/.claude/skills/bug-fixing/references/ux-patterns.md +158 -0
- package/.claude/skills/bug-fixing/references/windows-terminal-hygiene.md +106 -0
- package/.claude/skills/bug-fixing/references/zero-regression-matrix.md +239 -0
- package/.claude/skills/bug-fixing/references/zero-risk-protocol.md +102 -0
- package/.claude/skills/bug-fixing/scripts/format_code.py +611 -0
- package/.claude/skills/bug-fixing/scripts/generate_report_template.py +74 -0
- package/.claude/skills/bug-fixing/scripts/lint_check.py +816 -0
- package/.claude/skills/bug-fixing/scripts/requirements.txt +36 -0
- package/.claude/skills/cicd-pipeline/SKILL.md +300 -0
- package/.claude/skills/code-review/SKILL.md +535 -0
- package/.claude/skills/code-review/references/anti-pattern-scan.md +102 -0
- package/.claude/skills/code-review/references/automated-analysis.md +456 -0
- package/.claude/skills/code-review/references/backend-common-issues.md +589 -0
- package/.claude/skills/code-review/references/backend-expert-guide.md +415 -0
- package/.claude/skills/code-review/references/backend-review.md +868 -0
- package/.claude/skills/code-review/references/batch-processing-strategy.md +198 -0
- package/.claude/skills/code-review/references/call-chain-analysis-protocol.md +166 -0
- package/.claude/skills/code-review/references/common-patterns.md +321 -0
- package/.claude/skills/code-review/references/configuration-review.md +425 -0
- package/.claude/skills/code-review/references/control-flow-completeness.md +114 -0
- package/.claude/skills/code-review/references/database-review.md +298 -0
- package/.claude/skills/code-review/references/dependency-and-integrity-protocol.md +313 -0
- package/.claude/skills/code-review/references/external-standards.md +51 -0
- package/.claude/skills/code-review/references/feature-review.md +329 -0
- package/.claude/skills/code-review/references/file-review-template.md +326 -0
- package/.claude/skills/code-review/references/frontend-advanced.md +654 -0
- package/.claude/skills/code-review/references/frontend-common-issues.md +482 -0
- package/.claude/skills/code-review/references/frontend-expert-guide.md +342 -0
- package/.claude/skills/code-review/references/frontend-review.md +783 -0
- package/.claude/skills/code-review/references/fullstack-consistency.md +418 -0
- package/.claude/skills/code-review/references/fullstack-review.md +477 -0
- package/.claude/skills/code-review/references/functional-completeness.md +386 -0
- package/.claude/skills/code-review/references/hidden-bugs-detection.md +473 -0
- package/.claude/skills/code-review/references/ide-lint-errors-guide.md +173 -0
- package/.claude/skills/code-review/references/infrastructure-review.md +453 -0
- package/.claude/skills/code-review/references/iteration-review.md +264 -0
- package/.claude/skills/code-review/references/job-review.md +335 -0
- package/.claude/skills/code-review/references/layered-checklist-protocol.md +157 -0
- package/.claude/skills/code-review/references/logic-completeness.md +535 -0
- package/.claude/skills/code-review/references/mandatory-checklist.md +288 -0
- package/.claude/skills/code-review/references/multi-language-guide.md +800 -0
- package/.claude/skills/code-review/references/new-project-review.md +226 -0
- package/.claude/skills/code-review/references/non-code-files-review.md +451 -0
- package/.claude/skills/code-review/references/overlooked-issues.md +657 -0
- package/.claude/skills/code-review/references/platform-specific-review.md +195 -0
- package/.claude/skills/code-review/references/precision-analysis-protocol.md +260 -0
- package/.claude/skills/code-review/references/python-patterns.md +494 -0
- package/.claude/skills/code-review/references/rca-techniques.md +362 -0
- package/.claude/skills/code-review/references/report-template.md +430 -0
- package/.claude/skills/code-review/references/resource-limits-and-degradation.md +137 -0
- package/.claude/skills/code-review/references/review-dimensions.md +311 -0
- package/.claude/skills/code-review/references/review-guide.md +202 -0
- package/.claude/skills/code-review/references/review-knowledge-workflow.md +257 -0
- package/.claude/skills/code-review/references/review-progress-tracker-protocol.md +172 -0
- package/.claude/skills/code-review/references/review-record-template.md +195 -0
- package/.claude/skills/code-review/references/skill-orchestration.md +143 -0
- package/.claude/skills/code-review/references/ui-ux-review.md +470 -0
- package/.claude/skills/containerization/SKILL.md +313 -0
- package/.claude/skills/database-migrations/agents/database-admin.md +142 -0
- package/.claude/skills/database-migrations/agents/database-optimizer.md +144 -0
- package/.claude/skills/database-migrations/commands/migration-observability.md +408 -0
- package/.claude/skills/database-migrations/commands/sql-migrations.md +492 -0
- package/.claude/skills/finishing-a-development-branch/SKILL.md +319 -0
- package/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/skills/frontend-design/SKILL.md +587 -0
- package/.claude/skills/frontend-design/references/color-consistency.md +487 -0
- package/.claude/skills/frontend-design/references/color-palettes-full.md +657 -0
- package/.claude/skills/frontend-design/references/design-system-generator.md +285 -0
- package/.claude/skills/frontend-design/references/font-pairings-full.md +705 -0
- package/.claude/skills/frontend-design/references/industry-anti-patterns.md +281 -0
- package/.claude/skills/frontend-design/references/layout-anti-patterns.md +582 -0
- package/.claude/skills/frontend-design/references/motion-patterns.md +659 -0
- package/.claude/skills/frontend-design/references/pre-delivery-checklist.md +153 -0
- package/.claude/skills/frontend-design/references/responsive-design.md +555 -0
- package/.claude/skills/frontend-design/references/style-modification-rules.md +335 -0
- package/.claude/skills/frontend-design/references/ui-styles-full.md +383 -0
- package/.claude/skills/frontend-design/references/ui-styles-rating.md +191 -0
- package/.claude/skills/frontend-design/references/ux-guidelines.md +640 -0
- package/.claude/skills/fullstack-developer/SKILL.md +512 -0
- package/.claude/skills/fullstack-developer/references/api-contract-guide.md +312 -0
- package/.claude/skills/fullstack-developer/references/api-response-patterns.md +223 -0
- package/.claude/skills/fullstack-developer/references/async-patterns.md +220 -0
- package/.claude/skills/fullstack-developer/references/bug-prevention.md +914 -0
- package/.claude/skills/fullstack-developer/references/code-quality-checklist.md +271 -0
- package/.claude/skills/fullstack-developer/references/complete-development-workflow.md +278 -0
- package/.claude/skills/fullstack-developer/references/context-isolation-protocol.md +256 -0
- package/.claude/skills/fullstack-developer/references/database-migration.md +331 -0
- package/.claude/skills/fullstack-developer/references/dependency-and-integrity-protocol.md +390 -0
- package/.claude/skills/fullstack-developer/references/development-phases.md +333 -0
- package/.claude/skills/fullstack-developer/references/expert-guide.md +214 -0
- package/.claude/skills/fullstack-developer/references/file-import-patterns.md +114 -0
- package/.claude/skills/fullstack-developer/references/graceful-degradation-patterns.md +78 -0
- package/.claude/skills/fullstack-developer/references/ide-lint-errors-guide.md +183 -0
- package/.claude/skills/fullstack-developer/references/integration-testing.md +301 -0
- package/.claude/skills/fullstack-developer/references/mock-api-patterns.md +307 -0
- package/.claude/skills/fullstack-developer/references/phase-gate-template.md +249 -0
- package/.claude/skills/fullstack-developer/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/fullstack-developer/references/python-engineering.md +79 -0
- package/.claude/skills/fullstack-developer/references/skill-orchestration.md +214 -0
- package/.claude/skills/fullstack-developer/references/skill-router-table.md +304 -0
- package/.claude/skills/fullstack-developer/references/state-sync.md +217 -0
- package/.claude/skills/fullstack-developer/references/ui-testing-checklist.md +292 -0
- package/.claude/skills/fullstack-developer/scripts/format_code.py +611 -0
- package/.claude/skills/fullstack-developer/scripts/lint_check.py +816 -0
- package/.claude/skills/fullstack-developer/scripts/requirements.txt +36 -0
- package/.claude/skills/performance-optimization/SKILL.md +250 -0
- package/.claude/skills/product-requirements/SKILL.md +357 -0
- package/.claude/skills/product-requirements/references/acceptance-criteria.md +335 -0
- package/.claude/skills/product-requirements/references/answer-first-questioning-protocol.md +299 -0
- package/.claude/skills/product-requirements/references/competitive-analysis-guide.md +183 -0
- package/.claude/skills/product-requirements/references/document-accuracy-protocol.md +253 -0
- package/.claude/skills/product-requirements/references/document-management-protocol.md +278 -0
- package/.claude/skills/product-requirements/references/external-standards.md +62 -0
- package/.claude/skills/product-requirements/references/feature-spec-template.md +359 -0
- package/.claude/skills/product-requirements/references/knowledge-acquisition-protocol.md +251 -0
- package/.claude/skills/product-requirements/references/plan-execution-protocol.md +334 -0
- package/.claude/skills/product-requirements/references/plan-generation-protocol.md +264 -0
- package/.claude/skills/product-requirements/references/prioritization-frameworks.md +80 -0
- package/.claude/skills/product-requirements/references/requirement-decomposition-protocol.md +291 -0
- package/.claude/skills/product-requirements/references/user-story-examples.md +297 -0
- package/.claude/skills/product-requirements/references/workflow-templates.md +266 -0
- package/.claude/skills/react-best-practices/SKILL.md +198 -0
- package/.claude/skills/react-best-practices/references/advanced-patterns.md +94 -0
- package/.claude/skills/react-best-practices/references/bundle-optimization.md +182 -0
- package/.claude/skills/react-best-practices/references/client-data-fetching.md +112 -0
- package/.claude/skills/react-best-practices/references/complete-guide.md +2249 -0
- package/.claude/skills/react-best-practices/references/eliminating-waterfalls.md +169 -0
- package/.claude/skills/react-best-practices/references/javascript-performance.md +256 -0
- package/.claude/skills/react-best-practices/references/rendering-performance.md +230 -0
- package/.claude/skills/react-best-practices/references/rerender-optimization.md +214 -0
- package/.claude/skills/react-best-practices/references/server-performance.md +182 -0
- package/.claude/skills/security-audit/SKILL.md +226 -0
- package/.claude/skills/shared-references/advanced-debugging-techniques.md +186 -0
- package/.claude/skills/shared-references/code-quality-checklist.md +218 -0
- package/.claude/skills/shared-references/code-review-efficiency-guide.md +125 -0
- package/.claude/skills/shared-references/mcp-dependency-compatibility-protocol.md +276 -0
- package/.claude/skills/shared-references/skill-call-graph.md +230 -0
- package/.claude/skills/shared-references/skill-orchestration-protocol.md +281 -0
- package/.claude/skills/shared-references/subagent-dispatch-templates.md +199 -0
- package/.claude/skills/skill-expert-skills/LICENSE.txt +204 -0
- package/.claude/skills/skill-expert-skills/QUICK_NAVIGATION.md +374 -0
- package/.claude/skills/skill-expert-skills/SKILL.md +247 -0
- package/.claude/skills/skill-expert-skills/docs/_index.md +91 -0
- package/.claude/skills/skill-expert-skills/references/deep-research-methodology.md +389 -0
- package/.claude/skills/skill-expert-skills/references/docs-generation-workflow.md +398 -0
- package/.claude/skills/skill-expert-skills/references/domain-expertise-protocol.md +343 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/_index.md +54 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/backend-expertise.md +517 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/bug-fixing-expertise.md +363 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/code-review-expertise.md +392 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/frontend-expertise.md +410 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge-template.md +503 -0
- package/.claude/skills/skill-expert-skills/references/examples.md +782 -0
- package/.claude/skills/skill-expert-skills/references/integration-examples.md +655 -0
- package/.claude/skills/skill-expert-skills/references/knowledge-validation-checklist.md +246 -0
- package/.claude/skills/skill-expert-skills/references/latest-knowledge-acquisition.md +461 -0
- package/.claude/skills/skill-expert-skills/references/mcp-tools-guide.md +439 -0
- package/.claude/skills/skill-expert-skills/references/official-best-practices.md +616 -0
- package/.claude/skills/skill-expert-skills/references/patterns.md +218 -0
- package/.claude/skills/skill-expert-skills/references/plugin-skills-guide.md +432 -0
- package/.claude/skills/skill-expert-skills/references/requirement-elicitation-protocol.md +290 -0
- package/.claude/skills/skill-expert-skills/references/skill-creator-SKILL.md +353 -0
- package/.claude/skills/skill-expert-skills/references/skill-templates.md +583 -0
- package/.claude/skills/skill-expert-skills/references/skills-knowledge-base.md +561 -0
- package/.claude/skills/skill-expert-skills/references/tools-guide.md +379 -0
- package/.claude/skills/skill-expert-skills/references/troubleshooting.md +378 -0
- package/.claude/skills/skill-expert-skills/references/universality-guide.md +205 -0
- package/.claude/skills/skill-expert-skills/references/writing-style-guide.md +466 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/quick_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/universal_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/analyze_trigger.py +425 -0
- package/.claude/skills/skill-expert-skills/scripts/diff_with_official.py +188 -0
- package/.claude/skills/skill-expert-skills/scripts/init_skill.py +349 -0
- package/.claude/skills/skill-expert-skills/scripts/package_skill.py +156 -0
- package/.claude/skills/skill-expert-skills/scripts/quick_validate.py +493 -0
- package/.claude/skills/skill-expert-skills/scripts/requirements.txt +2 -0
- package/.claude/skills/skill-expert-skills/scripts/universal_validate.py +182 -0
- package/.claude/skills/skill-expert-skills/scripts/upgrade_skill.py +431 -0
- package/.claude/skills/subagent-driven-development/SKILL.md +268 -0
- package/.claude/skills/test-driven-development/SKILL.md +246 -0
- package/.claude/skills/test-driven-development/references/testing-anti-patterns.md +192 -0
- package/.claude/skills/using-git-worktrees/SKILL.md +266 -0
- package/.claude/skills/using-skillstack/SKILL.md +127 -0
- package/.claude/skills/vercel-deploy/SKILL.md +166 -0
- package/.claude/skills/vercel-deploy/scripts/deploy.sh +249 -0
- package/.claude/skills/verification-before-completion/SKILL.md +305 -0
- package/.claude/skills/writing-plans/SKILL.md +259 -0
- package/README.md +69 -0
- package/bin/cli.js +468 -0
- package/lib/init.js +333 -0
- package/package.json +29 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enhanced skill validation script with quality diagnostics
|
|
4
|
+
Features:
|
|
5
|
+
- Structure validation (frontmatter, naming, etc.)
|
|
6
|
+
- Conciseness check (< 500 lines recommended, < 800 lines max)
|
|
7
|
+
- Quality scoring (0-100)
|
|
8
|
+
- Actionable recommendations
|
|
9
|
+
- Resource statistics (references, scripts, assets)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List, Tuple, Any
|
|
18
|
+
|
|
19
|
+
# Configure stdout/stderr encoding for Windows
|
|
20
|
+
def _configure_stdio() -> None:
|
|
21
|
+
"""Ensure UTF-8 encoding for stdout/stderr on Windows"""
|
|
22
|
+
import sys
|
|
23
|
+
for stream in (sys.stdout, sys.stderr):
|
|
24
|
+
try:
|
|
25
|
+
stream.reconfigure(encoding='utf-8', errors='replace')
|
|
26
|
+
except Exception:
|
|
27
|
+
# Some environments don't support reconfigure()
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
_configure_stdio()
|
|
31
|
+
|
|
32
|
+
# Optional dependency: PyYAML
|
|
33
|
+
try:
|
|
34
|
+
import yaml
|
|
35
|
+
except ImportError: # pragma: no cover
|
|
36
|
+
yaml = None
|
|
37
|
+
|
|
38
|
+
# Conciseness thresholds for SKILL.md
|
|
39
|
+
SKILL_LINES_EXCELLENT = 200 # Excellent: very concise
|
|
40
|
+
SKILL_LINES_GOOD = 350 # Good: reasonably concise
|
|
41
|
+
SKILL_LINES_WARN = 500 # Soft limit: trigger warning
|
|
42
|
+
SKILL_LINES_ERROR = 800 # Hard limit: trigger error
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SkillQualityReport:
|
|
46
|
+
"""Quality report for a skill"""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.valid = True
|
|
50
|
+
self.errors: List[str] = []
|
|
51
|
+
self.warnings: List[str] = []
|
|
52
|
+
self.recommendations: List[str] = []
|
|
53
|
+
self.metrics: Dict[str, Any] = {}
|
|
54
|
+
self.score = 0
|
|
55
|
+
|
|
56
|
+
def add_error(self, message: str):
|
|
57
|
+
self.valid = False
|
|
58
|
+
self.errors.append(f"❌ ERROR: {message}")
|
|
59
|
+
|
|
60
|
+
def add_warning(self, message: str):
|
|
61
|
+
self.warnings.append(f"⚠️ WARNING: {message}")
|
|
62
|
+
|
|
63
|
+
def add_recommendation(self, message: str):
|
|
64
|
+
self.recommendations.append(f"💡 RECOMMENDATION: {message}")
|
|
65
|
+
|
|
66
|
+
def format_output(self, verbose: bool = False) -> str:
|
|
67
|
+
"""Format report as human-readable text"""
|
|
68
|
+
lines = []
|
|
69
|
+
|
|
70
|
+
# Header
|
|
71
|
+
if self.valid:
|
|
72
|
+
lines.append("=" * 70)
|
|
73
|
+
lines.append("✅ SKILL VALIDATION PASSED")
|
|
74
|
+
lines.append("=" * 70)
|
|
75
|
+
else:
|
|
76
|
+
lines.append("=" * 70)
|
|
77
|
+
lines.append("❌ SKILL VALIDATION FAILED")
|
|
78
|
+
lines.append("=" * 70)
|
|
79
|
+
|
|
80
|
+
lines.append("")
|
|
81
|
+
|
|
82
|
+
# Quality Score
|
|
83
|
+
if self.valid:
|
|
84
|
+
score_bar = self._get_score_bar(self.score)
|
|
85
|
+
score_label = self._get_score_label(self.score)
|
|
86
|
+
lines.append(f"📊 Quality Score: {self.score}/100 {score_bar} ({score_label})")
|
|
87
|
+
lines.append("")
|
|
88
|
+
|
|
89
|
+
# Metrics
|
|
90
|
+
if self.metrics and verbose:
|
|
91
|
+
lines.append("📈 Metrics:")
|
|
92
|
+
for key, value in self.metrics.items():
|
|
93
|
+
lines.append(f" • {key}: {value}")
|
|
94
|
+
lines.append("")
|
|
95
|
+
|
|
96
|
+
# Errors
|
|
97
|
+
if self.errors:
|
|
98
|
+
lines.append("🔴 Errors (Must Fix):")
|
|
99
|
+
for error in self.errors:
|
|
100
|
+
lines.append(f" {error}")
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
# Warnings
|
|
104
|
+
if self.warnings:
|
|
105
|
+
lines.append("🟡 Warnings (Should Fix):")
|
|
106
|
+
for warning in self.warnings:
|
|
107
|
+
lines.append(f" {warning}")
|
|
108
|
+
lines.append("")
|
|
109
|
+
|
|
110
|
+
# Recommendations
|
|
111
|
+
if self.recommendations:
|
|
112
|
+
lines.append("💡 Recommendations (Nice to Have):")
|
|
113
|
+
for rec in self.recommendations:
|
|
114
|
+
lines.append(f" {rec}")
|
|
115
|
+
lines.append("")
|
|
116
|
+
|
|
117
|
+
# Summary
|
|
118
|
+
if self.valid:
|
|
119
|
+
lines.append("=" * 70)
|
|
120
|
+
lines.append("✨ Summary:")
|
|
121
|
+
lines.append(f" Errors: {len(self.errors)}")
|
|
122
|
+
lines.append(f" Warnings: {len(self.warnings)}")
|
|
123
|
+
lines.append(f" Recommendations: {len(self.recommendations)}")
|
|
124
|
+
if self.score >= 90:
|
|
125
|
+
lines.append(" Status: 🏆 Excellent - Production Ready")
|
|
126
|
+
elif self.score >= 75:
|
|
127
|
+
lines.append(" Status: ✅ Good - Minor improvements suggested")
|
|
128
|
+
elif self.score >= 60:
|
|
129
|
+
lines.append(" Status: ⚠️ Fair - Consider improvements")
|
|
130
|
+
else:
|
|
131
|
+
lines.append(" Status: ⚠️ Needs Improvement")
|
|
132
|
+
lines.append("=" * 70)
|
|
133
|
+
|
|
134
|
+
return "\n".join(lines)
|
|
135
|
+
|
|
136
|
+
def _get_score_bar(self, score: int) -> str:
|
|
137
|
+
"""Generate visual score bar"""
|
|
138
|
+
filled = int(score / 10)
|
|
139
|
+
empty = 10 - filled
|
|
140
|
+
return "█" * filled + "░" * empty
|
|
141
|
+
|
|
142
|
+
def _get_score_label(self, score: int) -> str:
|
|
143
|
+
"""Get score label"""
|
|
144
|
+
if score >= 90:
|
|
145
|
+
return "Excellent"
|
|
146
|
+
elif score >= 75:
|
|
147
|
+
return "Good"
|
|
148
|
+
elif score >= 60:
|
|
149
|
+
return "Fair"
|
|
150
|
+
else:
|
|
151
|
+
return "Needs Improvement"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def count_resources(skill_path: Path) -> Dict[str, int]:
|
|
155
|
+
"""Count resource files in references/, scripts/, assets/"""
|
|
156
|
+
counts = {
|
|
157
|
+
'references': 0,
|
|
158
|
+
'scripts': 0,
|
|
159
|
+
'assets': 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for dir_name in ['references', 'scripts', 'assets']:
|
|
163
|
+
dir_path = skill_path / dir_name
|
|
164
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
165
|
+
# Count non-hidden files (excluding __pycache__, etc.)
|
|
166
|
+
counts[dir_name] = len([
|
|
167
|
+
f for f in dir_path.rglob('*')
|
|
168
|
+
if f.is_file()
|
|
169
|
+
and not f.name.startswith('.')
|
|
170
|
+
and '__pycache__' not in str(f)
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
return counts
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def check_problematic_special_chars(content: str) -> List[str]:
|
|
177
|
+
"""Check for special characters in backticks that may cause parsing issues"""
|
|
178
|
+
issues = []
|
|
179
|
+
|
|
180
|
+
# Pattern to find single special characters in backticks
|
|
181
|
+
# These can be misinterpreted as bash commands by Claude Code
|
|
182
|
+
problematic_patterns = [
|
|
183
|
+
(r'`!`', 'exclamation mark (!)', 'non-null assertion'),
|
|
184
|
+
(r'`\$`', 'dollar sign ($)', 'variable prefix'),
|
|
185
|
+
(r'`#`', 'hash (#)', 'comment marker'),
|
|
186
|
+
(r'`\|`', 'pipe (|)', 'vertical bar'),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
for pattern, char_name, replacement in problematic_patterns:
|
|
190
|
+
if re.search(pattern, content):
|
|
191
|
+
issues.append(
|
|
192
|
+
f"Found {char_name} in backticks which may cause parsing errors. "
|
|
193
|
+
f"Use '{replacement}' instead."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return issues
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def analyze_description_quality(description: str) -> Tuple[int, List[str]]:
|
|
200
|
+
"""Analyze description quality and return score + suggestions"""
|
|
201
|
+
score = 0
|
|
202
|
+
suggestions = []
|
|
203
|
+
|
|
204
|
+
# Check length (should be descriptive but not too long)
|
|
205
|
+
desc_len = len(description)
|
|
206
|
+
if 100 <= desc_len <= 800:
|
|
207
|
+
score += 20
|
|
208
|
+
elif desc_len < 100:
|
|
209
|
+
suggestions.append("Description is quite short. Consider adding more trigger scenarios.")
|
|
210
|
+
elif desc_len > 800:
|
|
211
|
+
suggestions.append("Description is very long. Consider condensing to key trigger scenarios.")
|
|
212
|
+
else:
|
|
213
|
+
score += 10
|
|
214
|
+
|
|
215
|
+
# Check for "Use when:" section (good practice)
|
|
216
|
+
if "use when" in description.lower() or "trigger" in description.lower():
|
|
217
|
+
score += 25
|
|
218
|
+
else:
|
|
219
|
+
suggestions.append("Add 'Use when:' section to clarify trigger scenarios.")
|
|
220
|
+
|
|
221
|
+
# Check for output description
|
|
222
|
+
if any(keyword in description.lower() for keyword in ['output', 'produce', 'generate', 'create']):
|
|
223
|
+
score += 15
|
|
224
|
+
else:
|
|
225
|
+
suggestions.append("Describe what the skill outputs or produces.")
|
|
226
|
+
|
|
227
|
+
# Check for specific examples/keywords
|
|
228
|
+
if description.count('\n-') >= 3 or description.count('\n*') >= 3:
|
|
229
|
+
score += 20 # Has bullet points (likely detailed)
|
|
230
|
+
else:
|
|
231
|
+
suggestions.append("Use bullet points to list specific trigger scenarios (3-5 recommended).")
|
|
232
|
+
|
|
233
|
+
# Check for "Not for:" section (good boundary setting)
|
|
234
|
+
if "not for" in description.lower():
|
|
235
|
+
score += 20
|
|
236
|
+
else:
|
|
237
|
+
suggestions.append("Consider adding 'Not for:' section to set clear boundaries.")
|
|
238
|
+
|
|
239
|
+
return score, suggestions
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def calculate_quality_score(report: SkillQualityReport,
|
|
243
|
+
skill_path: Path,
|
|
244
|
+
frontmatter: dict,
|
|
245
|
+
total_lines: int) -> int:
|
|
246
|
+
"""Calculate overall quality score (0-100)"""
|
|
247
|
+
score = 0
|
|
248
|
+
|
|
249
|
+
# 1. Conciseness Score (30 points)
|
|
250
|
+
if total_lines <= SKILL_LINES_EXCELLENT:
|
|
251
|
+
score += 30
|
|
252
|
+
report.metrics['conciseness'] = f"Excellent ({total_lines} lines, {int(total_lines/SKILL_LINES_WARN*100)}% of threshold)"
|
|
253
|
+
elif total_lines <= SKILL_LINES_GOOD:
|
|
254
|
+
score += 25
|
|
255
|
+
report.metrics['conciseness'] = f"Very Good ({total_lines} lines, {int(total_lines/SKILL_LINES_WARN*100)}% of threshold)"
|
|
256
|
+
elif total_lines < SKILL_LINES_WARN:
|
|
257
|
+
score += 20
|
|
258
|
+
report.metrics['conciseness'] = f"Good ({total_lines} lines, {int(total_lines/SKILL_LINES_WARN*100)}% of threshold)"
|
|
259
|
+
elif total_lines < SKILL_LINES_ERROR:
|
|
260
|
+
score += 10
|
|
261
|
+
report.metrics['conciseness'] = f"Acceptable ({total_lines} lines, {int(total_lines/SKILL_LINES_WARN*100)}% of threshold)"
|
|
262
|
+
report.add_warning(f"SKILL.md is approaching length limit ({total_lines}/{SKILL_LINES_ERROR} lines)")
|
|
263
|
+
else:
|
|
264
|
+
score += 0
|
|
265
|
+
report.metrics['conciseness'] = f"Too Long ({total_lines} lines exceeds {SKILL_LINES_ERROR} limit)"
|
|
266
|
+
|
|
267
|
+
# 2. Description Quality (25 points)
|
|
268
|
+
desc_score, desc_suggestions = analyze_description_quality(frontmatter.get('description', ''))
|
|
269
|
+
score += desc_score
|
|
270
|
+
report.metrics['description_quality'] = f"{desc_score}/25 points"
|
|
271
|
+
for suggestion in desc_suggestions:
|
|
272
|
+
report.add_recommendation(suggestion)
|
|
273
|
+
|
|
274
|
+
# 3. Best Practices (25 points)
|
|
275
|
+
best_practices_score = 0
|
|
276
|
+
|
|
277
|
+
# Has allowed-tools (5 points)
|
|
278
|
+
if 'allowed-tools' in frontmatter:
|
|
279
|
+
best_practices_score += 5
|
|
280
|
+
else:
|
|
281
|
+
report.add_warning("'allowed-tools' is missing. Recommend specifying minimal tools for security.")
|
|
282
|
+
|
|
283
|
+
# Has references/ directory (10 points)
|
|
284
|
+
resources = count_resources(skill_path)
|
|
285
|
+
if resources['references'] > 0:
|
|
286
|
+
best_practices_score += 10
|
|
287
|
+
report.metrics['references_count'] = resources['references']
|
|
288
|
+
else:
|
|
289
|
+
report.add_recommendation("Consider adding references/ for detailed knowledge/checklists.")
|
|
290
|
+
|
|
291
|
+
# Has scripts/ directory (5 points)
|
|
292
|
+
if resources['scripts'] > 0:
|
|
293
|
+
best_practices_score += 5
|
|
294
|
+
report.metrics['scripts_count'] = resources['scripts']
|
|
295
|
+
|
|
296
|
+
# Directory name matches skill name (5 points)
|
|
297
|
+
if skill_path.name == frontmatter.get('name', ''):
|
|
298
|
+
best_practices_score += 5
|
|
299
|
+
|
|
300
|
+
score += best_practices_score
|
|
301
|
+
report.metrics['best_practices'] = f"{best_practices_score}/25 points"
|
|
302
|
+
|
|
303
|
+
# 4. Structure Quality (20 points)
|
|
304
|
+
structure_score = 0
|
|
305
|
+
|
|
306
|
+
# Read SKILL.md content for structure analysis
|
|
307
|
+
skill_md = skill_path / 'SKILL.md'
|
|
308
|
+
content = skill_md.read_text(encoding="utf-8-sig").lower()
|
|
309
|
+
|
|
310
|
+
# Has decision tree or workflow (8 points)
|
|
311
|
+
if 'decision tree' in content or 'workflow' in content or '##' in content:
|
|
312
|
+
structure_score += 8
|
|
313
|
+
else:
|
|
314
|
+
report.add_recommendation("Add a decision tree or workflow section for clarity.")
|
|
315
|
+
|
|
316
|
+
# Has output contract/format (7 points)
|
|
317
|
+
if 'output' in content and ('contract' in content or 'format' in content or 'structure' in content):
|
|
318
|
+
structure_score += 7
|
|
319
|
+
else:
|
|
320
|
+
report.add_recommendation("Define clear output contract/format for predictability.")
|
|
321
|
+
|
|
322
|
+
# Has troubleshooting/FAQ (5 points)
|
|
323
|
+
if 'troubleshoot' in content or 'faq' in content or 'common' in content:
|
|
324
|
+
structure_score += 5
|
|
325
|
+
|
|
326
|
+
score += structure_score
|
|
327
|
+
report.metrics['structure'] = f"{structure_score}/20 points"
|
|
328
|
+
|
|
329
|
+
return min(score, 100) # Cap at 100
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def validate_skill(skill_path: str, verbose: bool = False) -> Tuple[bool, str]:
|
|
333
|
+
"""Enhanced validation with quality diagnostics"""
|
|
334
|
+
skill_path = Path(skill_path)
|
|
335
|
+
report = SkillQualityReport()
|
|
336
|
+
|
|
337
|
+
# Check SKILL.md exists
|
|
338
|
+
skill_md = skill_path / 'SKILL.md'
|
|
339
|
+
if not skill_md.exists():
|
|
340
|
+
report.add_error("SKILL.md not found")
|
|
341
|
+
return False, report.format_output(verbose)
|
|
342
|
+
|
|
343
|
+
# Read and validate encoding
|
|
344
|
+
try:
|
|
345
|
+
content = skill_md.read_text(encoding="utf-8-sig")
|
|
346
|
+
except UnicodeDecodeError:
|
|
347
|
+
report.add_error("SKILL.md must be UTF-8 encoded (optionally with BOM)")
|
|
348
|
+
return False, report.format_output(verbose)
|
|
349
|
+
|
|
350
|
+
# Check frontmatter
|
|
351
|
+
if not content.startswith('---'):
|
|
352
|
+
report.add_error("No YAML frontmatter found")
|
|
353
|
+
return False, report.format_output(verbose)
|
|
354
|
+
|
|
355
|
+
# Extract frontmatter
|
|
356
|
+
match = re.match(r'^---\r?\n(.*?)\r?\n---', content, re.DOTALL)
|
|
357
|
+
if not match:
|
|
358
|
+
report.add_error("Invalid frontmatter format")
|
|
359
|
+
return False, report.format_output(verbose)
|
|
360
|
+
|
|
361
|
+
frontmatter_text = match.group(1)
|
|
362
|
+
|
|
363
|
+
# Parse YAML frontmatter
|
|
364
|
+
if yaml is None:
|
|
365
|
+
report.add_error(
|
|
366
|
+
"Missing dependency: PyYAML. "
|
|
367
|
+
"Install with: python -m pip install -r .claude/skills/skill-expert-skills/scripts/requirements.txt"
|
|
368
|
+
)
|
|
369
|
+
return False, report.format_output(verbose)
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
373
|
+
if not isinstance(frontmatter, dict):
|
|
374
|
+
report.add_error("Frontmatter must be a YAML dictionary")
|
|
375
|
+
return False, report.format_output(verbose)
|
|
376
|
+
except yaml.YAMLError as e:
|
|
377
|
+
report.add_error(f"Invalid YAML in frontmatter: {e}")
|
|
378
|
+
return False, report.format_output(verbose)
|
|
379
|
+
|
|
380
|
+
# Validate frontmatter fields
|
|
381
|
+
ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
|
|
382
|
+
unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
|
|
383
|
+
if unexpected_keys:
|
|
384
|
+
report.add_error(
|
|
385
|
+
f"Unexpected key(s) in frontmatter: {', '.join(sorted(unexpected_keys))}. "
|
|
386
|
+
f"Allowed: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Check required fields
|
|
390
|
+
if 'name' not in frontmatter:
|
|
391
|
+
report.add_error("Missing 'name' in frontmatter")
|
|
392
|
+
if 'description' not in frontmatter:
|
|
393
|
+
report.add_error("Missing 'description' in frontmatter")
|
|
394
|
+
|
|
395
|
+
# If errors so far, return early
|
|
396
|
+
if report.errors:
|
|
397
|
+
return False, report.format_output(verbose)
|
|
398
|
+
|
|
399
|
+
# Validate name
|
|
400
|
+
name = frontmatter.get('name', '').strip()
|
|
401
|
+
if name:
|
|
402
|
+
if not isinstance(name, str):
|
|
403
|
+
report.add_error(f"Name must be a string, got {type(name).__name__}")
|
|
404
|
+
elif not re.match(r'^[a-z0-9-]+$', name):
|
|
405
|
+
report.add_error(f"Name '{name}' should be hyphen-case (lowercase, digits, hyphens only)")
|
|
406
|
+
elif name.startswith('-') or name.endswith('-') or '--' in name:
|
|
407
|
+
report.add_error(f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens")
|
|
408
|
+
elif len(name) > 64:
|
|
409
|
+
report.add_error(f"Name is too long ({len(name)} chars). Max 64 chars.")
|
|
410
|
+
|
|
411
|
+
# Directory name check
|
|
412
|
+
if skill_path.name != name:
|
|
413
|
+
report.add_error(f"Directory name '{skill_path.name}' must match frontmatter name '{name}'")
|
|
414
|
+
|
|
415
|
+
# Validate description
|
|
416
|
+
description = frontmatter.get('description', '').strip()
|
|
417
|
+
if description:
|
|
418
|
+
if not isinstance(description, str):
|
|
419
|
+
report.add_error(f"Description must be a string, got {type(description).__name__}")
|
|
420
|
+
elif '<' in description or '>' in description:
|
|
421
|
+
report.add_error("Description cannot contain angle brackets (< or >)")
|
|
422
|
+
elif len(description) > 1024:
|
|
423
|
+
report.add_error(f"Description is too long ({len(description)} chars). Max 1024 chars.")
|
|
424
|
+
|
|
425
|
+
# If errors, return early
|
|
426
|
+
if report.errors:
|
|
427
|
+
return False, report.format_output(verbose)
|
|
428
|
+
|
|
429
|
+
# Calculate metrics
|
|
430
|
+
total_lines = content.count('\n') + 1
|
|
431
|
+
report.metrics['skill_md_lines'] = total_lines
|
|
432
|
+
|
|
433
|
+
# Count resources
|
|
434
|
+
resources = count_resources(skill_path)
|
|
435
|
+
|
|
436
|
+
# Hard error: too long
|
|
437
|
+
if total_lines >= SKILL_LINES_ERROR:
|
|
438
|
+
report.add_error(
|
|
439
|
+
f"SKILL.md is too long ({total_lines} lines, max {SKILL_LINES_ERROR}). "
|
|
440
|
+
f"Move detailed content to references/"
|
|
441
|
+
)
|
|
442
|
+
return False, report.format_output(verbose)
|
|
443
|
+
|
|
444
|
+
# Check for problematic special characters in backticks
|
|
445
|
+
problematic_chars = check_problematic_special_chars(content)
|
|
446
|
+
for char_issue in problematic_chars:
|
|
447
|
+
report.add_warning(char_issue)
|
|
448
|
+
|
|
449
|
+
# Calculate quality score
|
|
450
|
+
report.score = calculate_quality_score(report, skill_path, frontmatter, total_lines)
|
|
451
|
+
|
|
452
|
+
# Add resource-based recommendations
|
|
453
|
+
if resources['references'] == 0:
|
|
454
|
+
report.add_recommendation("Add references/ directory for detailed documentation")
|
|
455
|
+
elif resources['references'] >= 10:
|
|
456
|
+
report.add_recommendation(f"Many reference files ({resources['references']}). Ensure good navigation in SKILL.md")
|
|
457
|
+
|
|
458
|
+
if resources['scripts'] == 0 and total_lines > 300:
|
|
459
|
+
report.add_recommendation("Consider extracting repetitive logic into scripts/")
|
|
460
|
+
|
|
461
|
+
# Add score-based recommendations
|
|
462
|
+
if report.score < 90:
|
|
463
|
+
if report.score < 75:
|
|
464
|
+
report.add_recommendation("Review examples.md in references/ for best practices")
|
|
465
|
+
report.add_recommendation("Run universal_validate.py to check portability")
|
|
466
|
+
|
|
467
|
+
return report.valid, report.format_output(verbose)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
if __name__ == "__main__":
|
|
471
|
+
# Parse arguments
|
|
472
|
+
verbose = False
|
|
473
|
+
skill_dir = None
|
|
474
|
+
|
|
475
|
+
for arg in sys.argv[1:]:
|
|
476
|
+
if arg in ['-v', '--verbose']:
|
|
477
|
+
verbose = True
|
|
478
|
+
elif arg in ['-h', '--help']:
|
|
479
|
+
print("Usage: python quick_validate.py <skill_directory> [--verbose|-v]")
|
|
480
|
+
print("\nOptions:")
|
|
481
|
+
print(" -v, --verbose Show detailed metrics")
|
|
482
|
+
print(" -h, --help Show this help message")
|
|
483
|
+
sys.exit(0)
|
|
484
|
+
else:
|
|
485
|
+
skill_dir = arg
|
|
486
|
+
|
|
487
|
+
if not skill_dir:
|
|
488
|
+
print("Usage: python quick_validate.py <skill_directory> [--verbose|-v]")
|
|
489
|
+
sys.exit(1)
|
|
490
|
+
|
|
491
|
+
valid, message = validate_skill(skill_dir, verbose=verbose)
|
|
492
|
+
print(message)
|
|
493
|
+
sys.exit(0 if valid else 1)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Universal (cross-project) validation for Skills content.
|
|
4
|
+
|
|
5
|
+
This script enforces a *high-confidence* subset of the "cross-project universal" rule:
|
|
6
|
+
- It flags obvious project-specific fingerprints like absolute user paths (Windows/macOS/Linux).
|
|
7
|
+
|
|
8
|
+
It intentionally avoids over-aggressive heuristics that would create many false positives.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python universal_validate.py <path/to/skill-folder>
|
|
12
|
+
Exit codes:
|
|
13
|
+
0: no issues found
|
|
14
|
+
1: violations found
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Iterable, List, Tuple
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _configure_stdio() -> None:
|
|
27
|
+
"""
|
|
28
|
+
Avoid UnicodeEncodeError on Windows consoles (e.g., GBK) by ensuring
|
|
29
|
+
unencodable characters are safely replaced instead of crashing.
|
|
30
|
+
"""
|
|
31
|
+
for stream in (sys.stdout, sys.stderr):
|
|
32
|
+
try:
|
|
33
|
+
stream.reconfigure(errors="replace")
|
|
34
|
+
except Exception:
|
|
35
|
+
# Some environments replace stdio with objects that don't support reconfigure().
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_configure_stdio()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class Finding:
|
|
44
|
+
file: Path
|
|
45
|
+
line_no: int
|
|
46
|
+
kind: str
|
|
47
|
+
pattern_name: str
|
|
48
|
+
excerpt: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def iter_text_files(skill_dir: Path) -> Iterable[Path]:
|
|
52
|
+
# Include Skill entry and common text/reference files.
|
|
53
|
+
include_names = {"SKILL.md"}
|
|
54
|
+
include_suffixes = {".md", ".txt", ".json", ".yaml", ".yml"}
|
|
55
|
+
|
|
56
|
+
for p in skill_dir.rglob("*"):
|
|
57
|
+
if not p.is_file():
|
|
58
|
+
continue
|
|
59
|
+
if p.name in include_names or p.suffix.lower() in include_suffixes:
|
|
60
|
+
# Skip huge binary-ish files if any mistakenly match.
|
|
61
|
+
# (We only include common text suffixes; this is just a final guard.)
|
|
62
|
+
yield p
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_text_lines(path: Path) -> List[str]:
|
|
66
|
+
# Use UTF-8 with BOM support. If a file isn't UTF-8, treat as a violation because
|
|
67
|
+
# cross-platform skills should be portable and readable.
|
|
68
|
+
try:
|
|
69
|
+
return path.read_text(encoding="utf-8-sig").splitlines()
|
|
70
|
+
except UnicodeDecodeError:
|
|
71
|
+
return [
|
|
72
|
+
"UNIVERSAL_VALIDATE_ERROR: file is not UTF-8 decodable; use UTF-8 encoding for portability."
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def scan_lines(lines: List[str]) -> List[Tuple[int, str]]:
|
|
77
|
+
return [(i + 1, line) for i, line in enumerate(lines)]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
ERROR_PATTERNS: List[Tuple[str, re.Pattern[str]]] = [
|
|
81
|
+
# Windows absolute paths: C:\Users\name\..., D:\repo\...
|
|
82
|
+
("windows_drive_path", re.compile(r"(?i)\b[a-z]:\\[^ \t\r\n]+")),
|
|
83
|
+
# Windows UNC paths: \\server\share\...
|
|
84
|
+
("windows_unc_path", re.compile(r"\\\\[^ \t\r\n]+")),
|
|
85
|
+
# macOS/Linux user home paths: /Users/name/... or /home/name/...
|
|
86
|
+
("posix_user_home_path", re.compile(r"/(?:Users|home)/[A-Za-z0-9._-]+/[^ \t\r\n]*")),
|
|
87
|
+
# Tilde home paths: ~/...
|
|
88
|
+
("tilde_home_path", re.compile(r"~\/[^ \t\r\n]+")),
|
|
89
|
+
# file:// URIs (often embed local paths)
|
|
90
|
+
("file_uri", re.compile(r"(?i)\bfile:///[^\s]+")),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def is_placeholder_match(match_text: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Treat obvious placeholder examples as non-violations.
|
|
96
|
+
|
|
97
|
+
We allow patterns like `C:\\...` or `/Users/...` used as generic examples in docs.
|
|
98
|
+
This keeps the validator useful while avoiding self-failing documentation.
|
|
99
|
+
"""
|
|
100
|
+
return "..." in match_text
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def validate_universal(skill_dir: Path) -> Tuple[bool, List[Finding]]:
|
|
104
|
+
findings: List[Finding] = []
|
|
105
|
+
|
|
106
|
+
for file_path in iter_text_files(skill_dir):
|
|
107
|
+
# Skip this skill's own LICENSE file — it is legal text and not part of "skill logic".
|
|
108
|
+
if file_path.name.lower() == "license.txt":
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
lines = read_text_lines(file_path)
|
|
112
|
+
# If file is not decodable, treat as ERROR at line 1.
|
|
113
|
+
if lines and lines[0].startswith("UNIVERSAL_VALIDATE_ERROR:"):
|
|
114
|
+
findings.append(
|
|
115
|
+
Finding(
|
|
116
|
+
file=file_path,
|
|
117
|
+
line_no=1,
|
|
118
|
+
kind="ERROR",
|
|
119
|
+
pattern_name="non_utf8_file",
|
|
120
|
+
excerpt=lines[0],
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
for line_no, line in scan_lines(lines):
|
|
126
|
+
for pattern_name, pattern in ERROR_PATTERNS:
|
|
127
|
+
m = pattern.search(line)
|
|
128
|
+
if not m:
|
|
129
|
+
continue
|
|
130
|
+
if is_placeholder_match(m.group(0)):
|
|
131
|
+
continue
|
|
132
|
+
excerpt = line.strip()
|
|
133
|
+
if len(excerpt) > 240:
|
|
134
|
+
excerpt = excerpt[:237] + "..."
|
|
135
|
+
findings.append(
|
|
136
|
+
Finding(
|
|
137
|
+
file=file_path,
|
|
138
|
+
line_no=line_no,
|
|
139
|
+
kind="ERROR",
|
|
140
|
+
pattern_name=pattern_name,
|
|
141
|
+
excerpt=excerpt,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
ok = not any(f.kind == "ERROR" for f in findings)
|
|
146
|
+
return ok, findings
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main() -> int:
|
|
150
|
+
if len(sys.argv) != 2:
|
|
151
|
+
print("Usage: python universal_validate.py <skill_directory>")
|
|
152
|
+
return 1
|
|
153
|
+
|
|
154
|
+
skill_dir = Path(sys.argv[1]).resolve()
|
|
155
|
+
if not skill_dir.exists() or not skill_dir.is_dir():
|
|
156
|
+
print(f"[ERROR] skill directory not found or not a directory: {skill_dir}")
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
ok, findings = validate_universal(skill_dir)
|
|
160
|
+
if ok:
|
|
161
|
+
print("[OK] Universal validation passed (no high-confidence project-specific fingerprints found).")
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
print("[FAIL] Universal validation failed:")
|
|
165
|
+
for f in findings:
|
|
166
|
+
rel = f.file
|
|
167
|
+
try:
|
|
168
|
+
rel = f.file.relative_to(Path.cwd())
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
print(f"- {f.kind} {f.pattern_name} at {rel}:{f.line_no}: {f.excerpt}")
|
|
172
|
+
|
|
173
|
+
print("\nFix guidance:")
|
|
174
|
+
print("- Remove absolute user paths (C:\\..., /Users/..., /home/..., ~/...).")
|
|
175
|
+
print("- Replace them with cross-project, relative, or conceptual descriptions (avoid project placeholders).")
|
|
176
|
+
return 1
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
raise SystemExit(main())
|
|
181
|
+
|
|
182
|
+
|