@heytherevibin/skillforge 0.2.1
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 +16 -0
- package/CODE_OF_CONDUCT.md +34 -0
- package/CONTRIBUTING.md +38 -0
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/RELEASING.md +93 -0
- package/SECURITY.md +31 -0
- package/STRATEGY.md +26 -0
- package/bin/cli.js +547 -0
- package/lib/packs.js +184 -0
- package/package.json +38 -0
- package/python/app/__init__.py +0 -0
- package/python/app/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/app/__pycache__/auth.cpython-312.pyc +0 -0
- package/python/app/__pycache__/main.cpython-312.pyc +0 -0
- package/python/app/auth.py +63 -0
- package/python/app/cli.py +78 -0
- package/python/app/db_paths.py +26 -0
- package/python/app/events_cli.py +175 -0
- package/python/app/main.py +647 -0
- package/python/app/materialize.py +138 -0
- package/python/app/mcp_server.py +610 -0
- package/python/app/route_cli.py +117 -0
- package/python/requirements-dev.txt +1 -0
- package/python/requirements.txt +7 -0
- package/python/tests/test_db_paths.py +41 -0
- package/skills/accessibility/SKILL.md +145 -0
- package/skills/agent-architecture-audit/SKILL.md +256 -0
- package/skills/agent-eval/SKILL.md +144 -0
- package/skills/agent-harness-construction/SKILL.md +72 -0
- package/skills/agent-introspection-debugging/SKILL.md +152 -0
- package/skills/agent-payment-x402/SKILL.md +224 -0
- package/skills/agent-sort/SKILL.md +214 -0
- package/skills/agentic-engineering/SKILL.md +62 -0
- package/skills/agentic-os/SKILL.md +386 -0
- package/skills/ai-first-engineering/SKILL.md +50 -0
- package/skills/ai-regression-testing/SKILL.md +384 -0
- package/skills/android-clean-architecture/SKILL.md +338 -0
- package/skills/angular-developer/SKILL.md +153 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +120 -0
- package/skills/api-design/SKILL.md +522 -0
- package/skills/architecture-decision-records/SKILL.md +178 -0
- package/skills/article-writing/SKILL.md +78 -0
- package/skills/automation-audit-ops/SKILL.md +141 -0
- package/skills/autonomous-agent-harness/SKILL.md +272 -0
- package/skills/autonomous-loops/SKILL.md +609 -0
- package/skills/backend-patterns/SKILL.md +560 -0
- package/skills/benchmark/SKILL.md +92 -0
- package/skills/blueprint/SKILL.md +104 -0
- package/skills/browser-qa/SKILL.md +86 -0
- package/skills/bun-runtime/SKILL.md +83 -0
- package/skills/canary-watch/SKILL.md +98 -0
- package/skills/carrier-relationship-management/SKILL.md +211 -0
- package/skills/cisco-ios-patterns/SKILL.md +163 -0
- package/skills/ck/SKILL.md +147 -0
- package/skills/ck/commands/forget.mjs +44 -0
- package/skills/ck/commands/info.mjs +24 -0
- package/skills/ck/commands/init.mjs +143 -0
- package/skills/ck/commands/list.mjs +40 -0
- package/skills/ck/commands/migrate.mjs +202 -0
- package/skills/ck/commands/resume.mjs +36 -0
- package/skills/ck/commands/save.mjs +210 -0
- package/skills/ck/commands/shared.mjs +387 -0
- package/skills/ck/hooks/session-start.mjs +224 -0
- package/skills/claude-devfleet/SKILL.md +103 -0
- package/skills/click-path-audit/SKILL.md +244 -0
- package/skills/clickhouse-io/SKILL.md +438 -0
- package/skills/code-tour/SKILL.md +235 -0
- package/skills/codebase-onboarding/SKILL.md +232 -0
- package/skills/coding-standards/SKILL.md +548 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +298 -0
- package/skills/connections-optimizer/SKILL.md +188 -0
- package/skills/content-engine/SKILL.md +126 -0
- package/skills/content-hash-cache-pattern/SKILL.md +160 -0
- package/skills/context-budget/SKILL.md +134 -0
- package/skills/continuous-agent-loop/SKILL.md +44 -0
- package/skills/continuous-learning/SKILL.md +129 -0
- package/skills/continuous-learning/config.json +18 -0
- package/skills/continuous-learning/evaluate-session.sh +69 -0
- package/skills/continuous-learning-v2/SKILL.md +358 -0
- package/skills/continuous-learning-v2/agents/observer-loop.sh +322 -0
- package/skills/continuous-learning-v2/agents/observer.md +198 -0
- package/skills/continuous-learning-v2/agents/session-guardian.sh +150 -0
- package/skills/continuous-learning-v2/agents/start-observer.sh +248 -0
- package/skills/continuous-learning-v2/config.json +8 -0
- package/skills/continuous-learning-v2/hooks/observe.sh +476 -0
- package/skills/continuous-learning-v2/scripts/detect-project.sh +288 -0
- package/skills/continuous-learning-v2/scripts/instinct-cli.py +1519 -0
- package/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +31 -0
- package/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +62 -0
- package/skills/continuous-learning-v2/scripts/test_parse_instinct.py +1018 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +182 -0
- package/skills/cost-tracking/SKILL.md +147 -0
- package/skills/council/SKILL.md +202 -0
- package/skills/cpp-coding-standards/SKILL.md +722 -0
- package/skills/cpp-testing/SKILL.md +323 -0
- package/skills/crosspost/SKILL.md +110 -0
- package/skills/csharp-testing/SKILL.md +320 -0
- package/skills/customer-billing-ops/SKILL.md +139 -0
- package/skills/customs-trade-compliance/SKILL.md +262 -0
- package/skills/dart-flutter-patterns/SKILL.md +562 -0
- package/skills/dashboard-builder/SKILL.md +108 -0
- package/skills/data-scraper-agent/SKILL.md +764 -0
- package/skills/database-migrations/SKILL.md +428 -0
- package/skills/deep-research/SKILL.md +158 -0
- package/skills/defi-amm-security/SKILL.md +166 -0
- package/skills/deployment-patterns/SKILL.md +426 -0
- package/skills/design-system/SKILL.md +81 -0
- package/skills/django-celery/SKILL.md +456 -0
- package/skills/django-patterns/SKILL.md +733 -0
- package/skills/django-security/SKILL.md +592 -0
- package/skills/django-tdd/SKILL.md +728 -0
- package/skills/django-verification/SKILL.md +468 -0
- package/skills/dmux-workflows/SKILL.md +190 -0
- package/skills/docker-patterns/SKILL.md +363 -0
- package/skills/documentation-lookup/SKILL.md +89 -0
- package/skills/dotnet-patterns/SKILL.md +320 -0
- package/skills/e2e-testing/SKILL.md +325 -0
- package/skills/email-ops/SKILL.md +120 -0
- package/skills/energy-procurement/SKILL.md +227 -0
- package/skills/enterprise-agent-ops/SKILL.md +49 -0
- package/skills/error-handling/SKILL.md +375 -0
- package/skills/eval-harness/SKILL.md +269 -0
- package/skills/evm-token-decimals/SKILL.md +130 -0
- package/skills/exa-search/SKILL.md +106 -0
- package/skills/fal-ai-media/SKILL.md +287 -0
- package/skills/fastapi-patterns/SKILL.md +327 -0
- package/skills/finance-billing-ops/SKILL.md +126 -0
- package/skills/flox-environments/SKILL.md +496 -0
- package/skills/flutter-dart-code-review/SKILL.md +434 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-design-direction/SKILL.md +92 -0
- package/skills/frontend-patterns/SKILL.md +641 -0
- package/skills/frontend-slides/SKILL.md +183 -0
- package/skills/frontend-slides/STYLE_PRESETS.md +330 -0
- package/skills/frontend-slides/animation-patterns.md +122 -0
- package/skills/frontend-slides/html-template.md +419 -0
- package/skills/frontend-slides/scripts/export-pdf.sh +418 -0
- package/skills/frontend-slides/scripts/extract-pptx.py +96 -0
- package/skills/frontend-slides/viewport-base.css +153 -0
- package/skills/fsharp-testing/SKILL.md +279 -0
- package/skills/gan-style-harness/SKILL.md +278 -0
- package/skills/gateguard/SKILL.md +125 -0
- package/skills/git-workflow/SKILL.md +714 -0
- package/skills/github-ops/SKILL.md +143 -0
- package/skills/golang-patterns/SKILL.md +673 -0
- package/skills/golang-testing/SKILL.md +719 -0
- package/skills/google-workspace-ops/SKILL.md +94 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +245 -0
- package/skills/healthcare-emr-patterns/SKILL.md +159 -0
- package/skills/healthcare-eval-harness/SKILL.md +207 -0
- package/skills/healthcare-phi-compliance/SKILL.md +145 -0
- package/skills/hermes-imports/SKILL.md +87 -0
- package/skills/hexagonal-architecture/SKILL.md +275 -0
- package/skills/hipaa-compliance/SKILL.md +78 -0
- package/skills/homelab-network-readiness/SKILL.md +169 -0
- package/skills/homelab-network-setup/SKILL.md +129 -0
- package/skills/homelab-pihole-dns/SKILL.md +274 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +311 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +305 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inventory-demand-planning/SKILL.md +246 -0
- package/skills/investor-materials/SKILL.md +95 -0
- package/skills/investor-outreach/SKILL.md +90 -0
- package/skills/ios-icon-gen/SKILL.md +157 -0
- package/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
- package/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
- package/skills/iterative-retrieval/SKILL.md +209 -0
- package/skills/java-coding-standards/SKILL.md +382 -0
- package/skills/jira-integration/SKILL.md +292 -0
- package/skills/jpa-patterns/SKILL.md +150 -0
- package/skills/knowledge-ops/SKILL.md +153 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +283 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +718 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +688 -0
- package/skills/kotlin-patterns/SKILL.md +710 -0
- package/skills/kotlin-testing/SKILL.md +823 -0
- package/skills/laravel-patterns/SKILL.md +414 -0
- package/skills/laravel-plugin-discovery/SKILL.md +228 -0
- package/skills/laravel-security/SKILL.md +284 -0
- package/skills/laravel-tdd/SKILL.md +282 -0
- package/skills/laravel-verification/SKILL.md +178 -0
- package/skills/lead-intelligence/SKILL.md +320 -0
- package/skills/lead-intelligence/agents/enrichment-agent.md +85 -0
- package/skills/lead-intelligence/agents/mutual-mapper.md +75 -0
- package/skills/lead-intelligence/agents/outreach-drafter.md +98 -0
- package/skills/lead-intelligence/agents/signal-scorer.md +60 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +146 -0
- package/skills/logistics-exception-management/SKILL.md +221 -0
- package/skills/make-interfaces-feel-better/SKILL.md +151 -0
- package/skills/manim-video/SKILL.md +88 -0
- package/skills/manim-video/assets/network_graph_scene.py +52 -0
- package/skills/market-research/SKILL.md +74 -0
- package/skills/mcp-server-patterns/SKILL.md +68 -0
- package/skills/messages-ops/SKILL.md +103 -0
- package/skills/mle-workflow/SKILL.md +345 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +435 -0
- package/skills/motion-ui/SKILL.md +574 -0
- package/skills/mysql-patterns/SKILL.md +411 -0
- package/skills/nanoclaw-repl/SKILL.md +32 -0
- package/skills/nestjs-patterns/SKILL.md +229 -0
- package/skills/netmiko-ssh-automation/SKILL.md +173 -0
- package/skills/network-bgp-diagnostics/SKILL.md +167 -0
- package/skills/network-config-validation/SKILL.md +210 -0
- package/skills/network-interface-health/SKILL.md +152 -0
- package/skills/nextjs-turbopack/SKILL.md +43 -0
- package/skills/nodejs-keccak256/SKILL.md +102 -0
- package/skills/nutrient-document-processing/SKILL.md +166 -0
- package/skills/nuxt4-patterns/SKILL.md +99 -0
- package/skills/openclaw-persona-forge/SKILL.md +288 -0
- package/skills/openclaw-persona-forge/gacha.py +224 -0
- package/skills/openclaw-persona-forge/gacha.sh +5 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +254 -0
- package/skills/perl-patterns/SKILL.md +503 -0
- package/skills/perl-security/SKILL.md +502 -0
- package/skills/perl-testing/SKILL.md +474 -0
- package/skills/plan-orchestrate/SKILL.md +253 -0
- package/skills/plankton-code-quality/SKILL.md +236 -0
- package/skills/postgres-patterns/SKILL.md +146 -0
- package/skills/product-capability/SKILL.md +140 -0
- package/skills/product-lens/SKILL.md +91 -0
- package/skills/production-audit/SKILL.md +206 -0
- package/skills/production-scheduling/SKILL.md +237 -0
- package/skills/project-flow-ops/SKILL.md +110 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +749 -0
- package/skills/python-testing/SKILL.md +815 -0
- package/skills/pytorch-patterns/SKILL.md +395 -0
- package/skills/quality-nonconformance/SKILL.md +259 -0
- package/skills/quarkus-patterns/SKILL.md +721 -0
- package/skills/quarkus-security/SKILL.md +466 -0
- package/skills/quarkus-tdd/SKILL.md +810 -0
- package/skills/quarkus-verification/SKILL.md +478 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +66 -0
- package/skills/redis-patterns/SKILL.md +402 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +219 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/remotion-video-creation/rules/3d.md +86 -0
- package/skills/remotion-video-creation/rules/animations.md +29 -0
- package/skills/remotion-video-creation/rules/assets/charts-bar-chart.tsx +173 -0
- package/skills/remotion-video-creation/rules/assets/text-animations-typewriter.tsx +100 -0
- package/skills/remotion-video-creation/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/skills/remotion-video-creation/rules/assets.md +78 -0
- package/skills/remotion-video-creation/rules/audio.md +172 -0
- package/skills/remotion-video-creation/rules/calculate-metadata.md +104 -0
- package/skills/remotion-video-creation/rules/can-decode.md +75 -0
- package/skills/remotion-video-creation/rules/charts.md +58 -0
- package/skills/remotion-video-creation/rules/compositions.md +146 -0
- package/skills/remotion-video-creation/rules/display-captions.md +126 -0
- package/skills/remotion-video-creation/rules/extract-frames.md +229 -0
- package/skills/remotion-video-creation/rules/fonts.md +152 -0
- package/skills/remotion-video-creation/rules/get-audio-duration.md +58 -0
- package/skills/remotion-video-creation/rules/get-video-dimensions.md +68 -0
- package/skills/remotion-video-creation/rules/get-video-duration.md +58 -0
- package/skills/remotion-video-creation/rules/gifs.md +138 -0
- package/skills/remotion-video-creation/rules/images.md +130 -0
- package/skills/remotion-video-creation/rules/import-srt-captions.md +67 -0
- package/skills/remotion-video-creation/rules/lottie.md +67 -0
- package/skills/remotion-video-creation/rules/measuring-dom-nodes.md +34 -0
- package/skills/remotion-video-creation/rules/measuring-text.md +143 -0
- package/skills/remotion-video-creation/rules/sequencing.md +106 -0
- package/skills/remotion-video-creation/rules/tailwind.md +11 -0
- package/skills/remotion-video-creation/rules/text-animations.md +20 -0
- package/skills/remotion-video-creation/rules/timing.md +179 -0
- package/skills/remotion-video-creation/rules/transcribe-captions.md +19 -0
- package/skills/remotion-video-creation/rules/transitions.md +122 -0
- package/skills/remotion-video-creation/rules/trimming.md +52 -0
- package/skills/remotion-video-creation/rules/videos.md +171 -0
- package/skills/repo-scan/SKILL.md +78 -0
- package/skills/research-ops/SKILL.md +111 -0
- package/skills/returns-reverse-logistics/SKILL.md +239 -0
- package/skills/rules-distill/SKILL.md +263 -0
- package/skills/rules-distill/scripts/scan-rules.sh +58 -0
- package/skills/rules-distill/scripts/scan-skills.sh +129 -0
- package/skills/rust-patterns/SKILL.md +498 -0
- package/skills/rust-testing/SKILL.md +499 -0
- package/skills/safety-guard/SKILL.md +74 -0
- package/skills/santa-method/SKILL.md +306 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +175 -0
- package/skills/scientific-db-uspto-database/SKILL.md +177 -0
- package/skills/scientific-pkg-gget/SKILL.md +166 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +192 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +160 -0
- package/skills/search-first/SKILL.md +181 -0
- package/skills/security-bounty-hunter/SKILL.md +99 -0
- package/skills/security-review/SKILL.md +502 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/seo/SKILL.md +153 -0
- package/skills/skill-comply/SKILL.md +57 -0
- package/skills/skill-comply/fixtures/compliant_trace.jsonl +5 -0
- package/skills/skill-comply/fixtures/noncompliant_trace.jsonl +3 -0
- package/skills/skill-comply/fixtures/tdd_spec.yaml +44 -0
- package/skills/skill-comply/prompts/classifier.md +24 -0
- package/skills/skill-comply/prompts/scenario_generator.md +62 -0
- package/skills/skill-comply/prompts/spec_generator.md +42 -0
- package/skills/skill-comply/pyproject.toml +15 -0
- package/skills/skill-comply/scripts/__init__.py +0 -0
- package/skills/skill-comply/scripts/classifier.py +85 -0
- package/skills/skill-comply/scripts/grader.py +124 -0
- package/skills/skill-comply/scripts/parser.py +107 -0
- package/skills/skill-comply/scripts/report.py +170 -0
- package/skills/skill-comply/scripts/run.py +127 -0
- package/skills/skill-comply/scripts/runner.py +186 -0
- package/skills/skill-comply/scripts/scenario_generator.py +70 -0
- package/skills/skill-comply/scripts/spec_generator.py +72 -0
- package/skills/skill-comply/scripts/utils.py +13 -0
- package/skills/skill-comply/tests/test_grader.py +197 -0
- package/skills/skill-comply/tests/test_parser.py +90 -0
- package/skills/skill-comply/tests/test_runner.py +172 -0
- package/skills/skill-scout/SKILL.md +139 -0
- package/skills/skill-stocktake/SKILL.md +193 -0
- package/skills/skill-stocktake/scripts/quick-diff.sh +87 -0
- package/skills/skill-stocktake/scripts/save-results.sh +56 -0
- package/skills/skill-stocktake/scripts/scan.sh +170 -0
- package/skills/social-graph-ranker/SKILL.md +153 -0
- package/skills/springboot-patterns/SKILL.md +313 -0
- package/skills/springboot-security/SKILL.md +271 -0
- package/skills/springboot-tdd/SKILL.md +157 -0
- package/skills/springboot-verification/SKILL.md +230 -0
- package/skills/strategic-compact/SKILL.md +129 -0
- package/skills/strategic-compact/suggest-compact.sh +54 -0
- package/skills/swift-actor-persistence/SKILL.md +142 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +189 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/tdd-workflow/SKILL.md +462 -0
- package/skills/team-builder/SKILL.md +166 -0
- package/skills/terminal-ops/SKILL.md +108 -0
- package/skills/tinystruct-patterns/SKILL.md +130 -0
- package/skills/tinystruct-patterns/references/architecture.md +77 -0
- package/skills/tinystruct-patterns/references/data-handling.md +35 -0
- package/skills/tinystruct-patterns/references/routing.md +57 -0
- package/skills/tinystruct-patterns/references/system-usage.md +74 -0
- package/skills/tinystruct-patterns/references/testing.md +59 -0
- package/skills/token-budget-advisor/SKILL.md +133 -0
- package/skills/ui-demo/SKILL.md +464 -0
- package/skills/ui-to-vue/SKILL.md +134 -0
- package/skills/unified-notifications-ops/SKILL.md +186 -0
- package/skills/verification-loop/SKILL.md +125 -0
- package/skills/video-editing/SKILL.md +309 -0
- package/skills/videodb/SKILL.md +373 -0
- package/skills/videodb/reference/api-reference.md +550 -0
- package/skills/videodb/reference/capture-reference.md +407 -0
- package/skills/videodb/reference/capture.md +101 -0
- package/skills/videodb/reference/editor.md +443 -0
- package/skills/videodb/reference/generative.md +331 -0
- package/skills/videodb/reference/rtstream-reference.md +564 -0
- package/skills/videodb/reference/rtstream.md +65 -0
- package/skills/videodb/reference/search.md +230 -0
- package/skills/videodb/reference/streaming.md +406 -0
- package/skills/videodb/reference/use-cases.md +118 -0
- package/skills/videodb/scripts/ws_listener.py +282 -0
- package/skills/visa-doc-translate/README.md +86 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +448 -0
- package/skills/windows-desktop-e2e/SKILL.md +787 -0
- package/skills/workspace-surface-audit/SKILL.md +124 -0
- package/skills/x-api/SKILL.md +233 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP server for skillforge.
|
|
3
|
+
|
|
4
|
+
Exposes skill routing as MCP tools so MCP-aware clients (Claude Desktop,
|
|
5
|
+
Claude Code, Cursor, etc.) can use the orchestrator without running the
|
|
6
|
+
HTTP server.
|
|
7
|
+
|
|
8
|
+
Tools exposed:
|
|
9
|
+
route_skills / skillforge_bootstrap — routing (+ optional project materialize).
|
|
10
|
+
materialize_project — .cursor/rules, docs/SKILLFORGE-PRD.md, CLAUDE.md block.
|
|
11
|
+
list_skills, skill_feedback, skill_referenced, disable_skill.
|
|
12
|
+
|
|
13
|
+
Run as: python -m app.mcp_server
|
|
14
|
+
Speaks MCP over stdio (the protocol's standard transport for local servers).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sqlite3
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from app.db_paths import resolve_orchestrator_db
|
|
27
|
+
from app.main import (
|
|
28
|
+
build_router_and_skills,
|
|
29
|
+
init_db,
|
|
30
|
+
load_all_skills,
|
|
31
|
+
log_event,
|
|
32
|
+
run_route_turn,
|
|
33
|
+
set_skill_disabled,
|
|
34
|
+
skill_catalog_manifest,
|
|
35
|
+
update_skill_stat,
|
|
36
|
+
Router,
|
|
37
|
+
)
|
|
38
|
+
from app.materialize import materialize_project_files
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _env_truthy(name: str, default: str = "1") -> bool:
|
|
42
|
+
return os.getenv(name, default).strip().lower() not in ("0", "false", "no", "")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _hot_reload_enabled() -> bool:
|
|
46
|
+
return _env_truthy("SKILLFORGE_SKILL_HOT_RELOAD", "1")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _watch_interval_sec() -> float:
|
|
50
|
+
raw = os.getenv("SKILLFORGE_WATCH_SKILLS_INTERVAL", "30")
|
|
51
|
+
try:
|
|
52
|
+
return float(raw)
|
|
53
|
+
except ValueError:
|
|
54
|
+
return 30.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _mcp_tools_list_changed_capability() -> bool:
|
|
58
|
+
"""Advertise listChanged only when we run a background poll that may emit notifications."""
|
|
59
|
+
return (
|
|
60
|
+
_hot_reload_enabled()
|
|
61
|
+
and _env_truthy("SKILLFORGE_MCP_LIST_CHANGED", "1")
|
|
62
|
+
and _watch_interval_sec() > 0
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MCPServer:
|
|
67
|
+
"""Minimal MCP server speaking JSON-RPC 2.0 over stdio.
|
|
68
|
+
|
|
69
|
+
Implements just the methods needed for a tool server:
|
|
70
|
+
- initialize
|
|
71
|
+
- tools/list
|
|
72
|
+
- tools/call
|
|
73
|
+
- notifications/initialized (no-op)
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
self.skills = None
|
|
78
|
+
self.router = None
|
|
79
|
+
self.initialized = False
|
|
80
|
+
self._catalog_manifest: tuple[tuple[str, int], ...] | None = None
|
|
81
|
+
self._reload_lock: asyncio.Lock | None = None
|
|
82
|
+
self._db_cache: dict[str, sqlite3.Connection] = {}
|
|
83
|
+
|
|
84
|
+
def _mcp_user_id(self, args: dict) -> str:
|
|
85
|
+
"""Per-tool user namespace for weights/sessions/events (aligned with HTTP bearer user id)."""
|
|
86
|
+
raw = (
|
|
87
|
+
args.get("user_id")
|
|
88
|
+
or os.getenv("SKILLFORGE_MCP_USER_ID", "")
|
|
89
|
+
or ""
|
|
90
|
+
)
|
|
91
|
+
return str(raw).strip()
|
|
92
|
+
|
|
93
|
+
def _project_root_from_args(self, args: dict) -> str | None:
|
|
94
|
+
raw = args.get("project_root")
|
|
95
|
+
if raw is not None and str(raw).strip():
|
|
96
|
+
return str(raw).strip()
|
|
97
|
+
env = os.getenv("SKILLFORGE_PROJECT_ROOT", "").strip()
|
|
98
|
+
return env or None
|
|
99
|
+
|
|
100
|
+
def _get_con(self, args: dict):
|
|
101
|
+
path = resolve_orchestrator_db(self._project_root_from_args(args))
|
|
102
|
+
key = str(path)
|
|
103
|
+
if key not in self._db_cache:
|
|
104
|
+
self._db_cache[key] = init_db(path)
|
|
105
|
+
return self._db_cache[key]
|
|
106
|
+
|
|
107
|
+
async def setup(self):
|
|
108
|
+
if self._reload_lock is None:
|
|
109
|
+
self._reload_lock = asyncio.Lock()
|
|
110
|
+
|
|
111
|
+
self.router, self.skills = await asyncio.to_thread(
|
|
112
|
+
build_router_and_skills, log=True, log_prefix="[skillforge-mcp]"
|
|
113
|
+
)
|
|
114
|
+
self._catalog_manifest = skill_catalog_manifest()
|
|
115
|
+
if _hot_reload_enabled():
|
|
116
|
+
interval = _watch_interval_sec()
|
|
117
|
+
if interval > 0:
|
|
118
|
+
asyncio.create_task(self._watch_skills_poll_loop(interval))
|
|
119
|
+
|
|
120
|
+
def _reload_catalog_sync(self):
|
|
121
|
+
skills = load_all_skills()
|
|
122
|
+
embed_model = self.router.embed_model
|
|
123
|
+
anthropic = self.router.anthropic
|
|
124
|
+
self.router = Router(skills, embed_model, anthropic)
|
|
125
|
+
self.skills = {s.name: s for s in skills}
|
|
126
|
+
print(f"[skillforge-mcp] Hot-reloaded {len(skills)} skills", file=sys.stderr)
|
|
127
|
+
|
|
128
|
+
def _emit_tools_list_changed(self):
|
|
129
|
+
if not _mcp_tools_list_changed_capability():
|
|
130
|
+
return
|
|
131
|
+
note = {
|
|
132
|
+
"jsonrpc": "2.0",
|
|
133
|
+
"method": "notifications/tools/list_changed",
|
|
134
|
+
"params": {},
|
|
135
|
+
}
|
|
136
|
+
sys.stdout.write(json.dumps(note) + "\n")
|
|
137
|
+
sys.stdout.flush()
|
|
138
|
+
|
|
139
|
+
async def _reload_if_stale(self, *, emit_notification: bool) -> bool:
|
|
140
|
+
if not _hot_reload_enabled() or self.router is None:
|
|
141
|
+
return False
|
|
142
|
+
m = skill_catalog_manifest()
|
|
143
|
+
if m == self._catalog_manifest:
|
|
144
|
+
return False
|
|
145
|
+
if self._reload_lock is None:
|
|
146
|
+
self._reload_lock = asyncio.Lock()
|
|
147
|
+
async with self._reload_lock:
|
|
148
|
+
m2 = skill_catalog_manifest()
|
|
149
|
+
if m2 == self._catalog_manifest:
|
|
150
|
+
return False
|
|
151
|
+
await asyncio.to_thread(self._reload_catalog_sync)
|
|
152
|
+
self._catalog_manifest = skill_catalog_manifest()
|
|
153
|
+
if emit_notification:
|
|
154
|
+
self._emit_tools_list_changed()
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
async def _watch_skills_poll_loop(self, interval: float):
|
|
158
|
+
while True:
|
|
159
|
+
await asyncio.sleep(interval)
|
|
160
|
+
try:
|
|
161
|
+
if not _hot_reload_enabled():
|
|
162
|
+
return
|
|
163
|
+
await self._reload_if_stale(emit_notification=True)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"[skillforge-mcp] watch skills: {e}", file=sys.stderr)
|
|
166
|
+
|
|
167
|
+
# ---- MCP handlers ----
|
|
168
|
+
|
|
169
|
+
def handle_initialize(self, params):
|
|
170
|
+
caps: dict = {"tools": {}}
|
|
171
|
+
if _mcp_tools_list_changed_capability():
|
|
172
|
+
caps["tools"]["listChanged"] = True
|
|
173
|
+
return {
|
|
174
|
+
"protocolVersion": "2024-11-05",
|
|
175
|
+
"capabilities": caps,
|
|
176
|
+
"serverInfo": {"name": "skillforge", "version": "0.2.1"},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def handle_tools_list(self, params):
|
|
180
|
+
return {
|
|
181
|
+
"tools": [
|
|
182
|
+
{
|
|
183
|
+
"name": "route_skills",
|
|
184
|
+
"description": (
|
|
185
|
+
"Route the user's prompt to the most relevant skills from the catalog "
|
|
186
|
+
"and return their full SKILL.md bodies. The client should inject the "
|
|
187
|
+
"returned content into the LLM's context. Returns up to 7 skills. "
|
|
188
|
+
"Pass project_root (workspace path) for per-repo SQLite in .skillforge/ "
|
|
189
|
+
"and learning; else use env SKILLFORGE_PROJECT_ROOT or global data dir. "
|
|
190
|
+
"Optional session_id for reroute stats; optional user_id for multi-user."
|
|
191
|
+
),
|
|
192
|
+
"inputSchema": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"properties": {
|
|
195
|
+
"prompt": {"type": "string", "description": "The user's prompt or task description"},
|
|
196
|
+
"project_root": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"description": "Repo/workspace root — stores orchestrator state in .skillforge/",
|
|
199
|
+
},
|
|
200
|
+
"conversation": {
|
|
201
|
+
"type": "array",
|
|
202
|
+
"description": "Optional recent messages for context",
|
|
203
|
+
"items": {"type": "object"},
|
|
204
|
+
},
|
|
205
|
+
"session_id": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "Stable id for this chat; reuse across turns for reroute detection",
|
|
208
|
+
},
|
|
209
|
+
"user_id": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"description": "Logical user id for weights/sessions/events (same as HTTP user id string)",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
"required": ["prompt"],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "list_skills",
|
|
219
|
+
"description": (
|
|
220
|
+
"List all available skills with descriptions and usage stats. "
|
|
221
|
+
"Optional project_root (or SKILLFORGE_PROJECT_ROOT) selects per-repo DB; else global."
|
|
222
|
+
),
|
|
223
|
+
"inputSchema": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"properties": {
|
|
226
|
+
"project_root": {"type": "string"},
|
|
227
|
+
"user_id": {"type": "string"},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "skill_feedback",
|
|
233
|
+
"description": (
|
|
234
|
+
"Report thumbs up (+1) or down (-1) for a skill, feeding the learning loop. "
|
|
235
|
+
"Use after observing whether a routed skill actually helped."
|
|
236
|
+
),
|
|
237
|
+
"inputSchema": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"properties": {
|
|
240
|
+
"skill_name": {"type": "string"},
|
|
241
|
+
"thumbs": {"type": "integer", "enum": [-1, 1]},
|
|
242
|
+
"project_root": {"type": "string"},
|
|
243
|
+
"user_id": {"type": "string"},
|
|
244
|
+
"session_id": {"type": "string", "description": "Optional; stored in events when set"},
|
|
245
|
+
},
|
|
246
|
+
"required": ["skill_name", "thumbs"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "disable_skill",
|
|
251
|
+
"description": "Disable (or re-enable) a skill from being routed to.",
|
|
252
|
+
"inputSchema": {
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"skill_name": {"type": "string"},
|
|
256
|
+
"disabled": {"type": "boolean"},
|
|
257
|
+
"project_root": {"type": "string"},
|
|
258
|
+
"user_id": {"type": "string"},
|
|
259
|
+
},
|
|
260
|
+
"required": ["skill_name", "disabled"],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "skill_referenced",
|
|
265
|
+
"description": (
|
|
266
|
+
"Record that a routed skill was used in the model output (updates referenced count + weight). "
|
|
267
|
+
"Call when the assistant clearly applied a skill returned by route_skills."
|
|
268
|
+
),
|
|
269
|
+
"inputSchema": {
|
|
270
|
+
"type": "object",
|
|
271
|
+
"properties": {
|
|
272
|
+
"skill_name": {"type": "string"},
|
|
273
|
+
"project_root": {"type": "string"},
|
|
274
|
+
"user_id": {"type": "string"},
|
|
275
|
+
},
|
|
276
|
+
"required": ["skill_name"],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "materialize_project",
|
|
281
|
+
"description": (
|
|
282
|
+
"Write project-local Skillforge files: .cursor/rules/skillforge.mdc, "
|
|
283
|
+
"docs/SKILLFORGE-PRD.md, and a CLAUDE.md section. "
|
|
284
|
+
"Pass project_root (workspace path) and skill_names from route_skills. "
|
|
285
|
+
"Hosts must supply project_root; MCP does not infer cwd."
|
|
286
|
+
),
|
|
287
|
+
"inputSchema": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"properties": {
|
|
290
|
+
"project_root": {"type": "string", "description": "Absolute or relative path to the repo root"},
|
|
291
|
+
"skill_names": {
|
|
292
|
+
"type": "array",
|
|
293
|
+
"items": {"type": "string"},
|
|
294
|
+
"description": "Skill names from the last route_skills result",
|
|
295
|
+
},
|
|
296
|
+
"merge": {
|
|
297
|
+
"type": "boolean",
|
|
298
|
+
"description": "If false and .cursor/rules/skillforge.mdc exists, skip overwriting that file",
|
|
299
|
+
"default": True,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
"required": ["project_root", "skill_names"],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"name": "skillforge_bootstrap",
|
|
307
|
+
"description": (
|
|
308
|
+
"One-shot: route_skills for the prompt, then materialize_project into project_root. "
|
|
309
|
+
"Same args as route_skills plus project_root and optional merge."
|
|
310
|
+
),
|
|
311
|
+
"inputSchema": {
|
|
312
|
+
"type": "object",
|
|
313
|
+
"properties": {
|
|
314
|
+
"prompt": {"type": "string"},
|
|
315
|
+
"project_root": {"type": "string"},
|
|
316
|
+
"conversation": {"type": "array", "items": {"type": "object"}},
|
|
317
|
+
"session_id": {"type": "string"},
|
|
318
|
+
"user_id": {"type": "string"},
|
|
319
|
+
"merge": {"type": "boolean", "default": True},
|
|
320
|
+
},
|
|
321
|
+
"required": ["prompt", "project_root"],
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async def handle_tools_call(self, params):
|
|
328
|
+
name = params.get("name")
|
|
329
|
+
args = params.get("arguments", {})
|
|
330
|
+
|
|
331
|
+
if name == "route_skills":
|
|
332
|
+
return await self._tool_route_skills(args)
|
|
333
|
+
if name == "list_skills":
|
|
334
|
+
return self._tool_list_skills(args)
|
|
335
|
+
if name == "skill_feedback":
|
|
336
|
+
return self._tool_skill_feedback(args)
|
|
337
|
+
if name == "disable_skill":
|
|
338
|
+
return self._tool_disable_skill(args)
|
|
339
|
+
if name == "skill_referenced":
|
|
340
|
+
return self._tool_skill_referenced(args)
|
|
341
|
+
if name == "materialize_project":
|
|
342
|
+
return self._tool_materialize_project(args)
|
|
343
|
+
if name == "skillforge_bootstrap":
|
|
344
|
+
return await self._tool_skillforge_bootstrap(args)
|
|
345
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
346
|
+
|
|
347
|
+
async def _tool_route_skills(self, args):
|
|
348
|
+
prompt = args.get("prompt", "")
|
|
349
|
+
conversation = args.get("conversation", [])
|
|
350
|
+
session_id = args.get("session_id") or None
|
|
351
|
+
user_id = self._mcp_user_id(args)
|
|
352
|
+
if not prompt.strip():
|
|
353
|
+
return {"content": [{"type": "text", "text": "No prompt provided."}]}
|
|
354
|
+
con = self._get_con(args)
|
|
355
|
+
result = await run_route_turn(
|
|
356
|
+
con,
|
|
357
|
+
self.router,
|
|
358
|
+
prompt,
|
|
359
|
+
conversation,
|
|
360
|
+
user_id=user_id,
|
|
361
|
+
session_id=session_id,
|
|
362
|
+
)
|
|
363
|
+
picked_names = result["picked_names"]
|
|
364
|
+
reasoning = result["reasoning"]
|
|
365
|
+
pr = self._project_root_from_args(args)
|
|
366
|
+
db_path = resolve_orchestrator_db(pr)
|
|
367
|
+
if pr:
|
|
368
|
+
try:
|
|
369
|
+
d = Path(pr).expanduser().resolve() / ".skillforge"
|
|
370
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
371
|
+
snap = {
|
|
372
|
+
"ts": time.time(),
|
|
373
|
+
"session_id": result["session_id"],
|
|
374
|
+
"picked": picked_names,
|
|
375
|
+
"reasoning": reasoning,
|
|
376
|
+
"route_ms": round(result["route_ms"], 1),
|
|
377
|
+
"user_id": user_id,
|
|
378
|
+
}
|
|
379
|
+
(d / "last_route.json").write_text(json.dumps(snap, indent=2), encoding="utf-8")
|
|
380
|
+
except OSError:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
# Build response: a header explaining what was loaded, then the skill bodies
|
|
384
|
+
blocks = [
|
|
385
|
+
f"# Skillforge — routed {len(picked_names)} skill(s)",
|
|
386
|
+
f"_DB:_ `{db_path}`",
|
|
387
|
+
f"_Reasoning: {reasoning}_" if reasoning else "",
|
|
388
|
+
"",
|
|
389
|
+
]
|
|
390
|
+
for n in picked_names:
|
|
391
|
+
s = self.skills.get(n)
|
|
392
|
+
if s:
|
|
393
|
+
blocks.append(f"---\n## Skill: {s.name}\n\n{s.body}\n")
|
|
394
|
+
if not picked_names:
|
|
395
|
+
blocks.append("_No skills matched this prompt closely enough to load._")
|
|
396
|
+
return {
|
|
397
|
+
"content": [{"type": "text", "text": "\n".join(b for b in blocks if b is not None)}],
|
|
398
|
+
"_meta": {
|
|
399
|
+
"picked": picked_names,
|
|
400
|
+
"reasoning": reasoning,
|
|
401
|
+
"session_id": result["session_id"],
|
|
402
|
+
"user_id": user_id,
|
|
403
|
+
"rerouted": result["rerouted"],
|
|
404
|
+
"change_pct": round(result["change"] * 100, 1),
|
|
405
|
+
"route_ms": round(result["route_ms"], 1),
|
|
406
|
+
"orchestrator_db": str(db_path),
|
|
407
|
+
},
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
def _tool_list_skills(self, args):
|
|
411
|
+
user_id = self._mcp_user_id(args)
|
|
412
|
+
con = self._get_con(args)
|
|
413
|
+
out = []
|
|
414
|
+
for name, s in sorted(self.skills.items()):
|
|
415
|
+
cur = con.execute(
|
|
416
|
+
"SELECT uses, referenced, thumbs_up, thumbs_down, disabled FROM skill_weights "
|
|
417
|
+
"WHERE user_id = ? AND skill_name = ?",
|
|
418
|
+
(user_id, name),
|
|
419
|
+
)
|
|
420
|
+
row = cur.fetchone()
|
|
421
|
+
uses, ref, up, down, disabled = row if row else (0, 0, 0, 0, 0)
|
|
422
|
+
out.append({
|
|
423
|
+
"name": name,
|
|
424
|
+
"description": s.description[:200],
|
|
425
|
+
"source": s.source,
|
|
426
|
+
"uses": uses,
|
|
427
|
+
"thumbs": up - down,
|
|
428
|
+
"disabled": bool(disabled),
|
|
429
|
+
})
|
|
430
|
+
# Format as readable text for MCP clients
|
|
431
|
+
lines = [f"# Skills ({len(out)} total)\n"]
|
|
432
|
+
for sk in out:
|
|
433
|
+
flag = " [DISABLED]" if sk["disabled"] else ""
|
|
434
|
+
lines.append(f"**{sk['name']}**{flag} ({sk['source']}, used {sk['uses']}x): {sk['description']}")
|
|
435
|
+
return {"content": [{"type": "text", "text": "\n".join(lines)}]}
|
|
436
|
+
|
|
437
|
+
def _tool_skill_feedback(self, args):
|
|
438
|
+
name = args.get("skill_name")
|
|
439
|
+
thumbs = args.get("thumbs", 0)
|
|
440
|
+
user_id = self._mcp_user_id(args)
|
|
441
|
+
session_id = args.get("session_id") or ""
|
|
442
|
+
con = self._get_con(args)
|
|
443
|
+
if name not in self.skills:
|
|
444
|
+
return {"content": [{"type": "text", "text": f"Unknown skill: {name}"}], "isError": True}
|
|
445
|
+
field = "thumbs_up" if thumbs > 0 else "thumbs_down"
|
|
446
|
+
update_skill_stat(con, name, field, 1, user_id=user_id)
|
|
447
|
+
if session_id:
|
|
448
|
+
log_event(
|
|
449
|
+
con,
|
|
450
|
+
session_id,
|
|
451
|
+
"feedback",
|
|
452
|
+
{"skill": name, "thumbs": thumbs},
|
|
453
|
+
user_id=user_id,
|
|
454
|
+
)
|
|
455
|
+
return {"content": [{"type": "text", "text": f"Recorded {'👍' if thumbs > 0 else '👎'} for {name}"}]}
|
|
456
|
+
|
|
457
|
+
def _tool_disable_skill(self, args):
|
|
458
|
+
name = args.get("skill_name")
|
|
459
|
+
disabled = args.get("disabled", False)
|
|
460
|
+
user_id = self._mcp_user_id(args)
|
|
461
|
+
con = self._get_con(args)
|
|
462
|
+
if name not in self.skills:
|
|
463
|
+
return {"content": [{"type": "text", "text": f"Unknown skill: {name}"}], "isError": True}
|
|
464
|
+
set_skill_disabled(con, name, disabled, user_id=user_id)
|
|
465
|
+
return {"content": [{"type": "text", "text": f"{'Disabled' if disabled else 'Enabled'} {name}"}]}
|
|
466
|
+
|
|
467
|
+
def _tool_skill_referenced(self, args):
|
|
468
|
+
name = args.get("skill_name")
|
|
469
|
+
user_id = self._mcp_user_id(args)
|
|
470
|
+
con = self._get_con(args)
|
|
471
|
+
if name not in self.skills:
|
|
472
|
+
return {"content": [{"type": "text", "text": f"Unknown skill: {name}"}], "isError": True}
|
|
473
|
+
update_skill_stat(con, name, "referenced", 1, user_id=user_id)
|
|
474
|
+
return {"content": [{"type": "text", "text": f"Recorded reference for {name}"}]}
|
|
475
|
+
|
|
476
|
+
def _tool_materialize_project(self, args):
|
|
477
|
+
root = (args.get("project_root") or "").strip()
|
|
478
|
+
names_raw = args.get("skill_names") or []
|
|
479
|
+
merge = args.get("merge", True)
|
|
480
|
+
if not root:
|
|
481
|
+
return {
|
|
482
|
+
"content": [{"type": "text", "text": "project_root is required."}],
|
|
483
|
+
"isError": True,
|
|
484
|
+
}
|
|
485
|
+
if not isinstance(names_raw, list):
|
|
486
|
+
names_raw = []
|
|
487
|
+
valid = [n for n in names_raw if isinstance(n, str) and n in self.skills]
|
|
488
|
+
desc = {n: self.skills[n].description for n in valid}
|
|
489
|
+
try:
|
|
490
|
+
out = materialize_project_files(root, valid, desc, merge=bool(merge))
|
|
491
|
+
except ValueError as e:
|
|
492
|
+
return {"content": [{"type": "text", "text": str(e)}], "isError": True}
|
|
493
|
+
lines = ["# Skillforge — materialized project files", "", "Written:", *[f"- {p}" for p in out["written"]], ""]
|
|
494
|
+
return {"content": [{"type": "text", "text": "\n".join(lines)}], "_meta": out}
|
|
495
|
+
|
|
496
|
+
async def _tool_skillforge_bootstrap(self, args):
|
|
497
|
+
prompt = args.get("prompt", "")
|
|
498
|
+
root = (args.get("project_root") or "").strip()
|
|
499
|
+
conversation = args.get("conversation", [])
|
|
500
|
+
session_id = args.get("session_id") or None
|
|
501
|
+
user_id = self._mcp_user_id(args)
|
|
502
|
+
merge = args.get("merge", True)
|
|
503
|
+
if not prompt.strip():
|
|
504
|
+
return {"content": [{"type": "text", "text": "No prompt provided."}], "isError": True}
|
|
505
|
+
if not root:
|
|
506
|
+
return {
|
|
507
|
+
"content": [{"type": "text", "text": "project_root is required."}],
|
|
508
|
+
"isError": True,
|
|
509
|
+
}
|
|
510
|
+
route = await self._tool_route_skills(
|
|
511
|
+
{
|
|
512
|
+
"prompt": prompt,
|
|
513
|
+
"project_root": root,
|
|
514
|
+
"conversation": conversation,
|
|
515
|
+
"session_id": session_id,
|
|
516
|
+
"user_id": user_id,
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
if route.get("isError"):
|
|
520
|
+
return route
|
|
521
|
+
picked = (route.get("_meta") or {}).get("picked") or []
|
|
522
|
+
mat = self._tool_materialize_project(
|
|
523
|
+
{"project_root": root, "skill_names": picked, "merge": merge}
|
|
524
|
+
)
|
|
525
|
+
if mat.get("isError"):
|
|
526
|
+
return mat
|
|
527
|
+
body = [
|
|
528
|
+
route["content"][0]["text"],
|
|
529
|
+
"---",
|
|
530
|
+
mat["content"][0]["text"],
|
|
531
|
+
]
|
|
532
|
+
return {
|
|
533
|
+
"content": [{"type": "text", "text": "\n".join(body)}],
|
|
534
|
+
"_meta": {
|
|
535
|
+
"route": route.get("_meta"),
|
|
536
|
+
"materialize": mat.get("_meta"),
|
|
537
|
+
},
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
# ---- JSON-RPC dispatcher ----
|
|
541
|
+
|
|
542
|
+
async def dispatch(self, request):
|
|
543
|
+
method = request.get("method")
|
|
544
|
+
params = request.get("params", {})
|
|
545
|
+
req_id = request.get("id")
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
if method == "initialize":
|
|
549
|
+
result = self.handle_initialize(params)
|
|
550
|
+
elif method == "notifications/initialized":
|
|
551
|
+
# Notification, no response expected. Now finish heavy setup.
|
|
552
|
+
if not self.initialized:
|
|
553
|
+
self.initialized = True
|
|
554
|
+
await self.setup()
|
|
555
|
+
return None
|
|
556
|
+
elif method == "tools/list":
|
|
557
|
+
if not self.initialized:
|
|
558
|
+
await self.setup()
|
|
559
|
+
self.initialized = True
|
|
560
|
+
await self._reload_if_stale(emit_notification=False)
|
|
561
|
+
result = self.handle_tools_list(params)
|
|
562
|
+
elif method == "tools/call":
|
|
563
|
+
if not self.initialized:
|
|
564
|
+
await self.setup()
|
|
565
|
+
self.initialized = True
|
|
566
|
+
await self._reload_if_stale(emit_notification=False)
|
|
567
|
+
result = await self.handle_tools_call(params)
|
|
568
|
+
else:
|
|
569
|
+
if req_id is None:
|
|
570
|
+
return None
|
|
571
|
+
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
572
|
+
|
|
573
|
+
if req_id is None:
|
|
574
|
+
return None # notification
|
|
575
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": result}
|
|
576
|
+
except Exception as e:
|
|
577
|
+
if req_id is None:
|
|
578
|
+
return None
|
|
579
|
+
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32603, "message": str(e)}}
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
async def main():
|
|
583
|
+
server = MCPServer()
|
|
584
|
+
loop = asyncio.get_event_loop()
|
|
585
|
+
reader = asyncio.StreamReader()
|
|
586
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
587
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
588
|
+
|
|
589
|
+
while True:
|
|
590
|
+
try:
|
|
591
|
+
line = await reader.readline()
|
|
592
|
+
except Exception:
|
|
593
|
+
break
|
|
594
|
+
if not line:
|
|
595
|
+
break
|
|
596
|
+
try:
|
|
597
|
+
request = json.loads(line.decode().strip())
|
|
598
|
+
except json.JSONDecodeError:
|
|
599
|
+
continue
|
|
600
|
+
response = await server.dispatch(request)
|
|
601
|
+
if response is not None:
|
|
602
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
603
|
+
sys.stdout.flush()
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if __name__ == "__main__":
|
|
607
|
+
try:
|
|
608
|
+
asyncio.run(main())
|
|
609
|
+
except KeyboardInterrupt:
|
|
610
|
+
pass
|