@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.
Files changed (402) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/CODE_OF_CONDUCT.md +34 -0
  3. package/CONTRIBUTING.md +38 -0
  4. package/LICENSE +21 -0
  5. package/README.md +337 -0
  6. package/RELEASING.md +93 -0
  7. package/SECURITY.md +31 -0
  8. package/STRATEGY.md +26 -0
  9. package/bin/cli.js +547 -0
  10. package/lib/packs.js +184 -0
  11. package/package.json +38 -0
  12. package/python/app/__init__.py +0 -0
  13. package/python/app/__pycache__/__init__.cpython-312.pyc +0 -0
  14. package/python/app/__pycache__/auth.cpython-312.pyc +0 -0
  15. package/python/app/__pycache__/main.cpython-312.pyc +0 -0
  16. package/python/app/auth.py +63 -0
  17. package/python/app/cli.py +78 -0
  18. package/python/app/db_paths.py +26 -0
  19. package/python/app/events_cli.py +175 -0
  20. package/python/app/main.py +647 -0
  21. package/python/app/materialize.py +138 -0
  22. package/python/app/mcp_server.py +610 -0
  23. package/python/app/route_cli.py +117 -0
  24. package/python/requirements-dev.txt +1 -0
  25. package/python/requirements.txt +7 -0
  26. package/python/tests/test_db_paths.py +41 -0
  27. package/skills/accessibility/SKILL.md +145 -0
  28. package/skills/agent-architecture-audit/SKILL.md +256 -0
  29. package/skills/agent-eval/SKILL.md +144 -0
  30. package/skills/agent-harness-construction/SKILL.md +72 -0
  31. package/skills/agent-introspection-debugging/SKILL.md +152 -0
  32. package/skills/agent-payment-x402/SKILL.md +224 -0
  33. package/skills/agent-sort/SKILL.md +214 -0
  34. package/skills/agentic-engineering/SKILL.md +62 -0
  35. package/skills/agentic-os/SKILL.md +386 -0
  36. package/skills/ai-first-engineering/SKILL.md +50 -0
  37. package/skills/ai-regression-testing/SKILL.md +384 -0
  38. package/skills/android-clean-architecture/SKILL.md +338 -0
  39. package/skills/angular-developer/SKILL.md +153 -0
  40. package/skills/angular-developer/references/angular-animations.md +160 -0
  41. package/skills/angular-developer/references/angular-aria.md +410 -0
  42. package/skills/angular-developer/references/cli.md +86 -0
  43. package/skills/angular-developer/references/component-harnesses.md +59 -0
  44. package/skills/angular-developer/references/component-styling.md +91 -0
  45. package/skills/angular-developer/references/components.md +117 -0
  46. package/skills/angular-developer/references/creating-services.md +97 -0
  47. package/skills/angular-developer/references/data-resolvers.md +69 -0
  48. package/skills/angular-developer/references/define-routes.md +67 -0
  49. package/skills/angular-developer/references/defining-providers.md +72 -0
  50. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  51. package/skills/angular-developer/references/e2e-testing.md +56 -0
  52. package/skills/angular-developer/references/effects.md +83 -0
  53. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  54. package/skills/angular-developer/references/host-elements.md +80 -0
  55. package/skills/angular-developer/references/injection-context.md +63 -0
  56. package/skills/angular-developer/references/inputs.md +101 -0
  57. package/skills/angular-developer/references/linked-signal.md +59 -0
  58. package/skills/angular-developer/references/loading-strategies.md +61 -0
  59. package/skills/angular-developer/references/mcp.md +108 -0
  60. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  61. package/skills/angular-developer/references/outputs.md +86 -0
  62. package/skills/angular-developer/references/reactive-forms.md +122 -0
  63. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  64. package/skills/angular-developer/references/resource.md +77 -0
  65. package/skills/angular-developer/references/route-animations.md +56 -0
  66. package/skills/angular-developer/references/route-guards.md +52 -0
  67. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  68. package/skills/angular-developer/references/router-testing.md +87 -0
  69. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  70. package/skills/angular-developer/references/signal-forms.md +795 -0
  71. package/skills/angular-developer/references/signals-overview.md +94 -0
  72. package/skills/angular-developer/references/tailwind-css.md +69 -0
  73. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  74. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  75. package/skills/api-connector-builder/SKILL.md +120 -0
  76. package/skills/api-design/SKILL.md +522 -0
  77. package/skills/architecture-decision-records/SKILL.md +178 -0
  78. package/skills/article-writing/SKILL.md +78 -0
  79. package/skills/automation-audit-ops/SKILL.md +141 -0
  80. package/skills/autonomous-agent-harness/SKILL.md +272 -0
  81. package/skills/autonomous-loops/SKILL.md +609 -0
  82. package/skills/backend-patterns/SKILL.md +560 -0
  83. package/skills/benchmark/SKILL.md +92 -0
  84. package/skills/blueprint/SKILL.md +104 -0
  85. package/skills/browser-qa/SKILL.md +86 -0
  86. package/skills/bun-runtime/SKILL.md +83 -0
  87. package/skills/canary-watch/SKILL.md +98 -0
  88. package/skills/carrier-relationship-management/SKILL.md +211 -0
  89. package/skills/cisco-ios-patterns/SKILL.md +163 -0
  90. package/skills/ck/SKILL.md +147 -0
  91. package/skills/ck/commands/forget.mjs +44 -0
  92. package/skills/ck/commands/info.mjs +24 -0
  93. package/skills/ck/commands/init.mjs +143 -0
  94. package/skills/ck/commands/list.mjs +40 -0
  95. package/skills/ck/commands/migrate.mjs +202 -0
  96. package/skills/ck/commands/resume.mjs +36 -0
  97. package/skills/ck/commands/save.mjs +210 -0
  98. package/skills/ck/commands/shared.mjs +387 -0
  99. package/skills/ck/hooks/session-start.mjs +224 -0
  100. package/skills/claude-devfleet/SKILL.md +103 -0
  101. package/skills/click-path-audit/SKILL.md +244 -0
  102. package/skills/clickhouse-io/SKILL.md +438 -0
  103. package/skills/code-tour/SKILL.md +235 -0
  104. package/skills/codebase-onboarding/SKILL.md +232 -0
  105. package/skills/coding-standards/SKILL.md +548 -0
  106. package/skills/compose-multiplatform-patterns/SKILL.md +298 -0
  107. package/skills/connections-optimizer/SKILL.md +188 -0
  108. package/skills/content-engine/SKILL.md +126 -0
  109. package/skills/content-hash-cache-pattern/SKILL.md +160 -0
  110. package/skills/context-budget/SKILL.md +134 -0
  111. package/skills/continuous-agent-loop/SKILL.md +44 -0
  112. package/skills/continuous-learning/SKILL.md +129 -0
  113. package/skills/continuous-learning/config.json +18 -0
  114. package/skills/continuous-learning/evaluate-session.sh +69 -0
  115. package/skills/continuous-learning-v2/SKILL.md +358 -0
  116. package/skills/continuous-learning-v2/agents/observer-loop.sh +322 -0
  117. package/skills/continuous-learning-v2/agents/observer.md +198 -0
  118. package/skills/continuous-learning-v2/agents/session-guardian.sh +150 -0
  119. package/skills/continuous-learning-v2/agents/start-observer.sh +248 -0
  120. package/skills/continuous-learning-v2/config.json +8 -0
  121. package/skills/continuous-learning-v2/hooks/observe.sh +476 -0
  122. package/skills/continuous-learning-v2/scripts/detect-project.sh +288 -0
  123. package/skills/continuous-learning-v2/scripts/instinct-cli.py +1519 -0
  124. package/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +31 -0
  125. package/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +62 -0
  126. package/skills/continuous-learning-v2/scripts/test_parse_instinct.py +1018 -0
  127. package/skills/cost-aware-llm-pipeline/SKILL.md +182 -0
  128. package/skills/cost-tracking/SKILL.md +147 -0
  129. package/skills/council/SKILL.md +202 -0
  130. package/skills/cpp-coding-standards/SKILL.md +722 -0
  131. package/skills/cpp-testing/SKILL.md +323 -0
  132. package/skills/crosspost/SKILL.md +110 -0
  133. package/skills/csharp-testing/SKILL.md +320 -0
  134. package/skills/customer-billing-ops/SKILL.md +139 -0
  135. package/skills/customs-trade-compliance/SKILL.md +262 -0
  136. package/skills/dart-flutter-patterns/SKILL.md +562 -0
  137. package/skills/dashboard-builder/SKILL.md +108 -0
  138. package/skills/data-scraper-agent/SKILL.md +764 -0
  139. package/skills/database-migrations/SKILL.md +428 -0
  140. package/skills/deep-research/SKILL.md +158 -0
  141. package/skills/defi-amm-security/SKILL.md +166 -0
  142. package/skills/deployment-patterns/SKILL.md +426 -0
  143. package/skills/design-system/SKILL.md +81 -0
  144. package/skills/django-celery/SKILL.md +456 -0
  145. package/skills/django-patterns/SKILL.md +733 -0
  146. package/skills/django-security/SKILL.md +592 -0
  147. package/skills/django-tdd/SKILL.md +728 -0
  148. package/skills/django-verification/SKILL.md +468 -0
  149. package/skills/dmux-workflows/SKILL.md +190 -0
  150. package/skills/docker-patterns/SKILL.md +363 -0
  151. package/skills/documentation-lookup/SKILL.md +89 -0
  152. package/skills/dotnet-patterns/SKILL.md +320 -0
  153. package/skills/e2e-testing/SKILL.md +325 -0
  154. package/skills/email-ops/SKILL.md +120 -0
  155. package/skills/energy-procurement/SKILL.md +227 -0
  156. package/skills/enterprise-agent-ops/SKILL.md +49 -0
  157. package/skills/error-handling/SKILL.md +375 -0
  158. package/skills/eval-harness/SKILL.md +269 -0
  159. package/skills/evm-token-decimals/SKILL.md +130 -0
  160. package/skills/exa-search/SKILL.md +106 -0
  161. package/skills/fal-ai-media/SKILL.md +287 -0
  162. package/skills/fastapi-patterns/SKILL.md +327 -0
  163. package/skills/finance-billing-ops/SKILL.md +126 -0
  164. package/skills/flox-environments/SKILL.md +496 -0
  165. package/skills/flutter-dart-code-review/SKILL.md +434 -0
  166. package/skills/foundation-models-on-device/SKILL.md +243 -0
  167. package/skills/frontend-design-direction/SKILL.md +92 -0
  168. package/skills/frontend-patterns/SKILL.md +641 -0
  169. package/skills/frontend-slides/SKILL.md +183 -0
  170. package/skills/frontend-slides/STYLE_PRESETS.md +330 -0
  171. package/skills/frontend-slides/animation-patterns.md +122 -0
  172. package/skills/frontend-slides/html-template.md +419 -0
  173. package/skills/frontend-slides/scripts/export-pdf.sh +418 -0
  174. package/skills/frontend-slides/scripts/extract-pptx.py +96 -0
  175. package/skills/frontend-slides/viewport-base.css +153 -0
  176. package/skills/fsharp-testing/SKILL.md +279 -0
  177. package/skills/gan-style-harness/SKILL.md +278 -0
  178. package/skills/gateguard/SKILL.md +125 -0
  179. package/skills/git-workflow/SKILL.md +714 -0
  180. package/skills/github-ops/SKILL.md +143 -0
  181. package/skills/golang-patterns/SKILL.md +673 -0
  182. package/skills/golang-testing/SKILL.md +719 -0
  183. package/skills/google-workspace-ops/SKILL.md +94 -0
  184. package/skills/healthcare-cdss-patterns/SKILL.md +245 -0
  185. package/skills/healthcare-emr-patterns/SKILL.md +159 -0
  186. package/skills/healthcare-eval-harness/SKILL.md +207 -0
  187. package/skills/healthcare-phi-compliance/SKILL.md +145 -0
  188. package/skills/hermes-imports/SKILL.md +87 -0
  189. package/skills/hexagonal-architecture/SKILL.md +275 -0
  190. package/skills/hipaa-compliance/SKILL.md +78 -0
  191. package/skills/homelab-network-readiness/SKILL.md +169 -0
  192. package/skills/homelab-network-setup/SKILL.md +129 -0
  193. package/skills/homelab-pihole-dns/SKILL.md +274 -0
  194. package/skills/homelab-vlan-segmentation/SKILL.md +311 -0
  195. package/skills/homelab-wireguard-vpn/SKILL.md +305 -0
  196. package/skills/hookify-rules/SKILL.md +128 -0
  197. package/skills/inventory-demand-planning/SKILL.md +246 -0
  198. package/skills/investor-materials/SKILL.md +95 -0
  199. package/skills/investor-outreach/SKILL.md +90 -0
  200. package/skills/ios-icon-gen/SKILL.md +157 -0
  201. package/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
  202. package/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
  203. package/skills/iterative-retrieval/SKILL.md +209 -0
  204. package/skills/java-coding-standards/SKILL.md +382 -0
  205. package/skills/jira-integration/SKILL.md +292 -0
  206. package/skills/jpa-patterns/SKILL.md +150 -0
  207. package/skills/knowledge-ops/SKILL.md +153 -0
  208. package/skills/kotlin-coroutines-flows/SKILL.md +283 -0
  209. package/skills/kotlin-exposed-patterns/SKILL.md +718 -0
  210. package/skills/kotlin-ktor-patterns/SKILL.md +688 -0
  211. package/skills/kotlin-patterns/SKILL.md +710 -0
  212. package/skills/kotlin-testing/SKILL.md +823 -0
  213. package/skills/laravel-patterns/SKILL.md +414 -0
  214. package/skills/laravel-plugin-discovery/SKILL.md +228 -0
  215. package/skills/laravel-security/SKILL.md +284 -0
  216. package/skills/laravel-tdd/SKILL.md +282 -0
  217. package/skills/laravel-verification/SKILL.md +178 -0
  218. package/skills/lead-intelligence/SKILL.md +320 -0
  219. package/skills/lead-intelligence/agents/enrichment-agent.md +85 -0
  220. package/skills/lead-intelligence/agents/mutual-mapper.md +75 -0
  221. package/skills/lead-intelligence/agents/outreach-drafter.md +98 -0
  222. package/skills/lead-intelligence/agents/signal-scorer.md +60 -0
  223. package/skills/liquid-glass-design/SKILL.md +279 -0
  224. package/skills/llm-trading-agent-security/SKILL.md +146 -0
  225. package/skills/logistics-exception-management/SKILL.md +221 -0
  226. package/skills/make-interfaces-feel-better/SKILL.md +151 -0
  227. package/skills/manim-video/SKILL.md +88 -0
  228. package/skills/manim-video/assets/network_graph_scene.py +52 -0
  229. package/skills/market-research/SKILL.md +74 -0
  230. package/skills/mcp-server-patterns/SKILL.md +68 -0
  231. package/skills/messages-ops/SKILL.md +103 -0
  232. package/skills/mle-workflow/SKILL.md +345 -0
  233. package/skills/motion-advanced/SKILL.md +596 -0
  234. package/skills/motion-foundations/SKILL.md +299 -0
  235. package/skills/motion-patterns/SKILL.md +435 -0
  236. package/skills/motion-ui/SKILL.md +574 -0
  237. package/skills/mysql-patterns/SKILL.md +411 -0
  238. package/skills/nanoclaw-repl/SKILL.md +32 -0
  239. package/skills/nestjs-patterns/SKILL.md +229 -0
  240. package/skills/netmiko-ssh-automation/SKILL.md +173 -0
  241. package/skills/network-bgp-diagnostics/SKILL.md +167 -0
  242. package/skills/network-config-validation/SKILL.md +210 -0
  243. package/skills/network-interface-health/SKILL.md +152 -0
  244. package/skills/nextjs-turbopack/SKILL.md +43 -0
  245. package/skills/nodejs-keccak256/SKILL.md +102 -0
  246. package/skills/nutrient-document-processing/SKILL.md +166 -0
  247. package/skills/nuxt4-patterns/SKILL.md +99 -0
  248. package/skills/openclaw-persona-forge/SKILL.md +288 -0
  249. package/skills/openclaw-persona-forge/gacha.py +224 -0
  250. package/skills/openclaw-persona-forge/gacha.sh +5 -0
  251. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  252. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  253. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  254. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  255. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  256. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  257. package/skills/opensource-pipeline/SKILL.md +254 -0
  258. package/skills/perl-patterns/SKILL.md +503 -0
  259. package/skills/perl-security/SKILL.md +502 -0
  260. package/skills/perl-testing/SKILL.md +474 -0
  261. package/skills/plan-orchestrate/SKILL.md +253 -0
  262. package/skills/plankton-code-quality/SKILL.md +236 -0
  263. package/skills/postgres-patterns/SKILL.md +146 -0
  264. package/skills/product-capability/SKILL.md +140 -0
  265. package/skills/product-lens/SKILL.md +91 -0
  266. package/skills/production-audit/SKILL.md +206 -0
  267. package/skills/production-scheduling/SKILL.md +237 -0
  268. package/skills/project-flow-ops/SKILL.md +110 -0
  269. package/skills/prompt-optimizer/SKILL.md +398 -0
  270. package/skills/python-patterns/SKILL.md +749 -0
  271. package/skills/python-testing/SKILL.md +815 -0
  272. package/skills/pytorch-patterns/SKILL.md +395 -0
  273. package/skills/quality-nonconformance/SKILL.md +259 -0
  274. package/skills/quarkus-patterns/SKILL.md +721 -0
  275. package/skills/quarkus-security/SKILL.md +466 -0
  276. package/skills/quarkus-tdd/SKILL.md +810 -0
  277. package/skills/quarkus-verification/SKILL.md +478 -0
  278. package/skills/ralphinho-rfc-pipeline/SKILL.md +66 -0
  279. package/skills/redis-patterns/SKILL.md +402 -0
  280. package/skills/regex-vs-llm-structured-text/SKILL.md +219 -0
  281. package/skills/remotion-video-creation/SKILL.md +43 -0
  282. package/skills/remotion-video-creation/rules/3d.md +86 -0
  283. package/skills/remotion-video-creation/rules/animations.md +29 -0
  284. package/skills/remotion-video-creation/rules/assets/charts-bar-chart.tsx +173 -0
  285. package/skills/remotion-video-creation/rules/assets/text-animations-typewriter.tsx +100 -0
  286. package/skills/remotion-video-creation/rules/assets/text-animations-word-highlight.tsx +108 -0
  287. package/skills/remotion-video-creation/rules/assets.md +78 -0
  288. package/skills/remotion-video-creation/rules/audio.md +172 -0
  289. package/skills/remotion-video-creation/rules/calculate-metadata.md +104 -0
  290. package/skills/remotion-video-creation/rules/can-decode.md +75 -0
  291. package/skills/remotion-video-creation/rules/charts.md +58 -0
  292. package/skills/remotion-video-creation/rules/compositions.md +146 -0
  293. package/skills/remotion-video-creation/rules/display-captions.md +126 -0
  294. package/skills/remotion-video-creation/rules/extract-frames.md +229 -0
  295. package/skills/remotion-video-creation/rules/fonts.md +152 -0
  296. package/skills/remotion-video-creation/rules/get-audio-duration.md +58 -0
  297. package/skills/remotion-video-creation/rules/get-video-dimensions.md +68 -0
  298. package/skills/remotion-video-creation/rules/get-video-duration.md +58 -0
  299. package/skills/remotion-video-creation/rules/gifs.md +138 -0
  300. package/skills/remotion-video-creation/rules/images.md +130 -0
  301. package/skills/remotion-video-creation/rules/import-srt-captions.md +67 -0
  302. package/skills/remotion-video-creation/rules/lottie.md +67 -0
  303. package/skills/remotion-video-creation/rules/measuring-dom-nodes.md +34 -0
  304. package/skills/remotion-video-creation/rules/measuring-text.md +143 -0
  305. package/skills/remotion-video-creation/rules/sequencing.md +106 -0
  306. package/skills/remotion-video-creation/rules/tailwind.md +11 -0
  307. package/skills/remotion-video-creation/rules/text-animations.md +20 -0
  308. package/skills/remotion-video-creation/rules/timing.md +179 -0
  309. package/skills/remotion-video-creation/rules/transcribe-captions.md +19 -0
  310. package/skills/remotion-video-creation/rules/transitions.md +122 -0
  311. package/skills/remotion-video-creation/rules/trimming.md +52 -0
  312. package/skills/remotion-video-creation/rules/videos.md +171 -0
  313. package/skills/repo-scan/SKILL.md +78 -0
  314. package/skills/research-ops/SKILL.md +111 -0
  315. package/skills/returns-reverse-logistics/SKILL.md +239 -0
  316. package/skills/rules-distill/SKILL.md +263 -0
  317. package/skills/rules-distill/scripts/scan-rules.sh +58 -0
  318. package/skills/rules-distill/scripts/scan-skills.sh +129 -0
  319. package/skills/rust-patterns/SKILL.md +498 -0
  320. package/skills/rust-testing/SKILL.md +499 -0
  321. package/skills/safety-guard/SKILL.md +74 -0
  322. package/skills/santa-method/SKILL.md +306 -0
  323. package/skills/scientific-db-pubmed-database/SKILL.md +175 -0
  324. package/skills/scientific-db-uspto-database/SKILL.md +177 -0
  325. package/skills/scientific-pkg-gget/SKILL.md +166 -0
  326. package/skills/scientific-thinking-literature-review/SKILL.md +192 -0
  327. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +160 -0
  328. package/skills/search-first/SKILL.md +181 -0
  329. package/skills/security-bounty-hunter/SKILL.md +99 -0
  330. package/skills/security-review/SKILL.md +502 -0
  331. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  332. package/skills/seo/SKILL.md +153 -0
  333. package/skills/skill-comply/SKILL.md +57 -0
  334. package/skills/skill-comply/fixtures/compliant_trace.jsonl +5 -0
  335. package/skills/skill-comply/fixtures/noncompliant_trace.jsonl +3 -0
  336. package/skills/skill-comply/fixtures/tdd_spec.yaml +44 -0
  337. package/skills/skill-comply/prompts/classifier.md +24 -0
  338. package/skills/skill-comply/prompts/scenario_generator.md +62 -0
  339. package/skills/skill-comply/prompts/spec_generator.md +42 -0
  340. package/skills/skill-comply/pyproject.toml +15 -0
  341. package/skills/skill-comply/scripts/__init__.py +0 -0
  342. package/skills/skill-comply/scripts/classifier.py +85 -0
  343. package/skills/skill-comply/scripts/grader.py +124 -0
  344. package/skills/skill-comply/scripts/parser.py +107 -0
  345. package/skills/skill-comply/scripts/report.py +170 -0
  346. package/skills/skill-comply/scripts/run.py +127 -0
  347. package/skills/skill-comply/scripts/runner.py +186 -0
  348. package/skills/skill-comply/scripts/scenario_generator.py +70 -0
  349. package/skills/skill-comply/scripts/spec_generator.py +72 -0
  350. package/skills/skill-comply/scripts/utils.py +13 -0
  351. package/skills/skill-comply/tests/test_grader.py +197 -0
  352. package/skills/skill-comply/tests/test_parser.py +90 -0
  353. package/skills/skill-comply/tests/test_runner.py +172 -0
  354. package/skills/skill-scout/SKILL.md +139 -0
  355. package/skills/skill-stocktake/SKILL.md +193 -0
  356. package/skills/skill-stocktake/scripts/quick-diff.sh +87 -0
  357. package/skills/skill-stocktake/scripts/save-results.sh +56 -0
  358. package/skills/skill-stocktake/scripts/scan.sh +170 -0
  359. package/skills/social-graph-ranker/SKILL.md +153 -0
  360. package/skills/springboot-patterns/SKILL.md +313 -0
  361. package/skills/springboot-security/SKILL.md +271 -0
  362. package/skills/springboot-tdd/SKILL.md +157 -0
  363. package/skills/springboot-verification/SKILL.md +230 -0
  364. package/skills/strategic-compact/SKILL.md +129 -0
  365. package/skills/strategic-compact/suggest-compact.sh +54 -0
  366. package/skills/swift-actor-persistence/SKILL.md +142 -0
  367. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  368. package/skills/swift-protocol-di-testing/SKILL.md +189 -0
  369. package/skills/swiftui-patterns/SKILL.md +259 -0
  370. package/skills/tdd-workflow/SKILL.md +462 -0
  371. package/skills/team-builder/SKILL.md +166 -0
  372. package/skills/terminal-ops/SKILL.md +108 -0
  373. package/skills/tinystruct-patterns/SKILL.md +130 -0
  374. package/skills/tinystruct-patterns/references/architecture.md +77 -0
  375. package/skills/tinystruct-patterns/references/data-handling.md +35 -0
  376. package/skills/tinystruct-patterns/references/routing.md +57 -0
  377. package/skills/tinystruct-patterns/references/system-usage.md +74 -0
  378. package/skills/tinystruct-patterns/references/testing.md +59 -0
  379. package/skills/token-budget-advisor/SKILL.md +133 -0
  380. package/skills/ui-demo/SKILL.md +464 -0
  381. package/skills/ui-to-vue/SKILL.md +134 -0
  382. package/skills/unified-notifications-ops/SKILL.md +186 -0
  383. package/skills/verification-loop/SKILL.md +125 -0
  384. package/skills/video-editing/SKILL.md +309 -0
  385. package/skills/videodb/SKILL.md +373 -0
  386. package/skills/videodb/reference/api-reference.md +550 -0
  387. package/skills/videodb/reference/capture-reference.md +407 -0
  388. package/skills/videodb/reference/capture.md +101 -0
  389. package/skills/videodb/reference/editor.md +443 -0
  390. package/skills/videodb/reference/generative.md +331 -0
  391. package/skills/videodb/reference/rtstream-reference.md +564 -0
  392. package/skills/videodb/reference/rtstream.md +65 -0
  393. package/skills/videodb/reference/search.md +230 -0
  394. package/skills/videodb/reference/streaming.md +406 -0
  395. package/skills/videodb/reference/use-cases.md +118 -0
  396. package/skills/videodb/scripts/ws_listener.py +282 -0
  397. package/skills/visa-doc-translate/README.md +86 -0
  398. package/skills/visa-doc-translate/SKILL.md +117 -0
  399. package/skills/vite-patterns/SKILL.md +448 -0
  400. package/skills/windows-desktop-e2e/SKILL.md +787 -0
  401. package/skills/workspace-surface-audit/SKILL.md +124 -0
  402. package/skills/x-api/SKILL.md +233 -0
