@prismer/runtime 2.0.7 → 2.2.55
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/CHANGELOG.md +3631 -0
- package/README.md +34 -12
- package/apc/skills/FIELD-DICTIONARY.md +111 -0
- package/apc/skills/bug-reproduce/SKILL.md +150 -0
- package/apc/skills/bug-reproduce/skill.json +96 -0
- package/apc/skills/code-review/SKILL.md +198 -0
- package/apc/skills/code-review/skill.json +124 -0
- package/apc/skills/design-review/SKILL.md +122 -0
- package/apc/skills/design-review/skill.json +88 -0
- package/apc/skills/doc-sync/SKILL.md +168 -0
- package/apc/skills/doc-sync/skill.json +81 -0
- package/apc/skills/env-doctor/SKILL.md +194 -0
- package/apc/skills/env-doctor/skill.json +209 -0
- package/apc/skills/git-ops/SKILL.md +189 -0
- package/apc/skills/git-ops/skill.json +94 -0
- package/apc/skills/impact-trace/SKILL.md +168 -0
- package/apc/skills/impact-trace/skill.json +104 -0
- package/apc/skills/observability/SKILL.md +195 -0
- package/apc/skills/observability/skill.json +116 -0
- package/apc/skills/release-db-config-sync/SKILL.md +186 -0
- package/apc/skills/release-db-config-sync/skill.json +109 -0
- package/apc/skills/release-ota-promote/SKILL.md +195 -0
- package/apc/skills/release-ota-promote/skill.json +176 -0
- package/apc/skills/release-preflight/SKILL.md +174 -0
- package/apc/skills/release-preflight/skill.json +175 -0
- package/apc/skills/release-rollback/SKILL.md +214 -0
- package/apc/skills/release-rollback/skill.json +230 -0
- package/apc/skills/release-tag/SKILL.md +194 -0
- package/apc/skills/release-tag/skill.json +94 -0
- package/apc/skills/releasing-prod/SKILL.md +49 -0
- package/apc/skills/releasing-test/SKILL.md +135 -0
- package/apc/skills/sdk-release/SKILL.md +200 -0
- package/apc/skills/spec-intake/SKILL.md +169 -0
- package/apc/skills/spec-intake/skill.json +93 -0
- package/apc/skills/test-result-feedback/SKILL.md +239 -0
- package/apc/skills/test-result-feedback/skill.json +193 -0
- package/apc/skills/test-runner/SKILL.md +169 -0
- package/apc/skills/test-runner/skill.json +103 -0
- package/apc/skills/ui-align/SKILL.md +209 -0
- package/apc/skills/ui-align/skill.json +114 -0
- package/apc/skills/ui-canvas/SKILL.md +148 -0
- package/apc/skills/ui-canvas/skill.json +127 -0
- package/built-in-skills/agent-coordination/SKILL.md +257 -0
- package/built-in-skills/agent-meta/SKILL.md +53 -0
- package/built-in-skills/assets/SKILL.md +133 -0
- package/built-in-skills/browser-use/SKILL.md +93 -0
- package/built-in-skills/canvas-design/LICENSE.txt +202 -0
- package/built-in-skills/canvas-design/SKILL.md +157 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/built-in-skills/claim-agent-ownership/SKILL.md +255 -0
- package/built-in-skills/claude-api/LICENSE.txt +202 -0
- package/built-in-skills/claude-api/SKILL.md +325 -0
- package/built-in-skills/claude-api/csharp/claude-api.md +402 -0
- package/built-in-skills/claude-api/curl/examples.md +216 -0
- package/built-in-skills/claude-api/curl/managed-agents.md +336 -0
- package/built-in-skills/claude-api/go/claude-api.md +421 -0
- package/built-in-skills/claude-api/go/managed-agents/README.md +561 -0
- package/built-in-skills/claude-api/java/claude-api.md +432 -0
- package/built-in-skills/claude-api/java/managed-agents/README.md +442 -0
- package/built-in-skills/claude-api/php/claude-api.md +375 -0
- package/built-in-skills/claude-api/php/managed-agents/README.md +435 -0
- package/built-in-skills/claude-api/python/claude-api/README.md +420 -0
- package/built-in-skills/claude-api/python/claude-api/batches.md +185 -0
- package/built-in-skills/claude-api/python/claude-api/files-api.md +165 -0
- package/built-in-skills/claude-api/python/claude-api/streaming.md +162 -0
- package/built-in-skills/claude-api/python/claude-api/tool-use.md +590 -0
- package/built-in-skills/claude-api/python/managed-agents/README.md +332 -0
- package/built-in-skills/claude-api/ruby/claude-api.md +113 -0
- package/built-in-skills/claude-api/ruby/managed-agents/README.md +389 -0
- package/built-in-skills/claude-api/shared/agent-design.md +101 -0
- package/built-in-skills/claude-api/shared/error-codes.md +213 -0
- package/built-in-skills/claude-api/shared/live-sources.md +135 -0
- package/built-in-skills/claude-api/shared/managed-agents-api-reference.md +378 -0
- package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +209 -0
- package/built-in-skills/claude-api/shared/managed-agents-core.md +238 -0
- package/built-in-skills/claude-api/shared/managed-agents-environments.md +215 -0
- package/built-in-skills/claude-api/shared/managed-agents-events.md +195 -0
- package/built-in-skills/claude-api/shared/managed-agents-memory.md +197 -0
- package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +99 -0
- package/built-in-skills/claude-api/shared/managed-agents-onboarding.md +114 -0
- package/built-in-skills/claude-api/shared/managed-agents-outcomes.md +106 -0
- package/built-in-skills/claude-api/shared/managed-agents-overview.md +68 -0
- package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
- package/built-in-skills/claude-api/shared/managed-agents-tools.md +321 -0
- package/built-in-skills/claude-api/shared/managed-agents-webhooks.md +110 -0
- package/built-in-skills/claude-api/shared/model-migration.md +779 -0
- package/built-in-skills/claude-api/shared/models.md +121 -0
- package/built-in-skills/claude-api/shared/prompt-caching.md +171 -0
- package/built-in-skills/claude-api/shared/tool-use-concepts.md +327 -0
- package/built-in-skills/claude-api/typescript/claude-api/README.md +333 -0
- package/built-in-skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/built-in-skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/built-in-skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +527 -0
- package/built-in-skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/built-in-skills/codebase-design/DEEPENING.md +37 -0
- package/built-in-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/built-in-skills/codebase-design/LICENSE +21 -0
- package/built-in-skills/codebase-design/SKILL.md +116 -0
- package/built-in-skills/conversation-compaction/SKILL.md +114 -0
- package/built-in-skills/council-creator/SKILL.md +426 -0
- package/built-in-skills/diagnosing-bugs/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/SKILL.md +136 -0
- package/built-in-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/built-in-skills/doc-coauthoring/SKILL.md +376 -0
- package/built-in-skills/document-generation/SKILL.md +105 -0
- package/built-in-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/built-in-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/built-in-skills/domain-modeling/LICENSE +21 -0
- package/built-in-skills/domain-modeling/SKILL.md +76 -0
- package/built-in-skills/frontend-design/LICENSE.txt +177 -0
- package/built-in-skills/frontend-design/SKILL.md +43 -0
- package/built-in-skills/human-approval/SKILL.md +129 -0
- package/built-in-skills/image-generate/SKILL.md +128 -0
- package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +289 -0
- package/built-in-skills/ingest/SKILL.md +73 -0
- package/built-in-skills/internal-comms/LICENSE.txt +202 -0
- package/built-in-skills/internal-comms/SKILL.md +33 -0
- package/built-in-skills/internal-comms/examples/3p-updates.md +47 -0
- package/built-in-skills/internal-comms/examples/company-newsletter.md +65 -0
- package/built-in-skills/internal-comms/examples/faq-answers.md +30 -0
- package/built-in-skills/internal-comms/examples/general-comms.md +16 -0
- package/built-in-skills/liteparse/SKILL.md +176 -0
- package/built-in-skills/mcp-builder/LICENSE.txt +202 -0
- package/built-in-skills/mcp-builder/SKILL.md +237 -0
- package/built-in-skills/mcp-builder/reference/evaluation.md +602 -0
- package/built-in-skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/built-in-skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/built-in-skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/built-in-skills/mcp-builder/scripts/connections.py +151 -0
- package/built-in-skills/mcp-builder/scripts/evaluation.py +373 -0
- package/built-in-skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -0
- package/built-in-skills/memory/SKILL.md +471 -0
- package/built-in-skills/memory-dream/SKILL.md +339 -0
- package/built-in-skills/office-artifacts/SKILL.md +211 -0
- package/built-in-skills/okr/SKILL.md +154 -0
- package/built-in-skills/persona/SKILL.md +81 -0
- package/built-in-skills/persona-generator/SKILL.md +296 -0
- package/built-in-skills/pkf-svg/SKILL.md +253 -0
- package/built-in-skills/pkf-writing/SKILL.md +236 -0
- package/built-in-skills/prismer-im-collab/SKILL.md +168 -0
- package/built-in-skills/proactivity/SKILL.md +84 -0
- package/built-in-skills/remotion/SKILL.md +431 -0
- package/built-in-skills/role-builder/SKILL.md +203 -0
- package/built-in-skills/role-builder/scripts/author-role.mjs +334 -0
- package/built-in-skills/role-builder/scripts/ingest-role.mjs +223 -0
- package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +290 -0
- package/built-in-skills/role-builder/scripts/operation-harness.mjs +267 -0
- package/built-in-skills/skill-authoring/SKILL.md +134 -0
- package/built-in-skills/skill-authoring/skill.json +74 -0
- package/built-in-skills/skill-builder/SKILL.md +171 -0
- package/built-in-skills/skill-builder/scripts/ingest.mjs +265 -0
- package/built-in-skills/skill-creator/LICENSE.txt +202 -0
- package/built-in-skills/skill-creator/SKILL.md +227 -0
- package/built-in-skills/skill-creator/agents/analyzer.md +274 -0
- package/built-in-skills/skill-creator/agents/comparator.md +202 -0
- package/built-in-skills/skill-creator/agents/grader.md +223 -0
- package/built-in-skills/skill-creator/assets/eval_review.html +146 -0
- package/built-in-skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/built-in-skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/built-in-skills/skill-creator/references/external-library-import.md +110 -0
- package/built-in-skills/skill-creator/references/schemas.md +430 -0
- package/built-in-skills/skill-creator/scripts/__init__.py +0 -0
- package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/built-in-skills/skill-creator/scripts/generate_report.py +326 -0
- package/built-in-skills/skill-creator/scripts/import-library.mjs +475 -0
- package/built-in-skills/skill-creator/scripts/improve_description.py +247 -0
- package/built-in-skills/skill-creator/scripts/package_skill.py +136 -0
- package/built-in-skills/skill-creator/scripts/quick_validate.py +103 -0
- package/built-in-skills/skill-creator/scripts/run_eval.py +310 -0
- package/built-in-skills/skill-creator/scripts/run_loop.py +328 -0
- package/built-in-skills/skill-creator/scripts/utils.py +47 -0
- package/built-in-skills/slack-gif-creator/LICENSE.txt +202 -0
- package/built-in-skills/slack-gif-creator/SKILL.md +291 -0
- package/built-in-skills/slack-gif-creator/core/easing.py +234 -0
- package/built-in-skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/built-in-skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/built-in-skills/slack-gif-creator/core/validators.py +136 -0
- package/built-in-skills/slack-gif-creator/requirements.txt +4 -0
- package/built-in-skills/tasks/SKILL.md +413 -0
- package/built-in-skills/tdd/LICENSE +21 -0
- package/built-in-skills/tdd/SKILL.md +110 -0
- package/built-in-skills/tdd/mocking.md +59 -0
- package/built-in-skills/tdd/refactoring.md +10 -0
- package/built-in-skills/tdd/tests.md +61 -0
- package/built-in-skills/team/SKILL.md +77 -0
- package/built-in-skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/built-in-skills/web-artifacts-builder/SKILL.md +105 -0
- package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +334 -0
- package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/built-in-skills/webapp-testing/LICENSE.txt +202 -0
- package/built-in-skills/webapp-testing/SKILL.md +97 -0
- package/built-in-skills/webapp-testing/examples/console_logging.py +35 -0
- package/built-in-skills/webapp-testing/examples/element_discovery.py +40 -0
- package/built-in-skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/built-in-skills/webapp-testing/scripts/with_server.py +106 -0
- package/built-in-skills/wechat-pay/SKILL.md +59 -0
- package/dist/cli.cjs +72577 -16101
- package/dist/cli.js +72752 -16232
- package/dist/index.cjs +72632 -16024
- package/dist/index.d.cts +4956 -640
- package/dist/index.d.ts +4956 -640
- package/dist/index.js +72564 -15963
- package/package.json +39 -6
- package/plugins/memory/prismer/__init__.py +1211 -0
- package/plugins/memory/prismer/plugin.yaml +8 -0
- package/plugins/memory/prismer/tool-schemas.generated.json +249 -0
- package/plugins/tools/prismer-recall/__init__.py +282 -0
- package/plugins/tools/prismer-recall/plugin.yaml +15 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Improve a skill description based on eval results.
|
|
3
|
+
|
|
4
|
+
Takes eval results (from run_eval.py) and generates an improved description
|
|
5
|
+
by calling `claude -p` as a subprocess (same auth pattern as run_eval.py —
|
|
6
|
+
uses the session's Claude Code auth, no separate ANTHROPIC_API_KEY needed).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from scripts.utils import parse_skill_md
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _call_claude(prompt: str, model: str | None, timeout: int = 300) -> str:
|
|
21
|
+
"""Run `claude -p` with the prompt on stdin and return the text response.
|
|
22
|
+
|
|
23
|
+
Prompt goes over stdin (not argv) because it embeds the full SKILL.md
|
|
24
|
+
body and can easily exceed comfortable argv length.
|
|
25
|
+
"""
|
|
26
|
+
cmd = ["claude", "-p", "--output-format", "text"]
|
|
27
|
+
if model:
|
|
28
|
+
cmd.extend(["--model", model])
|
|
29
|
+
|
|
30
|
+
# Remove CLAUDECODE env var to allow nesting claude -p inside a
|
|
31
|
+
# Claude Code session. The guard is for interactive terminal conflicts;
|
|
32
|
+
# programmatic subprocess usage is safe. Same pattern as run_eval.py.
|
|
33
|
+
env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}
|
|
34
|
+
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
cmd,
|
|
37
|
+
input=prompt,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
env=env,
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
)
|
|
43
|
+
if result.returncode != 0:
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
f"claude -p exited {result.returncode}\nstderr: {result.stderr}"
|
|
46
|
+
)
|
|
47
|
+
return result.stdout
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def improve_description(
|
|
51
|
+
skill_name: str,
|
|
52
|
+
skill_content: str,
|
|
53
|
+
current_description: str,
|
|
54
|
+
eval_results: dict,
|
|
55
|
+
history: list[dict],
|
|
56
|
+
model: str,
|
|
57
|
+
test_results: dict | None = None,
|
|
58
|
+
log_dir: Path | None = None,
|
|
59
|
+
iteration: int | None = None,
|
|
60
|
+
) -> str:
|
|
61
|
+
"""Call Claude to improve the description based on eval results."""
|
|
62
|
+
failed_triggers = [
|
|
63
|
+
r for r in eval_results["results"]
|
|
64
|
+
if r["should_trigger"] and not r["pass"]
|
|
65
|
+
]
|
|
66
|
+
false_triggers = [
|
|
67
|
+
r for r in eval_results["results"]
|
|
68
|
+
if not r["should_trigger"] and not r["pass"]
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# Build scores summary
|
|
72
|
+
train_score = f"{eval_results['summary']['passed']}/{eval_results['summary']['total']}"
|
|
73
|
+
if test_results:
|
|
74
|
+
test_score = f"{test_results['summary']['passed']}/{test_results['summary']['total']}"
|
|
75
|
+
scores_summary = f"Train: {train_score}, Test: {test_score}"
|
|
76
|
+
else:
|
|
77
|
+
scores_summary = f"Train: {train_score}"
|
|
78
|
+
|
|
79
|
+
prompt = f"""You are optimizing a skill description for a Claude Code skill called "{skill_name}". A "skill" is sort of like a prompt, but with progressive disclosure -- there's a title and description that Claude sees when deciding whether to use the skill, and then if it does use the skill, it reads the .md file which has lots more details and potentially links to other resources in the skill folder like helper files and scripts and additional documentation or examples.
|
|
80
|
+
|
|
81
|
+
The description appears in Claude's "available_skills" list. When a user sends a query, Claude decides whether to invoke the skill based solely on the title and on this description. Your goal is to write a description that triggers for relevant queries, and doesn't trigger for irrelevant ones.
|
|
82
|
+
|
|
83
|
+
Here's the current description:
|
|
84
|
+
<current_description>
|
|
85
|
+
"{current_description}"
|
|
86
|
+
</current_description>
|
|
87
|
+
|
|
88
|
+
Current scores ({scores_summary}):
|
|
89
|
+
<scores_summary>
|
|
90
|
+
"""
|
|
91
|
+
if failed_triggers:
|
|
92
|
+
prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n"
|
|
93
|
+
for r in failed_triggers:
|
|
94
|
+
prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
|
|
95
|
+
prompt += "\n"
|
|
96
|
+
|
|
97
|
+
if false_triggers:
|
|
98
|
+
prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n"
|
|
99
|
+
for r in false_triggers:
|
|
100
|
+
prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
|
|
101
|
+
prompt += "\n"
|
|
102
|
+
|
|
103
|
+
if history:
|
|
104
|
+
prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n"
|
|
105
|
+
for h in history:
|
|
106
|
+
train_s = f"{h.get('train_passed', h.get('passed', 0))}/{h.get('train_total', h.get('total', 0))}"
|
|
107
|
+
test_s = f"{h.get('test_passed', '?')}/{h.get('test_total', '?')}" if h.get('test_passed') is not None else None
|
|
108
|
+
score_str = f"train={train_s}" + (f", test={test_s}" if test_s else "")
|
|
109
|
+
prompt += f'<attempt {score_str}>\n'
|
|
110
|
+
prompt += f'Description: "{h["description"]}"\n'
|
|
111
|
+
if "results" in h:
|
|
112
|
+
prompt += "Train results:\n"
|
|
113
|
+
for r in h["results"]:
|
|
114
|
+
status = "PASS" if r["pass"] else "FAIL"
|
|
115
|
+
prompt += f' [{status}] "{r["query"][:80]}" (triggered {r["triggers"]}/{r["runs"]})\n'
|
|
116
|
+
if h.get("note"):
|
|
117
|
+
prompt += f'Note: {h["note"]}\n'
|
|
118
|
+
prompt += "</attempt>\n\n"
|
|
119
|
+
|
|
120
|
+
prompt += f"""</scores_summary>
|
|
121
|
+
|
|
122
|
+
Skill content (for context on what the skill does):
|
|
123
|
+
<skill_content>
|
|
124
|
+
{skill_content}
|
|
125
|
+
</skill_content>
|
|
126
|
+
|
|
127
|
+
Based on the failures, write a new and improved description that is more likely to trigger correctly. When I say "based on the failures", it's a bit of a tricky line to walk because we don't want to overfit to the specific cases you're seeing. So what I DON'T want you to do is produce an ever-expanding list of specific queries that this skill should or shouldn't trigger for. Instead, try to generalize from the failures to broader categories of user intent and situations where this skill would be useful or not useful. The reason for this is twofold:
|
|
128
|
+
|
|
129
|
+
1. Avoid overfitting
|
|
130
|
+
2. The list might get loooong and it's injected into ALL queries and there might be a lot of skills, so we don't want to blow too much space on any given description.
|
|
131
|
+
|
|
132
|
+
Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy. There is a hard limit of 1024 characters — descriptions over that will be truncated, so stay comfortably under it.
|
|
133
|
+
|
|
134
|
+
Here are some tips that we've found to work well in writing these descriptions:
|
|
135
|
+
- The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
|
|
136
|
+
- The skill description should focus on the user's intent, what they are trying to achieve, vs. the implementation details of how the skill works.
|
|
137
|
+
- The description competes with other skills for Claude's attention — make it distinctive and immediately recognizable.
|
|
138
|
+
- If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
|
|
139
|
+
|
|
140
|
+
I'd encourage you to be creative and mix up the style in different iterations since you'll have multiple opportunities to try different approaches and we'll just grab the highest-scoring one at the end.
|
|
141
|
+
|
|
142
|
+
Please respond with only the new description text in <new_description> tags, nothing else."""
|
|
143
|
+
|
|
144
|
+
text = _call_claude(prompt, model)
|
|
145
|
+
|
|
146
|
+
match = re.search(r"<new_description>(.*?)</new_description>", text, re.DOTALL)
|
|
147
|
+
description = match.group(1).strip().strip('"') if match else text.strip().strip('"')
|
|
148
|
+
|
|
149
|
+
transcript: dict = {
|
|
150
|
+
"iteration": iteration,
|
|
151
|
+
"prompt": prompt,
|
|
152
|
+
"response": text,
|
|
153
|
+
"parsed_description": description,
|
|
154
|
+
"char_count": len(description),
|
|
155
|
+
"over_limit": len(description) > 1024,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Safety net: the prompt already states the 1024-char hard limit, but if
|
|
159
|
+
# the model blew past it anyway, make one fresh single-turn call that
|
|
160
|
+
# quotes the too-long version and asks for a shorter rewrite. (The old
|
|
161
|
+
# SDK path did this as a true multi-turn; `claude -p` is one-shot, so we
|
|
162
|
+
# inline the prior output into the new prompt instead.)
|
|
163
|
+
if len(description) > 1024:
|
|
164
|
+
shorten_prompt = (
|
|
165
|
+
f"{prompt}\n\n"
|
|
166
|
+
f"---\n\n"
|
|
167
|
+
f"A previous attempt produced this description, which at "
|
|
168
|
+
f"{len(description)} characters is over the 1024-character hard limit:\n\n"
|
|
169
|
+
f'"{description}"\n\n'
|
|
170
|
+
f"Rewrite it to be under 1024 characters while keeping the most "
|
|
171
|
+
f"important trigger words and intent coverage. Respond with only "
|
|
172
|
+
f"the new description in <new_description> tags."
|
|
173
|
+
)
|
|
174
|
+
shorten_text = _call_claude(shorten_prompt, model)
|
|
175
|
+
match = re.search(r"<new_description>(.*?)</new_description>", shorten_text, re.DOTALL)
|
|
176
|
+
shortened = match.group(1).strip().strip('"') if match else shorten_text.strip().strip('"')
|
|
177
|
+
|
|
178
|
+
transcript["rewrite_prompt"] = shorten_prompt
|
|
179
|
+
transcript["rewrite_response"] = shorten_text
|
|
180
|
+
transcript["rewrite_description"] = shortened
|
|
181
|
+
transcript["rewrite_char_count"] = len(shortened)
|
|
182
|
+
description = shortened
|
|
183
|
+
|
|
184
|
+
transcript["final_description"] = description
|
|
185
|
+
|
|
186
|
+
if log_dir:
|
|
187
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
188
|
+
log_file = log_dir / f"improve_iter_{iteration or 'unknown'}.json"
|
|
189
|
+
log_file.write_text(json.dumps(transcript, indent=2))
|
|
190
|
+
|
|
191
|
+
return description
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def main():
|
|
195
|
+
parser = argparse.ArgumentParser(description="Improve a skill description based on eval results")
|
|
196
|
+
parser.add_argument("--eval-results", required=True, help="Path to eval results JSON (from run_eval.py)")
|
|
197
|
+
parser.add_argument("--skill-path", required=True, help="Path to skill directory")
|
|
198
|
+
parser.add_argument("--history", default=None, help="Path to history JSON (previous attempts)")
|
|
199
|
+
parser.add_argument("--model", required=True, help="Model for improvement")
|
|
200
|
+
parser.add_argument("--verbose", action="store_true", help="Print thinking to stderr")
|
|
201
|
+
args = parser.parse_args()
|
|
202
|
+
|
|
203
|
+
skill_path = Path(args.skill_path)
|
|
204
|
+
if not (skill_path / "SKILL.md").exists():
|
|
205
|
+
print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
eval_results = json.loads(Path(args.eval_results).read_text())
|
|
209
|
+
history = []
|
|
210
|
+
if args.history:
|
|
211
|
+
history = json.loads(Path(args.history).read_text())
|
|
212
|
+
|
|
213
|
+
name, _, content = parse_skill_md(skill_path)
|
|
214
|
+
current_description = eval_results["description"]
|
|
215
|
+
|
|
216
|
+
if args.verbose:
|
|
217
|
+
print(f"Current: {current_description}", file=sys.stderr)
|
|
218
|
+
print(f"Score: {eval_results['summary']['passed']}/{eval_results['summary']['total']}", file=sys.stderr)
|
|
219
|
+
|
|
220
|
+
new_description = improve_description(
|
|
221
|
+
skill_name=name,
|
|
222
|
+
skill_content=content,
|
|
223
|
+
current_description=current_description,
|
|
224
|
+
eval_results=eval_results,
|
|
225
|
+
history=history,
|
|
226
|
+
model=args.model,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if args.verbose:
|
|
230
|
+
print(f"Improved: {new_description}", file=sys.stderr)
|
|
231
|
+
|
|
232
|
+
# Output as JSON with both the new description and updated history
|
|
233
|
+
output = {
|
|
234
|
+
"description": new_description,
|
|
235
|
+
"history": history + [{
|
|
236
|
+
"description": current_description,
|
|
237
|
+
"passed": eval_results["summary"]["passed"],
|
|
238
|
+
"failed": eval_results["summary"]["failed"],
|
|
239
|
+
"total": eval_results["summary"]["total"],
|
|
240
|
+
"results": eval_results["results"],
|
|
241
|
+
}],
|
|
242
|
+
}
|
|
243
|
+
print(json.dumps(output, indent=2))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
main()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Packager - Creates a distributable .skill file of a skill folder
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python utils/package_skill.py <path/to/skill-folder> [output-directory]
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
python utils/package_skill.py skills/public/my-skill
|
|
10
|
+
python utils/package_skill.py skills/public/my-skill ./dist
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import fnmatch
|
|
14
|
+
import sys
|
|
15
|
+
import zipfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from scripts.quick_validate import validate_skill
|
|
18
|
+
|
|
19
|
+
# Patterns to exclude when packaging skills.
|
|
20
|
+
EXCLUDE_DIRS = {"__pycache__", "node_modules"}
|
|
21
|
+
EXCLUDE_GLOBS = {"*.pyc"}
|
|
22
|
+
EXCLUDE_FILES = {".DS_Store"}
|
|
23
|
+
# Directories excluded only at the skill root (not when nested deeper).
|
|
24
|
+
ROOT_EXCLUDE_DIRS = {"evals"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def should_exclude(rel_path: Path) -> bool:
|
|
28
|
+
"""Check if a path should be excluded from packaging."""
|
|
29
|
+
parts = rel_path.parts
|
|
30
|
+
if any(part in EXCLUDE_DIRS for part in parts):
|
|
31
|
+
return True
|
|
32
|
+
# rel_path is relative to skill_path.parent, so parts[0] is the skill
|
|
33
|
+
# folder name and parts[1] (if present) is the first subdir.
|
|
34
|
+
if len(parts) > 1 and parts[1] in ROOT_EXCLUDE_DIRS:
|
|
35
|
+
return True
|
|
36
|
+
name = rel_path.name
|
|
37
|
+
if name in EXCLUDE_FILES:
|
|
38
|
+
return True
|
|
39
|
+
return any(fnmatch.fnmatch(name, pat) for pat in EXCLUDE_GLOBS)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def package_skill(skill_path, output_dir=None):
|
|
43
|
+
"""
|
|
44
|
+
Package a skill folder into a .skill file.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
skill_path: Path to the skill folder
|
|
48
|
+
output_dir: Optional output directory for the .skill file (defaults to current directory)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to the created .skill file, or None if error
|
|
52
|
+
"""
|
|
53
|
+
skill_path = Path(skill_path).resolve()
|
|
54
|
+
|
|
55
|
+
# Validate skill folder exists
|
|
56
|
+
if not skill_path.exists():
|
|
57
|
+
print(f"❌ Error: Skill folder not found: {skill_path}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
if not skill_path.is_dir():
|
|
61
|
+
print(f"❌ Error: Path is not a directory: {skill_path}")
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# Validate SKILL.md exists
|
|
65
|
+
skill_md = skill_path / "SKILL.md"
|
|
66
|
+
if not skill_md.exists():
|
|
67
|
+
print(f"❌ Error: SKILL.md not found in {skill_path}")
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
# Run validation before packaging
|
|
71
|
+
print("🔍 Validating skill...")
|
|
72
|
+
valid, message = validate_skill(skill_path)
|
|
73
|
+
if not valid:
|
|
74
|
+
print(f"❌ Validation failed: {message}")
|
|
75
|
+
print(" Please fix the validation errors before packaging.")
|
|
76
|
+
return None
|
|
77
|
+
print(f"✅ {message}\n")
|
|
78
|
+
|
|
79
|
+
# Determine output location
|
|
80
|
+
skill_name = skill_path.name
|
|
81
|
+
if output_dir:
|
|
82
|
+
output_path = Path(output_dir).resolve()
|
|
83
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
else:
|
|
85
|
+
output_path = Path.cwd()
|
|
86
|
+
|
|
87
|
+
skill_filename = output_path / f"{skill_name}.skill"
|
|
88
|
+
|
|
89
|
+
# Create the .skill file (zip format)
|
|
90
|
+
try:
|
|
91
|
+
with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
92
|
+
# Walk through the skill directory, excluding build artifacts
|
|
93
|
+
for file_path in skill_path.rglob('*'):
|
|
94
|
+
if not file_path.is_file():
|
|
95
|
+
continue
|
|
96
|
+
arcname = file_path.relative_to(skill_path.parent)
|
|
97
|
+
if should_exclude(arcname):
|
|
98
|
+
print(f" Skipped: {arcname}")
|
|
99
|
+
continue
|
|
100
|
+
zipf.write(file_path, arcname)
|
|
101
|
+
print(f" Added: {arcname}")
|
|
102
|
+
|
|
103
|
+
print(f"\n✅ Successfully packaged skill to: {skill_filename}")
|
|
104
|
+
return skill_filename
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"❌ Error creating .skill file: {e}")
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main():
|
|
112
|
+
if len(sys.argv) < 2:
|
|
113
|
+
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
|
|
114
|
+
print("\nExample:")
|
|
115
|
+
print(" python utils/package_skill.py skills/public/my-skill")
|
|
116
|
+
print(" python utils/package_skill.py skills/public/my-skill ./dist")
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
skill_path = sys.argv[1]
|
|
120
|
+
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
|
121
|
+
|
|
122
|
+
print(f"📦 Packaging skill: {skill_path}")
|
|
123
|
+
if output_dir:
|
|
124
|
+
print(f" Output directory: {output_dir}")
|
|
125
|
+
print()
|
|
126
|
+
|
|
127
|
+
result = package_skill(skill_path, output_dir)
|
|
128
|
+
|
|
129
|
+
if result:
|
|
130
|
+
sys.exit(0)
|
|
131
|
+
else:
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
main()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick validation script for skills - minimal version
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import yaml
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
def validate_skill(skill_path):
|
|
13
|
+
"""Basic validation of a skill"""
|
|
14
|
+
skill_path = Path(skill_path)
|
|
15
|
+
|
|
16
|
+
# Check SKILL.md exists
|
|
17
|
+
skill_md = skill_path / 'SKILL.md'
|
|
18
|
+
if not skill_md.exists():
|
|
19
|
+
return False, "SKILL.md not found"
|
|
20
|
+
|
|
21
|
+
# Read and validate frontmatter
|
|
22
|
+
content = skill_md.read_text()
|
|
23
|
+
if not content.startswith('---'):
|
|
24
|
+
return False, "No YAML frontmatter found"
|
|
25
|
+
|
|
26
|
+
# Extract frontmatter
|
|
27
|
+
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
28
|
+
if not match:
|
|
29
|
+
return False, "Invalid frontmatter format"
|
|
30
|
+
|
|
31
|
+
frontmatter_text = match.group(1)
|
|
32
|
+
|
|
33
|
+
# Parse YAML frontmatter
|
|
34
|
+
try:
|
|
35
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
36
|
+
if not isinstance(frontmatter, dict):
|
|
37
|
+
return False, "Frontmatter must be a YAML dictionary"
|
|
38
|
+
except yaml.YAMLError as e:
|
|
39
|
+
return False, f"Invalid YAML in frontmatter: {e}"
|
|
40
|
+
|
|
41
|
+
# Define allowed properties
|
|
42
|
+
ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
|
|
43
|
+
|
|
44
|
+
# Check for unexpected properties (excluding nested keys under metadata)
|
|
45
|
+
unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
|
|
46
|
+
if unexpected_keys:
|
|
47
|
+
return False, (
|
|
48
|
+
f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. "
|
|
49
|
+
f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Check required fields
|
|
53
|
+
if 'name' not in frontmatter:
|
|
54
|
+
return False, "Missing 'name' in frontmatter"
|
|
55
|
+
if 'description' not in frontmatter:
|
|
56
|
+
return False, "Missing 'description' in frontmatter"
|
|
57
|
+
|
|
58
|
+
# Extract name for validation
|
|
59
|
+
name = frontmatter.get('name', '')
|
|
60
|
+
if not isinstance(name, str):
|
|
61
|
+
return False, f"Name must be a string, got {type(name).__name__}"
|
|
62
|
+
name = name.strip()
|
|
63
|
+
if name:
|
|
64
|
+
# Check naming convention (kebab-case: lowercase with hyphens)
|
|
65
|
+
if not re.match(r'^[a-z0-9-]+$', name):
|
|
66
|
+
return False, f"Name '{name}' should be kebab-case (lowercase letters, digits, and hyphens only)"
|
|
67
|
+
if name.startswith('-') or name.endswith('-') or '--' in name:
|
|
68
|
+
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
|
|
69
|
+
# Check name length (max 64 characters per spec)
|
|
70
|
+
if len(name) > 64:
|
|
71
|
+
return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters."
|
|
72
|
+
|
|
73
|
+
# Extract and validate description
|
|
74
|
+
description = frontmatter.get('description', '')
|
|
75
|
+
if not isinstance(description, str):
|
|
76
|
+
return False, f"Description must be a string, got {type(description).__name__}"
|
|
77
|
+
description = description.strip()
|
|
78
|
+
if description:
|
|
79
|
+
# Check for angle brackets
|
|
80
|
+
if '<' in description or '>' in description:
|
|
81
|
+
return False, "Description cannot contain angle brackets (< or >)"
|
|
82
|
+
# Check description length (max 1024 characters per spec)
|
|
83
|
+
if len(description) > 1024:
|
|
84
|
+
return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters."
|
|
85
|
+
|
|
86
|
+
# Validate compatibility field if present (optional)
|
|
87
|
+
compatibility = frontmatter.get('compatibility', '')
|
|
88
|
+
if compatibility:
|
|
89
|
+
if not isinstance(compatibility, str):
|
|
90
|
+
return False, f"Compatibility must be a string, got {type(compatibility).__name__}"
|
|
91
|
+
if len(compatibility) > 500:
|
|
92
|
+
return False, f"Compatibility is too long ({len(compatibility)} characters). Maximum is 500 characters."
|
|
93
|
+
|
|
94
|
+
return True, "Skill is valid!"
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
if len(sys.argv) != 2:
|
|
98
|
+
print("Usage: python quick_validate.py <skill_directory>")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
valid, message = validate_skill(sys.argv[1])
|
|
102
|
+
print(message)
|
|
103
|
+
sys.exit(0 if valid else 1)
|