@@ -0,0 +1,647 @@
1
+ """
2
+ skillforge — skill orchestrator co-tool for Claude (MCP-first).
3
+
4
+ Primary surface: MCP stdio — route_skills and related tools for hosts
5
+ (Claude Desktop, Cursor, Claude Code).
6
+
7
+ Optional: headless HTTP API (POST /chat, /events, …) for integrations.
8
+ Live usage: `skillforge events --watch` (terminal).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import sqlite3
16
+ import sys
17
+ import time
18
+ import uuid
19
+ from contextlib import asynccontextmanager
20
+ from dataclasses import dataclass
21
+ from pathlib import Path
22
+ from typing import Any, Optional
23
+
24
+ import numpy as np
25
+ from anthropic import AsyncAnthropic
26
+ from fastapi import FastAPI, Request
27
+ from fastapi.responses import StreamingResponse
28
+ from pydantic import BaseModel
29
+ from sentence_transformers import SentenceTransformer
30
+
31
+ from app.db_paths import global_db_path, resolve_orchestrator_db
32
+
33
+ # ---------- Config (env-driven so the Node wrapper controls paths) ----------
34
+ BUNDLED_SKILLS = Path(os.getenv("SKILLFORGE_BUNDLED_SKILLS", "./skills"))
35
+ USER_SKILLS = Path(os.getenv("SKILLFORGE_USER_SKILLS", str(Path.home() / ".skillforge" / "skills")))
36
+
37
+
38
+ DB_PATH = global_db_path()
39
+
40
+
41
+ EMBED_MODEL = os.getenv("SKILLFORGE_EMBED_MODEL", "all-MiniLM-L6-v2")
42
+ ROUTER_MODEL = os.getenv("SKILLFORGE_ROUTER_MODEL", "claude-haiku-4-5-20251001")
43
+ ANSWER_MODEL = os.getenv("SKILLFORGE_ANSWER_MODEL", "claude-opus-4-7")
44
+ TOP_K_CANDIDATES = int(os.getenv("SKILLFORGE_TOP_K", "15"))
45
+ MAX_ACTIVE_SKILLS = int(os.getenv("SKILLFORGE_MAX_ACTIVE", "7"))
46
+ REROUTE_THRESHOLD = float(os.getenv("SKILLFORGE_REROUTE_THRESHOLD", "0.4"))
47
+ # "" | "full" | "embedding" — embedding skips Haiku and takes top skills from the shortlist only.
48
+ SKILLFORGE_ROUTER_MODE = os.getenv("SKILLFORGE_ROUTER_MODE", "").strip().lower()
49
+
50
+
51
+ def build_router_and_skills(
52
+ *,
53
+ log: bool = True,
54
+ log_prefix: str = "[skillforge]",
55
+ ) -> tuple[Router, dict[str, Skill]]:
56
+ """Load embedding model, skill catalog, and Router (shared by MCP and ``skillforge route`` CLI)."""
57
+ if log:
58
+ print(f"{log_prefix} Loading skills...", file=sys.stderr)
59
+ skills = load_all_skills()
60
+ embed_model = SentenceTransformer(os.getenv("SKILLFORGE_EMBED_MODEL", "all-MiniLM-L6-v2"))
61
+ key = os.getenv("ANTHROPIC_API_KEY", "").strip()
62
+ mode = SKILLFORGE_ROUTER_MODE
63
+ if mode == "embedding":
64
+ anthropic = None
65
+ router_note = "embedding-only (SKILLFORGE_ROUTER_MODE=embedding)"
66
+ elif mode == "full":
67
+ if key:
68
+ anthropic = AsyncAnthropic()
69
+ router_note = "full Haiku router (SKILLFORGE_ROUTER_MODE=full)"
70
+ else:
71
+ anthropic = None
72
+ router_note = (
73
+ "embedding-only (SKILLFORGE_ROUTER_MODE=full but no ANTHROPIC_API_KEY — "
74
+ "Haiku routing skipped)"
75
+ )
76
+ elif key:
77
+ anthropic = AsyncAnthropic()
78
+ router_note = "full Haiku router (default; ANTHROPIC_API_KEY set)"
79
+ else:
80
+ anthropic = None
81
+ router_note = (
82
+ "embedding-only (no ANTHROPIC_API_KEY — keyless. "
83
+ "Set ANTHROPIC_API_KEY for Haiku routing.)"
84
+ )
85
+ if log:
86
+ print(f"{log_prefix} {router_note}", file=sys.stderr)
87
+ print(
88
+ f"{log_prefix} Loaded {len(skills)} skills from bundled={BUNDLED_SKILLS} user={USER_SKILLS}",
89
+ file=sys.stderr,
90
+ )
91
+ router = Router(skills, embed_model, anthropic)
92
+ skmap = {s.name: s for s in skills}
93
+ return router, skmap
94
+
95
+
96
+ # ---------- Skill loading ----------
97
+ @dataclass
98
+ class Skill:
99
+ name: str
100
+ title: str
101
+ description: str
102
+ body: str
103
+ source: str # "bundled" | "user"
104
+ disabled: bool = False
105
+ embedding: np.ndarray | None = None
106
+
107
+
108
+ def parse_skill_md(path: Path, source: str) -> Skill | None:
109
+ """Parse a SKILL.md: extract frontmatter name/description + body.
110
+
111
+ Handles YAML block scalars (>, |) where description spans multiple
112
+ indented lines after the key.
113
+ """
114
+ try:
115
+ text = path.read_text(encoding="utf-8")
116
+ except Exception:
117
+ return None
118
+ name = path.parent.name
119
+ title = name.replace("-", " ").title()
120
+ description = ""
121
+ body = text
122
+ if text.startswith("---"):
123
+ end = text.find("---", 3)
124
+ if end != -1:
125
+ fm = text[3:end]
126
+ body = text[end + 3:].strip()
127
+ lines = fm.splitlines()
128
+ i = 0
129
+ while i < len(lines):
130
+ line = lines[i]
131
+ if ":" in line and not line.startswith(" ") and not line.startswith("\t"):
132
+ k, _, v = line.partition(":")
133
+ k = k.strip().lower()
134
+ v = v.strip()
135
+ # YAML block scalar: collect subsequent indented lines
136
+ if v in (">", "|", ">-", "|-", ">+", "|+"):
137
+ collected = []
138
+ j = i + 1
139
+ while j < len(lines) and (lines[j].startswith(" ") or lines[j].startswith("\t") or lines[j].strip() == ""):
140
+ collected.append(lines[j].strip())
141
+ j += 1
142
+ v = " ".join(s for s in collected if s)
143
+ i = j - 1
144
+ else:
145
+ v = v.strip('"').strip("'")
146
+ if k == "name":
147
+ title = v
148
+ elif k == "description":
149
+ description = v
150
+ i += 1
151
+ if not description:
152
+ for chunk in body.split("\n\n"):
153
+ chunk = chunk.strip()
154
+ if chunk and not chunk.startswith("#"):
155
+ description = chunk[:500]
156
+ break
157
+ return Skill(name=name, title=title, description=description, body=body, source=source)
158
+
159
+
160
+ def load_all_skills() -> list[Skill]:
161
+ """Load from bundled dir first, then user dir (user overrides bundled by name)."""
162
+ by_name: dict[str, Skill] = {}
163
+ for src_dir, label in [(BUNDLED_SKILLS, "bundled"), (USER_SKILLS, "user")]:
164
+ if not src_dir.exists():
165
+ continue
166
+ for skill_md in sorted(src_dir.glob("*/SKILL.md")):
167
+ s = parse_skill_md(skill_md, label)
168
+ if s:
169
+ by_name[s.name] = s # later sources override
170
+ return list(by_name.values())
171
+
172
+
173
+ def iter_skill_md_paths() -> list[Path]:
174
+ """All discovered SKILL.md paths in load order (bundled, then user overrides)."""
175
+ paths: list[Path] = []
176
+ for src_dir in (BUNDLED_SKILLS, USER_SKILLS):
177
+ if not src_dir.exists():
178
+ continue
179
+ for skill_md in sorted(src_dir.glob("*/SKILL.md")):
180
+ paths.append(skill_md)
181
+ return paths
182
+
183
+
184
+ def skill_catalog_manifest() -> tuple[tuple[str, int], ...]:
185
+ """Stable on-disk fingerprint for MCP hot-reload (resolved path + mtime_ns)."""
186
+ rows: list[tuple[str, int]] = []
187
+ for skill_md in iter_skill_md_paths():
188
+ try:
189
+ mtime_ns = int(skill_md.stat().st_mtime_ns)
190
+ except OSError:
191
+ mtime_ns = 0
192
+ rows.append((str(skill_md.resolve()), mtime_ns))
193
+ return tuple(rows)
194
+
195
+
196
+ # ---------- Database ----------
197
+ def init_db(db_file: Path | None = None):
198
+ path = global_db_path() if db_file is None else db_file
199
+ path.parent.mkdir(parents=True, exist_ok=True)
200
+ con = sqlite3.connect(str(path))
201
+ con.executescript("""
202
+ CREATE TABLE IF NOT EXISTS events (
203
+ id TEXT PRIMARY KEY,
204
+ ts REAL NOT NULL,
205
+ user_id TEXT DEFAULT '',
206
+ session_id TEXT,
207
+ event_type TEXT,
208
+ payload TEXT
209
+ );
210
+ CREATE TABLE IF NOT EXISTS skill_weights (
211
+ user_id TEXT DEFAULT '',
212
+ skill_name TEXT,
213
+ weight REAL DEFAULT 0.0,
214
+ uses INTEGER DEFAULT 0,
215
+ referenced INTEGER DEFAULT 0,
216
+ thumbs_up INTEGER DEFAULT 0,
217
+ thumbs_down INTEGER DEFAULT 0,
218
+ disabled INTEGER DEFAULT 0,
219
+ updated_at REAL,
220
+ PRIMARY KEY (user_id, skill_name)
221
+ );
222
+ CREATE TABLE IF NOT EXISTS sessions (
223
+ id TEXT PRIMARY KEY,
224
+ user_id TEXT DEFAULT '',
225
+ created_at REAL,
226
+ active_skills TEXT,
227
+ turn_count INTEGER DEFAULT 0
228
+ );
229
+ CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts DESC);
230
+ CREATE INDEX IF NOT EXISTS idx_events_user ON events(user_id, ts DESC);
231
+ """)
232
+ # Backward-compat: if upgrading from 0.1.0 schema, add user_id column where missing.
233
+ for table in ("events", "sessions"):
234
+ try:
235
+ con.execute(f"ALTER TABLE {table} ADD COLUMN user_id TEXT DEFAULT ''")
236
+ except sqlite3.OperationalError:
237
+ pass # already exists
238
+ con.commit()
239
+ return con
240
+
241
+
242
+ def log_event(con, session_id, event_type, payload, user_id=""):
243
+ con.execute(
244
+ "INSERT INTO events (id, ts, user_id, session_id, event_type, payload) VALUES (?, ?, ?, ?, ?, ?)",
245
+ (str(uuid.uuid4()), time.time(), user_id, session_id, event_type, json.dumps(payload)),
246
+ )
247
+ con.commit()
248
+
249
+
250
+ def get_skill_weight(con, name, user_id=""):
251
+ cur = con.execute(
252
+ "SELECT weight, disabled FROM skill_weights WHERE user_id = ? AND skill_name = ?",
253
+ (user_id, name),
254
+ )
255
+ row = cur.fetchone()
256
+ if not row:
257
+ return 0.0, False
258
+ return row[0], bool(row[1])
259
+
260
+
261
+ def update_skill_stat(con, name, field, delta=1, user_id=""):
262
+ con.execute(
263
+ f"""INSERT INTO skill_weights (user_id, skill_name, {field}, updated_at)
264
+ VALUES (?, ?, ?, ?)
265
+ ON CONFLICT(user_id, skill_name) DO UPDATE SET
266
+ {field} = {field} + ?,
267
+ updated_at = ?""",
268
+ (user_id, name, delta, time.time(), delta, time.time()),
269
+ )
270
+ cur = con.execute(
271
+ "SELECT uses, referenced, thumbs_up, thumbs_down FROM skill_weights WHERE user_id = ? AND skill_name = ?",
272
+ (user_id, name),
273
+ )
274
+ row = cur.fetchone()
275
+ if row:
276
+ uses, referenced, up, down = row
277
+ ref_rate = (referenced / uses) if uses > 0 else 0.0
278
+ thumbs_score = (up - down) * 0.1
279
+ weight = (ref_rate - 0.5) * 0.3 + thumbs_score
280
+ con.execute(
281
+ "UPDATE skill_weights SET weight = ? WHERE user_id = ? AND skill_name = ?",
282
+ (weight, user_id, name),
283
+ )
284
+ con.commit()
285
+
286
+
287
+ def set_skill_disabled(con, name, disabled: bool, user_id=""):
288
+ con.execute(
289
+ """INSERT INTO skill_weights (user_id, skill_name, disabled, updated_at) VALUES (?, ?, ?, ?)
290
+ ON CONFLICT(user_id, skill_name) DO UPDATE SET disabled = ?, updated_at = ?""",
291
+ (user_id, name, int(disabled), time.time(), int(disabled), time.time()),
292
+ )
293
+ con.commit()
294
+
295
+
296
+ # ---------- Router ----------
297
+ class Router:
298
+ def __init__(self, skills, embed_model, anthropic: Optional[AsyncAnthropic]):
299
+ self.skills = skills
300
+ self.embed_model = embed_model
301
+ self.anthropic = anthropic
302
+ texts = [f"{s.title}: {s.description}" for s in skills]
303
+ print(f"[skillforge] Embedding {len(skills)} skills...")
304
+ embeddings = embed_model.encode(texts, show_progress_bar=False, convert_to_numpy=True)
305
+ for s, e in zip(skills, embeddings):
306
+ s.embedding = e / np.linalg.norm(e)
307
+ self.matrix = np.stack([s.embedding for s in skills]) if skills else np.zeros((0, 0))
308
+ print(f"[skillforge] Ready. {len(skills)} skills, matrix shape: {self.matrix.shape}")
309
+
310
+ def shortlist(self, prompt, con, k=TOP_K_CANDIDATES, user_id=""):
311
+ if len(self.skills) == 0:
312
+ return []
313
+ q = self.embed_model.encode(prompt, convert_to_numpy=True)
314
+ q = q / np.linalg.norm(q)
315
+ sims = self.matrix @ q
316
+ biased = sims.copy()
317
+ for i, s in enumerate(self.skills):
318
+ w, disabled = get_skill_weight(con, s.name, user_id=user_id)
319
+ if disabled:
320
+ biased[i] = -999.0
321
+ else:
322
+ biased[i] += w
323
+ top_idx = np.argsort(-biased)[:k]
324
+ return [(self.skills[i], float(sims[i])) for i in top_idx if biased[i] > -100]
325
+
326
+ def pick_final_embedding_only(self, candidates):
327
+ """Pick up to MAX_ACTIVE_SKILLS from the shortlist order (similarity + weights). No LLM call."""
328
+ if not candidates:
329
+ return [], "no candidates available"
330
+ names = [s.name for s, _ in candidates[:MAX_ACTIVE_SKILLS]]
331
+ return names, (
332
+ "embedding-only: top candidates by similarity and learned weights"
333
+ )
334
+
335
+ async def pick_final(self, prompt, conversation, candidates):
336
+ if self.anthropic is None:
337
+ return self.pick_final_embedding_only(candidates)
338
+ if not candidates:
339
+ return [], "no candidates available"
340
+ catalog = "\n".join(
341
+ f"- {s.name}: {s.description[:200]}" for s, _ in candidates
342
+ )
343
+ recent = ""
344
+ if conversation:
345
+ recent = "\n\nRecent conversation:\n" + "\n".join(
346
+ f"{m['role']}: {m['content'][:200]}" for m in conversation[-4:]
347
+ )
348
+ sys = (
349
+ "You are a skill router. Given a user prompt and a candidate list of skills, "
350
+ f"pick 0 to {MAX_ACTIVE_SKILLS} skills that would genuinely help answer this prompt. "
351
+ "Be ruthless — only include a skill if it directly applies. Empty list is valid. "
352
+ 'Respond ONLY in JSON: {"skills": ["name1","name2"], "reasoning": "one sentence"}'
353
+ )
354
+ user = f"User prompt:\n{prompt}{recent}\n\nCandidate skills:\n{catalog}"
355
+ try:
356
+ resp = await self.anthropic.messages.create(
357
+ model=ROUTER_MODEL,
358
+ max_tokens=400,
359
+ system=sys,
360
+ messages=[{"role": "user", "content": user}],
361
+ )
362
+ text = resp.content[0].text.strip()
363
+ if text.startswith("```"):
364
+ text = text.split("```")[1]
365
+ if text.startswith("json"):
366
+ text = text[4:]
367
+ data = json.loads(text.strip())
368
+ picked = [n for n in data.get("skills", []) if any(s.name == n for s, _ in candidates)]
369
+ return picked[:MAX_ACTIVE_SKILLS], data.get("reasoning", "")
370
+ except Exception as e:
371
+ return [s.name for s, _ in candidates[:3]], f"router-fallback: {e}"
372
+
373
+
374
+ def jaccard_change(old, new):
375
+ if not old and not new:
376
+ return 0.0
377
+ if not old or not new:
378
+ return 1.0
379
+ inter = len(old & new)
380
+ union = len(old | new)
381
+ return 1.0 - (inter / union)
382
+
383
+
384
+ async def run_route_turn(
385
+ con: sqlite3.Connection,
386
+ router: Router,
387
+ prompt: str,
388
+ conversation: list,
389
+ user_id: str = "",
390
+ session_id: str | None = None,
391
+ ) -> dict[str, Any]:
392
+ """Shared routing + session + telemetry for HTTP /chat and MCP route_skills.
393
+
394
+ Updates sessions, skill usage stats, and writes a route row to events.
395
+ """
396
+ sid = session_id or str(uuid.uuid4())
397
+ t0 = time.time()
398
+ candidates = router.shortlist(prompt, con, user_id=user_id)
399
+ picked_names, reasoning = await router.pick_final(prompt, conversation, candidates)
400
+ route_ms = (time.time() - t0) * 1000
401
+
402
+ prev_active: set[str] = set()
403
+ cur = con.execute(
404
+ "SELECT active_skills FROM sessions WHERE id = ? AND user_id = ?",
405
+ (sid, user_id),
406
+ )
407
+ row = cur.fetchone()
408
+ if row and row[0]:
409
+ prev_active = set(json.loads(row[0]))
410
+ change = jaccard_change(prev_active, set(picked_names))
411
+ rerouted = change >= REROUTE_THRESHOLD and bool(prev_active)
412
+
413
+ con.execute(
414
+ """INSERT INTO sessions (id, user_id, created_at, active_skills, turn_count) VALUES (?, ?, ?, ?, 1)
415
+ ON CONFLICT(id) DO UPDATE SET active_skills = ?, turn_count = turn_count + 1""",
416
+ (sid, user_id, time.time(), json.dumps(picked_names), json.dumps(picked_names)),
417
+ )
418
+ con.commit()
419
+ for n in picked_names:
420
+ update_skill_stat(con, n, "uses", 1, user_id=user_id)
421
+
422
+ event = {
423
+ "type": "route",
424
+ "session_id": sid,
425
+ "user_id": user_id,
426
+ "prompt": prompt[:300],
427
+ "candidates": [{"name": s.name, "score": sc} for s, sc in candidates[:10]],
428
+ "picked": picked_names,
429
+ "reasoning": reasoning,
430
+ "rerouted": rerouted,
431
+ "change_pct": round(change * 100, 1),
432
+ "route_ms": round(route_ms, 1),
433
+ "ts": time.time(),
434
+ }
435
+ log_event(con, sid, "route", event, user_id=user_id)
436
+ return {
437
+ "session_id": sid,
438
+ "picked_names": picked_names,
439
+ "reasoning": reasoning,
440
+ "candidates": candidates,
441
+ "route_ms": route_ms,
442
+ "rerouted": rerouted,
443
+ "change": change,
444
+ "event": event,
445
+ }
446
+
447
+
448
+ # ---------- App ----------
449
+ app_state: dict[str, Any] = {}
450
+
451
+
452
+ @asynccontextmanager
453
+ async def lifespan(app: FastAPI):
454
+ print(f"[skillforge] Loading skills from {BUNDLED_SKILLS} + {USER_SKILLS}")
455
+ skills = load_all_skills()
456
+ print(f"[skillforge] Loaded {len(skills)} skills")
457
+ if not skills:
458
+ print("[skillforge] WARNING: no skills found")
459
+ embed_model = SentenceTransformer(EMBED_MODEL)
460
+ anthropic = AsyncAnthropic()
461
+ router_anthropic = None if SKILLFORGE_ROUTER_MODE == "embedding" else anthropic
462
+ if router_anthropic is None:
463
+ print("[skillforge] Router mode: embedding-only (Haiku step skipped; /chat still uses ANSWER model)")
464
+ print("[skillforge] Live usage (terminal): skillforge events --watch")
465
+ router = Router(skills, embed_model, router_anthropic)
466
+ con = init_db()
467
+ app_state.update(
468
+ skills={s.name: s for s in skills},
469
+ router=router,
470
+ anthropic=anthropic,
471
+ con=con,
472
+ )
473
+ yield
474
+ con.close()
475
+
476
+
477
+ app = FastAPI(lifespan=lifespan, title="skillforge")
478
+
479
+
480
+ class ChatRequest(BaseModel):
481
+ prompt: str
482
+ session_id: str | None = None
483
+ conversation: list[dict] = []
484
+
485
+
486
+ class FeedbackRequest(BaseModel):
487
+ session_id: str
488
+ skill_name: str
489
+ thumbs: int
490
+
491
+
492
+ class DisableRequest(BaseModel):
493
+ skill_name: str
494
+ disabled: bool
495
+
496
+
497
+ @app.post("/chat")
498
+ async def chat(req: ChatRequest, request: Request):
499
+ from app.auth import resolve_user
500
+ user_id = resolve_user(request)
501
+ router: Router = app_state["router"]
502
+ con = app_state["con"]
503
+ anthropic: AsyncAnthropic = app_state["anthropic"]
504
+
505
+ result = await run_route_turn(
506
+ con,
507
+ router,
508
+ req.prompt,
509
+ req.conversation,
510
+ user_id=user_id,
511
+ session_id=req.session_id,
512
+ )
513
+ session_id = result["session_id"]
514
+ picked_names = result["picked_names"]
515
+
516
+ skills_map = app_state["skills"]
517
+ skill_blocks = []
518
+ for n in picked_names:
519
+ s = skills_map.get(n)
520
+ if s:
521
+ skill_blocks.append(f'<skill name="{s.name}">\n{s.body}\n</skill>')
522
+ system_prompt = (
523
+ "You are a helpful assistant. The following skills have been dynamically loaded "
524
+ "for this turn based on the user's request. Use them when relevant; ignore them when not.\n\n"
525
+ + "\n\n".join(skill_blocks)
526
+ ) if skill_blocks else "You are a helpful assistant."
527
+
528
+ messages = req.conversation + [{"role": "user", "content": req.prompt}]
529
+
530
+ async def stream():
531
+ full_text = []
532
+ try:
533
+ async with anthropic.messages.stream(
534
+ model=ANSWER_MODEL,
535
+ max_tokens=4096,
536
+ system=system_prompt,
537
+ messages=messages,
538
+ ) as s:
539
+ async for chunk in s.text_stream:
540
+ full_text.append(chunk)
541
+ yield f"data: {json.dumps({'delta': chunk})}\n\n"
542
+ except Exception as e:
543
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
544
+ return
545
+ response_text = "".join(full_text)
546
+ for n in picked_names:
547
+ s = skills_map.get(n)
548
+ if not s:
549
+ continue
550
+ keywords = [w for w in s.body.split()[:50] if len(w) > 6][:5]
551
+ hits = sum(1 for kw in keywords if kw.lower() in response_text.lower())
552
+ if hits >= 2 or s.name in response_text.lower():
553
+ update_skill_stat(con, n, "referenced", 1, user_id=user_id)
554
+ yield f"data: {json.dumps({'done': True, 'session_id': session_id, 'picked': picked_names})}\n\n"
555
+
556
+ return StreamingResponse(stream(), media_type="text/event-stream")
557
+
558
+
559
+ @app.post("/feedback")
560
+ def feedback(req: FeedbackRequest, request: Request):
561
+ from app.auth import resolve_user
562
+ user_id = resolve_user(request)
563
+ con = app_state["con"]
564
+ field = "thumbs_up" if req.thumbs > 0 else "thumbs_down"
565
+ update_skill_stat(con, req.skill_name, field, 1, user_id=user_id)
566
+ log_event(con, req.session_id, "feedback",
567
+ {"skill": req.skill_name, "thumbs": req.thumbs},
568
+ user_id=user_id)
569
+ return {"ok": True}
570
+
571
+
572
+ @app.post("/skills/disable")
573
+ def disable(req: DisableRequest, request: Request):
574
+ from app.auth import resolve_user
575
+ user_id = resolve_user(request)
576
+ con = app_state["con"]
577
+ set_skill_disabled(con, req.skill_name, req.disabled, user_id=user_id)
578
+ return {"ok": True}
579
+
580
+
581
+ @app.get("/skills")
582
+ def list_skills(request: Request):
583
+ from app.auth import resolve_user
584
+ user_id = resolve_user(request)
585
+ con = app_state["con"]
586
+ skills_map = app_state["skills"]
587
+ out = []
588
+ for name, s in skills_map.items():
589
+ cur = con.execute(
590
+ "SELECT weight, uses, referenced, thumbs_up, thumbs_down, disabled FROM skill_weights WHERE user_id = ? AND skill_name = ?",
591
+ (user_id, name),
592
+ )
593
+ row = cur.fetchone()
594
+ weight, uses, ref, up, down, disabled = row if row else (0.0, 0, 0, 0, 0, 0)
595
+ out.append({
596
+ "name": name,
597
+ "title": s.title,
598
+ "description": s.description[:200],
599
+ "source": s.source,
600
+ "weight": weight,
601
+ "uses": uses,
602
+ "referenced": ref,
603
+ "thumbs_up": up,
604
+ "thumbs_down": down,
605
+ "disabled": bool(disabled),
606
+ })
607
+ out.sort(key=lambda x: -x["uses"])
608
+ return out
609
+
610
+
611
+ @app.get("/events")
612
+ def recent_events(request: Request, limit: int = 50):
613
+ from app.auth import resolve_user, auth_enabled
614
+ user_id = resolve_user(request)
615
+ con = app_state["con"]
616
+ if auth_enabled():
617
+ cur = con.execute(
618
+ "SELECT ts, session_id, event_type, payload FROM events WHERE user_id = ? ORDER BY ts DESC LIMIT ?",
619
+ (user_id, limit),
620
+ )
621
+ else:
622
+ cur = con.execute(
623
+ "SELECT ts, session_id, event_type, payload FROM events ORDER BY ts DESC LIMIT ?",
624
+ (limit,),
625
+ )
626
+ return [
627
+ {"ts": ts, "session_id": sid, "type": et, "payload": json.loads(p)}
628
+ for ts, sid, et, p in cur.fetchall()
629
+ ]
630
+
631
+
632
+ @app.get("/")
633
+ def root():
634
+ return {
635
+ "service": "skillforge",
636
+ "docs": "POST /chat, GET /events, GET /skills, GET /healthz",
637
+ "live_log": "skillforge events --watch",
638
+ }
639
+
640
+
641
+ @app.get("/healthz")
642
+ def health():
643
+ return {
644
+ "skills_loaded": len(app_state.get("skills", {})),
645
+ "ok": True,
646
+ "live_log": "skillforge events --watch",
647
+ }