genoma-evolution 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. package/.brv/.obsidian/app.json +1 -0
  2. package/.brv/.obsidian/appearance.json +1 -0
  3. package/.brv/.obsidian/core-plugins.json +33 -0
  4. package/.brv/.obsidian/graph.json +22 -0
  5. package/.brv/.obsidian/workspace.json +195 -0
  6. package/.brv/Sin ti/314/201tulo 1.canvas" +1 -0
  7. package/.brv/Sin ti/314/201tulo 2.canvas" +1 -0
  8. package/.brv/Sin ti/314/201tulo.canvas" +1 -0
  9. package/.brv/_queue_status.json +1 -0
  10. package/.brv/config.json +5 -0
  11. package/.brv/context-tree/_index.md +60 -0
  12. package/.brv/context-tree/_manifest.json +165 -0
  13. package/.brv/context-tree/backend/_index.md +24 -0
  14. package/.brv/context-tree/backend/backend/_index.md +40 -0
  15. package/.brv/context-tree/backend/backend/init.abstract.md +0 -0
  16. package/.brv/context-tree/backend/backend/init.md +27 -0
  17. package/.brv/context-tree/backend/backend/init.overview.md +29 -0
  18. package/.brv/context-tree/backend/backend/job_tracker.abstract.md +1 -0
  19. package/.brv/context-tree/backend/backend/job_tracker.md +273 -0
  20. package/.brv/context-tree/backend/backend/job_tracker.overview.md +31 -0
  21. package/.brv/context-tree/backend/backend/main.abstract.md +0 -0
  22. package/.brv/context-tree/backend/backend/main.md +1292 -0
  23. package/.brv/context-tree/backend/backend/main.overview.md +30 -0
  24. package/.brv/context-tree/backend/backend/requirements.abstract.md +1 -0
  25. package/.brv/context-tree/backend/backend/requirements.md +37 -0
  26. package/.brv/context-tree/backend/backend/requirements.overview.md +28 -0
  27. package/.brv/context-tree/docs/_index.md +37 -0
  28. package/.brv/context-tree/docs/api/_index.md +54 -0
  29. package/.brv/context-tree/docs/api/context.md +11 -0
  30. package/.brv/context-tree/docs/api/hermes_api_openapi_specification.abstract.md +0 -0
  31. package/.brv/context-tree/docs/api/hermes_api_openapi_specification.md +468 -0
  32. package/.brv/context-tree/docs/api/hermes_api_openapi_specification.overview.md +44 -0
  33. package/.brv/context-tree/frontend/_index.md +48 -0
  34. package/.brv/context-tree/frontend/hermes_dashboard/_index.md +31 -0
  35. package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.abstract.md +0 -0
  36. package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.md +41 -0
  37. package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.overview.md +34 -0
  38. package/.brv/context-tree/frontend/src/_index.md +53 -0
  39. package/.brv/context-tree/frontend/src/components/_index.md +52 -0
  40. package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.abstract.md +0 -0
  41. package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.md +161 -0
  42. package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.overview.md +32 -0
  43. package/.brv/context-tree/frontend/src/context.md +10 -0
  44. package/.brv/context-tree/frontend/src/functioncallingpage.abstract.md +0 -0
  45. package/.brv/context-tree/frontend/src/functioncallingpage.md +34 -0
  46. package/.brv/context-tree/frontend/src/functioncallingpage.overview.md +26 -0
  47. package/.brv/context-tree/frontend/src/lib/_index.md +48 -0
  48. package/.brv/context-tree/frontend/src/lib/api_client_library.abstract.md +1 -0
  49. package/.brv/context-tree/frontend/src/lib/api_client_library.md +403 -0
  50. package/.brv/context-tree/frontend/src/lib/api_client_library.overview.md +69 -0
  51. package/.brv/context-tree/frontend/src/page.abstract.md +0 -0
  52. package/.brv/context-tree/frontend/src/page.md +103 -0
  53. package/.brv/context-tree/frontend/src/page.overview.md +7 -0
  54. package/.brv/context-tree/frontend/src/settingspage.abstract.md +0 -0
  55. package/.brv/context-tree/frontend/src/settingspage.md +124 -0
  56. package/.brv/context-tree/frontend/src/settingspage.overview.md +34 -0
  57. package/.brv/context-tree/frontend/src/sidebar.abstract.md +0 -0
  58. package/.brv/context-tree/frontend/src/sidebar.md +170 -0
  59. package/.brv/context-tree/frontend/src/sidebar.overview.md +25 -0
  60. package/.brv/context-tree/meta/_index.md +24 -0
  61. package/.brv/context-tree/meta/curation_context/_index.md +24 -0
  62. package/.brv/context-tree/meta/curation_context/empty_context.abstract.md +4 -0
  63. package/.brv/context-tree/meta/curation_context/empty_context.md +35 -0
  64. package/.brv/context-tree/meta/curation_context/empty_context.overview.md +20 -0
  65. package/.brv/dream-log/drm-1777341062653.json +33 -0
  66. package/.brv/dream-state.json +8 -0
  67. package/.brv/dream.lock +0 -0
  68. package/.brv/review-backups/docs/api/hermes_api_openapi_specification.md +468 -0
  69. package/.claude/settings.local.json +7 -0
  70. package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/app.json +1 -0
  71. package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/appearance.json +1 -0
  72. package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/core-plugins.json +33 -0
  73. package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/graph.json +22 -0
  74. package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/workspace.json +195 -0
  75. package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo 1.canvas" +1 -0
  76. package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo 2.canvas" +1 -0
  77. package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo.canvas" +1 -0
  78. package/.claude/worktrees/phase-2-mcp/.brv/_queue_status.json +1 -0
  79. package/.claude/worktrees/phase-2-mcp/.brv/config.json +5 -0
  80. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/_index.md +60 -0
  81. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/_manifest.json +165 -0
  82. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/_index.md +24 -0
  83. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/_index.md +40 -0
  84. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.abstract.md +0 -0
  85. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.md +27 -0
  86. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.overview.md +29 -0
  87. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.abstract.md +1 -0
  88. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.md +273 -0
  89. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.overview.md +31 -0
  90. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.abstract.md +0 -0
  91. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.md +1292 -0
  92. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.overview.md +30 -0
  93. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.abstract.md +1 -0
  94. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.md +37 -0
  95. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.overview.md +28 -0
  96. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/_index.md +37 -0
  97. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/_index.md +54 -0
  98. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/context.md +11 -0
  99. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.abstract.md +0 -0
  100. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.md +468 -0
  101. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.overview.md +44 -0
  102. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/_index.md +48 -0
  103. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/_index.md +31 -0
  104. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.abstract.md +0 -0
  105. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.md +41 -0
  106. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.overview.md +34 -0
  107. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/_index.md +53 -0
  108. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/_index.md +52 -0
  109. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.abstract.md +0 -0
  110. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.md +161 -0
  111. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.overview.md +32 -0
  112. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/context.md +10 -0
  113. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.abstract.md +0 -0
  114. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.md +34 -0
  115. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.overview.md +26 -0
  116. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/_index.md +48 -0
  117. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.abstract.md +1 -0
  118. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.md +403 -0
  119. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.overview.md +69 -0
  120. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.abstract.md +0 -0
  121. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.md +103 -0
  122. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.overview.md +7 -0
  123. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.abstract.md +0 -0
  124. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.md +124 -0
  125. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.overview.md +34 -0
  126. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.abstract.md +0 -0
  127. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.md +170 -0
  128. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.overview.md +25 -0
  129. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/_index.md +24 -0
  130. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/_index.md +24 -0
  131. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.abstract.md +4 -0
  132. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.md +35 -0
  133. package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.overview.md +20 -0
  134. package/.claude/worktrees/phase-2-mcp/.brv/dream-log/drm-1777341062653.json +33 -0
  135. package/.claude/worktrees/phase-2-mcp/.brv/dream-state.json +8 -0
  136. package/.claude/worktrees/phase-2-mcp/.brv/dream.lock +0 -0
  137. package/.claude/worktrees/phase-2-mcp/.brv/review-backups/docs/api/hermes_api_openapi_specification.md +468 -0
  138. package/.claude/worktrees/phase-2-mcp/.claude/settings.local.json +13 -0
  139. package/.claude/worktrees/phase-2-mcp/.kilocode/package-lock.json +378 -0
  140. package/.claude/worktrees/phase-2-mcp/.kilocode/package.json +5 -0
  141. package/.claude/worktrees/phase-2-mcp/AGENTS.md +5 -0
  142. package/.claude/worktrees/phase-2-mcp/CLAUDE.md +29 -0
  143. package/.claude/worktrees/phase-2-mcp/QA_AUDIT_PLAN.md +156 -0
  144. package/.claude/worktrees/phase-2-mcp/README.md +316 -0
  145. package/.claude/worktrees/phase-2-mcp/agent-agnostic-evolution-dashboard.md +405 -0
  146. package/.claude/worktrees/phase-2-mcp/backend/__init__.py +0 -0
  147. package/.claude/worktrees/phase-2-mcp/backend/collectors/__init__.py +0 -0
  148. package/.claude/worktrees/phase-2-mcp/backend/collectors/claude_code_collector.py +277 -0
  149. package/.claude/worktrees/phase-2-mcp/backend/collectors/hermes_collector.py +68 -0
  150. package/.claude/worktrees/phase-2-mcp/backend/curator.py +512 -0
  151. package/.claude/worktrees/phase-2-mcp/backend/eval/__init__.py +19 -0
  152. package/.claude/worktrees/phase-2-mcp/backend/eval/engine.py +116 -0
  153. package/.claude/worktrees/phase-2-mcp/backend/eval/scorers.py +201 -0
  154. package/.claude/worktrees/phase-2-mcp/backend/generate_dataset.py +86 -0
  155. package/.claude/worktrees/phase-2-mcp/backend/job_tracker.py +232 -0
  156. package/.claude/worktrees/phase-2-mcp/backend/main.py +1746 -0
  157. package/.claude/worktrees/phase-2-mcp/backend/mcp_server.py +250 -0
  158. package/.claude/worktrees/phase-2-mcp/backend/promethean/__init__.py +24 -0
  159. package/.claude/worktrees/phase-2-mcp/backend/promethean/cycle_orchestrator.py +270 -0
  160. package/.claude/worktrees/phase-2-mcp/backend/promethean/delta_validator.py +191 -0
  161. package/.claude/worktrees/phase-2-mcp/backend/promethean/dspy_compiler.py +315 -0
  162. package/.claude/worktrees/phase-2-mcp/backend/promethean/gepa_strategist.py +213 -0
  163. package/.claude/worktrees/phase-2-mcp/backend/promethean/models.py +260 -0
  164. package/.claude/worktrees/phase-2-mcp/backend/promethean/skill_deployer.py +195 -0
  165. package/.claude/worktrees/phase-2-mcp/backend/promethean/trace_ingestion.py +142 -0
  166. package/.claude/worktrees/phase-2-mcp/backend/requirements.txt +6 -0
  167. package/.claude/worktrees/phase-2-mcp/backend/sdd_evolve.py +459 -0
  168. package/.claude/worktrees/phase-2-mcp/backend/skill_detector.py +227 -0
  169. package/.claude/worktrees/phase-2-mcp/backend/skill_registry.py +289 -0
  170. package/.claude/worktrees/phase-2-mcp/backend/storage/__init__.py +5 -0
  171. package/.claude/worktrees/phase-2-mcp/backend/storage/run_store.py +393 -0
  172. package/.claude/worktrees/phase-2-mcp/backend/storage/schema.sql +99 -0
  173. package/.claude/worktrees/phase-2-mcp/backend/validate_evolution.py +267 -0
  174. package/.claude/worktrees/phase-2-mcp/components.json +28 -0
  175. package/.claude/worktrees/phase-2-mcp/docs/api/hermes-api.openapi.yaml +438 -0
  176. package/.claude/worktrees/phase-2-mcp/docs/hero.svg +148 -0
  177. package/.claude/worktrees/phase-2-mcp/eslint.config.mjs +18 -0
  178. package/.claude/worktrees/phase-2-mcp/install.sh +245 -0
  179. package/.claude/worktrees/phase-2-mcp/next-env.d.ts +6 -0
  180. package/.claude/worktrees/phase-2-mcp/next.config.ts +32 -0
  181. package/.claude/worktrees/phase-2-mcp/package-lock.json +11936 -0
  182. package/.claude/worktrees/phase-2-mcp/package.json +41 -0
  183. package/.claude/worktrees/phase-2-mcp/pnpm-workspace.yaml +4 -0
  184. package/.claude/worktrees/phase-2-mcp/postcss.config.mjs +7 -0
  185. package/.claude/worktrees/phase-2-mcp/public/file.svg +1 -0
  186. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Bold.otf +0 -0
  187. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Heavy.otf +0 -0
  188. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Medium.otf +0 -0
  189. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Regular.otf +0 -0
  190. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Semibold.otf +0 -0
  191. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Bold.otf +0 -0
  192. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Heavy.otf +0 -0
  193. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Medium.otf +0 -0
  194. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Regular.otf +0 -0
  195. package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Semibold.otf +0 -0
  196. package/.claude/worktrees/phase-2-mcp/public/globe.svg +1 -0
  197. package/.claude/worktrees/phase-2-mcp/public/next.svg +1 -0
  198. package/.claude/worktrees/phase-2-mcp/public/theme-preview.html +257 -0
  199. package/.claude/worktrees/phase-2-mcp/public/vercel.svg +1 -0
  200. package/.claude/worktrees/phase-2-mcp/public/window.svg +1 -0
  201. package/.claude/worktrees/phase-2-mcp/run.sh +26 -0
  202. package/.claude/worktrees/phase-2-mcp/skills-lock.json +10 -0
  203. package/.claude/worktrees/phase-2-mcp/specs/event-schema.md +223 -0
  204. package/.claude/worktrees/phase-2-mcp/specs/examples/run.jsonl +3 -0
  205. package/.claude/worktrees/phase-2-mcp/src/app/api/[...path]/route.ts +55 -0
  206. package/.claude/worktrees/phase-2-mcp/src/app/api/auth/token/route.ts +22 -0
  207. package/.claude/worktrees/phase-2-mcp/src/app/evolution/page.tsx +589 -0
  208. package/.claude/worktrees/phase-2-mcp/src/app/favicon.ico +0 -0
  209. package/.claude/worktrees/phase-2-mcp/src/app/globals.css +321 -0
  210. package/.claude/worktrees/phase-2-mcp/src/app/layout.tsx +63 -0
  211. package/.claude/worktrees/phase-2-mcp/src/app/page.tsx +70 -0
  212. package/.claude/worktrees/phase-2-mcp/src/app/skills/page.tsx +369 -0
  213. package/.claude/worktrees/phase-2-mcp/src/components/ApiConfigCard.tsx +199 -0
  214. package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.css +1 -0
  215. package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.d.ts +1 -0
  216. package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.jsx +1 -0
  217. package/.claude/worktrees/phase-2-mcp/src/components/CoreLoopToggle.tsx +111 -0
  218. package/.claude/worktrees/phase-2-mcp/src/components/EnvironmentStatus.tsx +176 -0
  219. package/.claude/worktrees/phase-2-mcp/src/components/EvolutionBackground.tsx +1 -0
  220. package/.claude/worktrees/phase-2-mcp/src/components/ReactQueryProvider.tsx +24 -0
  221. package/.claude/worktrees/phase-2-mcp/src/components/Sidebar.tsx +247 -0
  222. package/.claude/worktrees/phase-2-mcp/src/components/SkillDiffViewer.tsx +154 -0
  223. package/.claude/worktrees/phase-2-mcp/src/components/ThemeAwareBackground.tsx +67 -0
  224. package/.claude/worktrees/phase-2-mcp/src/components/ThemeToggle.tsx +54 -0
  225. package/.claude/worktrees/phase-2-mcp/src/components/WelcomeHero.tsx +77 -0
  226. package/.claude/worktrees/phase-2-mcp/src/components/bits/ClickSpark.tsx +116 -0
  227. package/.claude/worktrees/phase-2-mcp/src/components/bits/CountUp.tsx +98 -0
  228. package/.claude/worktrees/phase-2-mcp/src/components/bits/DarkSelect.tsx +95 -0
  229. package/.claude/worktrees/phase-2-mcp/src/components/bits/DecryptedText.tsx +161 -0
  230. package/.claude/worktrees/phase-2-mcp/src/components/bits/ElectricBorder.tsx +184 -0
  231. package/.claude/worktrees/phase-2-mcp/src/components/bits/GlitchText.tsx +34 -0
  232. package/.claude/worktrees/phase-2-mcp/src/components/bits/ShinyText.tsx +55 -0
  233. package/.claude/worktrees/phase-2-mcp/src/components/bits/SpotlightCard.tsx +42 -0
  234. package/.claude/worktrees/phase-2-mcp/src/components/bits/TextType.tsx +95 -0
  235. package/.claude/worktrees/phase-2-mcp/src/components/bits/index.ts +9 -0
  236. package/.claude/worktrees/phase-2-mcp/src/components/pages/CuratorPage.tsx +632 -0
  237. package/.claude/worktrees/phase-2-mcp/src/components/pages/DatasetPage.tsx +271 -0
  238. package/.claude/worktrees/phase-2-mcp/src/components/pages/EvolutionPage.tsx +676 -0
  239. package/.claude/worktrees/phase-2-mcp/src/components/pages/FunctionCallingPage.tsx +1 -0
  240. package/.claude/worktrees/phase-2-mcp/src/components/pages/LogsPage.tsx +272 -0
  241. package/.claude/worktrees/phase-2-mcp/src/components/pages/MetricsPage.tsx +246 -0
  242. package/.claude/worktrees/phase-2-mcp/src/components/pages/OverviewPage.tsx +420 -0
  243. package/.claude/worktrees/phase-2-mcp/src/components/pages/SettingsPage.tsx +88 -0
  244. package/.claude/worktrees/phase-2-mcp/src/components/pages/SkillStudioPage.tsx +376 -0
  245. package/.claude/worktrees/phase-2-mcp/src/components/ui/animated-theme-toggler.tsx +97 -0
  246. package/.claude/worktrees/phase-2-mcp/src/components/ui/button.tsx +67 -0
  247. package/.claude/worktrees/phase-2-mcp/src/components/ui/card.tsx +103 -0
  248. package/.claude/worktrees/phase-2-mcp/src/components/ui/input.tsx +19 -0
  249. package/.claude/worktrees/phase-2-mcp/src/components/ui/separator.tsx +28 -0
  250. package/.claude/worktrees/phase-2-mcp/src/components/ui/sheet.tsx +147 -0
  251. package/.claude/worktrees/phase-2-mcp/src/components/ui/sidebar.tsx +702 -0
  252. package/.claude/worktrees/phase-2-mcp/src/components/ui/skeleton.tsx +13 -0
  253. package/.claude/worktrees/phase-2-mcp/src/components/ui/theme-toggle.tsx +272 -0
  254. package/.claude/worktrees/phase-2-mcp/src/components/ui/tooltip.tsx +57 -0
  255. package/.claude/worktrees/phase-2-mcp/src/hooks/use-mobile.ts +19 -0
  256. package/.claude/worktrees/phase-2-mcp/src/lib/api.ts +455 -0
  257. package/.claude/worktrees/phase-2-mcp/src/lib/queryClient.ts +12 -0
  258. package/.claude/worktrees/phase-2-mcp/src/lib/utils.ts +6 -0
  259. package/.claude/worktrees/phase-2-mcp/stitch/agent_dashboard/DESIGN_SPEC.md +521 -0
  260. package/.claude/worktrees/phase-2-mcp/stitch/agent_dashboard/prototype.html +676 -0
  261. package/.claude/worktrees/phase-2-mcp/stitch/curator_workspace/code.html +448 -0
  262. package/.claude/worktrees/phase-2-mcp/stitch/curator_workspace/screen.png +0 -0
  263. package/.claude/worktrees/phase-2-mcp/stitch/datasets/code.html +479 -0
  264. package/.claude/worktrees/phase-2-mcp/stitch/datasets/screen.png +0 -0
  265. package/.claude/worktrees/phase-2-mcp/stitch/evolution_history/code.html +461 -0
  266. package/.claude/worktrees/phase-2-mcp/stitch/evolution_history/screen.png +0 -0
  267. package/.claude/worktrees/phase-2-mcp/stitch/hermes_dashboard/DESIGN.md +192 -0
  268. package/.claude/worktrees/phase-2-mcp/stitch/hermes_dashboard/DESIGN_SPEC.md +455 -0
  269. package/.claude/worktrees/phase-2-mcp/stitch/hermes_overview/code.html +399 -0
  270. package/.claude/worktrees/phase-2-mcp/stitch/hermes_overview/screen.png +0 -0
  271. package/.claude/worktrees/phase-2-mcp/stitch/live_logs/code.html +324 -0
  272. package/.claude/worktrees/phase-2-mcp/stitch/live_logs/screen.png +0 -0
  273. package/.claude/worktrees/phase-2-mcp/stitch/skill_hub/code.html +596 -0
  274. package/.claude/worktrees/phase-2-mcp/stitch/skill_hub/screen.png +0 -0
  275. package/.claude/worktrees/phase-2-mcp/stitch/system_metrics/code.html +527 -0
  276. package/.claude/worktrees/phase-2-mcp/stitch/system_metrics/screen.png +0 -0
  277. package/.claude/worktrees/phase-2-mcp/stitch/system_settings/code.html +257 -0
  278. package/.claude/worktrees/phase-2-mcp/stitch/system_settings/screen.png +0 -0
  279. package/.claude/worktrees/phase-2-mcp/test_dashboard.py +201 -0
  280. package/.claude/worktrees/phase-2-mcp/tests/collectors/__init__.py +0 -0
  281. package/.claude/worktrees/phase-2-mcp/tests/collectors/fixtures/sample_session.jsonl +7 -0
  282. package/.claude/worktrees/phase-2-mcp/tests/collectors/test_claude_code_collector.py +171 -0
  283. package/.claude/worktrees/phase-2-mcp/tests/collectors/test_hermes_collector.py +167 -0
  284. package/.claude/worktrees/phase-2-mcp/tests/eval/test_engine.py +234 -0
  285. package/.claude/worktrees/phase-2-mcp/tests/eval/test_scorers.py +249 -0
  286. package/.claude/worktrees/phase-2-mcp/tests/storage/__init__.py +0 -0
  287. package/.claude/worktrees/phase-2-mcp/tests/storage/test_run_store.py +359 -0
  288. package/.claude/worktrees/phase-2-mcp/tests/test_curator.py +559 -0
  289. package/.claude/worktrees/phase-2-mcp/tests/test_mcp_server.py +114 -0
  290. package/.claude/worktrees/phase-2-mcp/tsconfig.json +34 -0
  291. package/.env.example +72 -0
  292. package/.kilocode/package-lock.json +378 -0
  293. package/.kilocode/package.json +5 -0
  294. package/AGENTS.md +5 -0
  295. package/CLAUDE.md +29 -0
  296. package/QA_AUDIT_PLAN.md +156 -0
  297. package/README.md +355 -0
  298. package/agent-agnostic-evolution-dashboard.md +405 -0
  299. package/backend/__init__.py +0 -0
  300. package/backend/collectors/__init__.py +0 -0
  301. package/backend/collectors/claude_code_collector.py +277 -0
  302. package/backend/collectors/hermes_collector.py +68 -0
  303. package/backend/curator.py +512 -0
  304. package/backend/eval/__init__.py +19 -0
  305. package/backend/eval/engine.py +116 -0
  306. package/backend/eval/scorers.py +201 -0
  307. package/backend/generate_dataset.py +86 -0
  308. package/backend/job_tracker.py +232 -0
  309. package/backend/main.py +1746 -0
  310. package/backend/mcp_server.py +250 -0
  311. package/backend/promethean/__init__.py +24 -0
  312. package/backend/promethean/cycle_orchestrator.py +270 -0
  313. package/backend/promethean/delta_validator.py +191 -0
  314. package/backend/promethean/dspy_compiler.py +315 -0
  315. package/backend/promethean/gepa_strategist.py +213 -0
  316. package/backend/promethean/models.py +260 -0
  317. package/backend/promethean/skill_deployer.py +195 -0
  318. package/backend/promethean/trace_ingestion.py +142 -0
  319. package/backend/requirements.txt +6 -0
  320. package/backend/sdd_evolve.py +459 -0
  321. package/backend/skill_detector.py +227 -0
  322. package/backend/skill_registry.py +289 -0
  323. package/backend/storage/__init__.py +5 -0
  324. package/backend/storage/run_store.py +393 -0
  325. package/backend/storage/schema.sql +99 -0
  326. package/backend/validate_evolution.py +267 -0
  327. package/bin/genoma.js +250 -0
  328. package/components.json +28 -0
  329. package/docs/api/hermes-api.openapi.yaml +438 -0
  330. package/docs/hero.svg +148 -0
  331. package/eslint.config.mjs +18 -0
  332. package/install.sh +245 -0
  333. package/next-env.d.ts +6 -0
  334. package/next.config.ts +32 -0
  335. package/package.json +46 -0
  336. package/pnpm-workspace.yaml +4 -0
  337. package/postcss.config.mjs +7 -0
  338. package/public/file.svg +1 -0
  339. package/public/fonts/SF-Pro-Display-Bold.otf +0 -0
  340. package/public/fonts/SF-Pro-Display-Heavy.otf +0 -0
  341. package/public/fonts/SF-Pro-Display-Medium.otf +0 -0
  342. package/public/fonts/SF-Pro-Display-Regular.otf +0 -0
  343. package/public/fonts/SF-Pro-Display-Semibold.otf +0 -0
  344. package/public/fonts/SF-Pro-Text-Bold.otf +0 -0
  345. package/public/fonts/SF-Pro-Text-Heavy.otf +0 -0
  346. package/public/fonts/SF-Pro-Text-Medium.otf +0 -0
  347. package/public/fonts/SF-Pro-Text-Regular.otf +0 -0
  348. package/public/fonts/SF-Pro-Text-Semibold.otf +0 -0
  349. package/public/globe.svg +1 -0
  350. package/public/next.svg +1 -0
  351. package/public/theme-preview.html +257 -0
  352. package/public/vercel.svg +1 -0
  353. package/public/window.svg +1 -0
  354. package/run.sh +26 -0
  355. package/scripts/postinstall.js +50 -0
  356. package/skills-lock.json +10 -0
  357. package/specs/event-schema.md +223 -0
  358. package/specs/examples/run.jsonl +3 -0
  359. package/src/app/api/[...path]/route.ts +55 -0
  360. package/src/app/api/auth/token/route.ts +22 -0
  361. package/src/app/evolution/page.tsx +589 -0
  362. package/src/app/favicon.ico +0 -0
  363. package/src/app/globals.css +321 -0
  364. package/src/app/layout.tsx +63 -0
  365. package/src/app/page.tsx +70 -0
  366. package/src/app/skills/page.tsx +369 -0
  367. package/src/components/ApiConfigCard.tsx +199 -0
  368. package/src/components/ColorBends.css +1 -0
  369. package/src/components/ColorBends.d.ts +1 -0
  370. package/src/components/ColorBends.jsx +1 -0
  371. package/src/components/CoreLoopToggle.tsx +111 -0
  372. package/src/components/EnvironmentStatus.tsx +176 -0
  373. package/src/components/EvolutionBackground.tsx +1 -0
  374. package/src/components/ReactQueryProvider.tsx +24 -0
  375. package/src/components/Sidebar.tsx +247 -0
  376. package/src/components/SkillDiffViewer.tsx +154 -0
  377. package/src/components/ThemeAwareBackground.tsx +67 -0
  378. package/src/components/ThemeToggle.tsx +54 -0
  379. package/src/components/WelcomeHero.tsx +77 -0
  380. package/src/components/bits/ClickSpark.tsx +116 -0
  381. package/src/components/bits/CountUp.tsx +98 -0
  382. package/src/components/bits/DarkSelect.tsx +95 -0
  383. package/src/components/bits/DecryptedText.tsx +161 -0
  384. package/src/components/bits/ElectricBorder.tsx +184 -0
  385. package/src/components/bits/GlitchText.tsx +34 -0
  386. package/src/components/bits/ShinyText.tsx +55 -0
  387. package/src/components/bits/SpotlightCard.tsx +42 -0
  388. package/src/components/bits/TextType.tsx +95 -0
  389. package/src/components/bits/index.ts +9 -0
  390. package/src/components/pages/CuratorPage.tsx +632 -0
  391. package/src/components/pages/DatasetPage.tsx +271 -0
  392. package/src/components/pages/EvolutionPage.tsx +676 -0
  393. package/src/components/pages/FunctionCallingPage.tsx +1 -0
  394. package/src/components/pages/LogsPage.tsx +272 -0
  395. package/src/components/pages/MetricsPage.tsx +246 -0
  396. package/src/components/pages/OverviewPage.tsx +420 -0
  397. package/src/components/pages/SettingsPage.tsx +88 -0
  398. package/src/components/pages/SkillStudioPage.tsx +376 -0
  399. package/src/components/ui/animated-theme-toggler.tsx +97 -0
  400. package/src/components/ui/button.tsx +67 -0
  401. package/src/components/ui/card.tsx +103 -0
  402. package/src/components/ui/input.tsx +19 -0
  403. package/src/components/ui/separator.tsx +28 -0
  404. package/src/components/ui/sheet.tsx +147 -0
  405. package/src/components/ui/sidebar.tsx +702 -0
  406. package/src/components/ui/skeleton.tsx +13 -0
  407. package/src/components/ui/theme-toggle.tsx +272 -0
  408. package/src/components/ui/tooltip.tsx +57 -0
  409. package/src/hooks/use-mobile.ts +19 -0
  410. package/src/lib/api.ts +455 -0
  411. package/src/lib/queryClient.ts +12 -0
  412. package/src/lib/utils.ts +6 -0
  413. package/stitch/agent_dashboard/DESIGN_SPEC.md +521 -0
  414. package/stitch/agent_dashboard/prototype.html +676 -0
  415. package/stitch/curator_workspace/code.html +448 -0
  416. package/stitch/curator_workspace/screen.png +0 -0
  417. package/stitch/datasets/code.html +479 -0
  418. package/stitch/datasets/screen.png +0 -0
  419. package/stitch/evolution_history/code.html +461 -0
  420. package/stitch/evolution_history/screen.png +0 -0
  421. package/stitch/hermes_dashboard/DESIGN.md +192 -0
  422. package/stitch/hermes_dashboard/DESIGN_SPEC.md +455 -0
  423. package/stitch/hermes_overview/code.html +399 -0
  424. package/stitch/hermes_overview/screen.png +0 -0
  425. package/stitch/live_logs/code.html +324 -0
  426. package/stitch/live_logs/screen.png +0 -0
  427. package/stitch/skill_hub/code.html +596 -0
  428. package/stitch/skill_hub/screen.png +0 -0
  429. package/stitch/system_metrics/code.html +527 -0
  430. package/stitch/system_metrics/screen.png +0 -0
  431. package/stitch/system_settings/code.html +257 -0
  432. package/stitch/system_settings/screen.png +0 -0
  433. package/test_dashboard.py +201 -0
  434. package/tests/collectors/__init__.py +0 -0
  435. package/tests/collectors/fixtures/sample_session.jsonl +7 -0
  436. package/tests/collectors/test_claude_code_collector.py +171 -0
  437. package/tests/collectors/test_hermes_collector.py +167 -0
  438. package/tests/eval/test_engine.py +234 -0
  439. package/tests/eval/test_scorers.py +249 -0
  440. package/tests/storage/__init__.py +0 -0
  441. package/tests/storage/test_run_store.py +359 -0
  442. package/tests/test_curator.py +559 -0
  443. package/tests/test_e2e_npm.py +621 -0
  444. package/tests/test_mcp_server.py +114 -0
  445. package/tsconfig.json +34 -0
@@ -0,0 +1,559 @@
1
+ #!/usr/bin/env python3
2
+ """Comprehensive test suite for the Hermes Dashboard Curator module.
3
+
4
+ Tests both the curator.py module functions and the API endpoints.
5
+
6
+ Run: python3 -m pytest tests/test_curator.py -v
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import sys
12
+ import tempfile
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+ from fastapi.testclient import TestClient
17
+
18
+ # ── Fixtures ────────────────────────────────────────────────────────
19
+
20
+
21
+ @pytest.fixture
22
+ def temp_skills_dir(tmp_path):
23
+ """Create a temporary skills directory structure for testing."""
24
+ skills_dir = tmp_path / ".hermes" / "skills"
25
+ skills_dir.mkdir(parents=True)
26
+
27
+ # Create a bundled manifest
28
+ bundled = skills_dir / ".bundled_manifest"
29
+ bundled.write_text("bundled-skill:abc123\nanother-bundled:def456\n")
30
+
31
+ # Create .archive dir
32
+ archive_dir = skills_dir / ".archive"
33
+ archive_dir.mkdir()
34
+
35
+ return skills_dir
36
+
37
+
38
+ @pytest.fixture
39
+ def temp_usage_file(temp_skills_dir):
40
+ """Create a usage.json with sample data."""
41
+ usage = {
42
+ "active-skill": {
43
+ "use_count": 10,
44
+ "view_count": 25,
45
+ "patch_count": 2,
46
+ "state": "active",
47
+ "pinned": False,
48
+ "created_at": "2026-03-01T14:20:00+00:00",
49
+ "last_used_at": "2026-04-28T18:12:03+00:00",
50
+ "last_viewed_at": "2026-04-28T09:44:17+00:00",
51
+ "last_patched_at": "2026-04-20T22:01:55+00:00",
52
+ "archived_at": None,
53
+ },
54
+ "stale-skill": {
55
+ "use_count": 3,
56
+ "view_count": 5,
57
+ "patch_count": 0,
58
+ "state": "stale",
59
+ "pinned": False,
60
+ "created_at": "2026-01-15T10:00:00+00:00",
61
+ "last_used_at": "2026-03-20T08:30:00+00:00",
62
+ "last_viewed_at": "2026-03-20T08:30:00+00:00",
63
+ "last_patched_at": None,
64
+ "archived_at": None,
65
+ },
66
+ "archived-skill": {
67
+ "use_count": 1,
68
+ "view_count": 2,
69
+ "patch_count": 0,
70
+ "state": "archived",
71
+ "pinned": False,
72
+ "created_at": "2025-12-01T10:00:00+00:00",
73
+ "last_used_at": "2026-01-10T14:00:00+00:00",
74
+ "last_viewed_at": "2026-01-10T14:00:00+00:00",
75
+ "last_patched_at": None,
76
+ "archived_at": "2026-04-10T10:00:00+00:00",
77
+ },
78
+ "pinned-skill": {
79
+ "use_count": 5,
80
+ "view_count": 8,
81
+ "patch_count": 1,
82
+ "state": "active",
83
+ "pinned": True,
84
+ "created_at": "2026-02-10T09:00:00+00:00",
85
+ "last_used_at": "2026-04-25T16:45:00+00:00",
86
+ "last_viewed_at": "2026-04-24T11:30:00+00:00",
87
+ "last_patched_at": "2026-04-22T10:15:00+00:00",
88
+ "archived_at": None,
89
+ },
90
+ "bundled-skill": {
91
+ "use_count": 20,
92
+ "view_count": 50,
93
+ "patch_count": 5,
94
+ "state": "active",
95
+ "pinned": False,
96
+ "created_at": "2026-01-01T00:00:00+00:00",
97
+ "last_used_at": "2026-04-29T10:00:00+00:00",
98
+ "last_viewed_at": "2026-04-29T10:00:00+00:00",
99
+ "last_patched_at": "2026-04-28T15:00:00+00:00",
100
+ "archived_at": None,
101
+ },
102
+ }
103
+ usage_file = temp_skills_dir / ".usage.json"
104
+ usage_file.write_text(json.dumps(usage, indent=2))
105
+ return usage_file
106
+
107
+
108
+ @pytest.fixture
109
+ def curator_module(temp_skills_dir, temp_usage_file, monkeypatch):
110
+ """Patch curator paths to use temp directory and import the module."""
111
+ import importlib
112
+
113
+ # Patch all paths in curator.py
114
+ monkeypatch.setattr(
115
+ "backend.curator.SKILLS_DIR", temp_skills_dir
116
+ )
117
+ monkeypatch.setattr(
118
+ "backend.curator.USAGE_FILE", temp_skills_dir / ".usage.json"
119
+ )
120
+ monkeypatch.setattr(
121
+ "backend.curator.CURATOR_LOG_DIR", temp_skills_dir.parent / "logs" / "curator"
122
+ )
123
+ monkeypatch.setattr(
124
+ "backend.curator.ARCHIVE_DIR", temp_skills_dir / ".archive"
125
+ )
126
+ monkeypatch.setattr(
127
+ "backend.curator.BUNDLED_MANIFEST", temp_skills_dir / ".bundled_manifest"
128
+ )
129
+
130
+ # Ensure paths exist
131
+ (temp_skills_dir.parent / "logs" / "curator").mkdir(parents=True, exist_ok=True)
132
+
133
+ # Reload to pick up patched paths
134
+ if "backend.curator" in sys.modules:
135
+ del sys.modules["backend.curator"]
136
+ from backend import curator
137
+ return curator
138
+
139
+
140
+ # ═══════════════════════════════════════════════════════════════════════
141
+ # Unit Tests — Module Functions
142
+ # ═══════════════════════════════════════════════════════════════════════
143
+
144
+
145
+ class TestLoadSaveUsage:
146
+ def test_load_returns_empty_when_no_file(self, temp_skills_dir, monkeypatch):
147
+ monkeypatch.setattr("backend.curator.USAGE_FILE", temp_skills_dir / "nonexistent.json")
148
+ from backend.curator import _load_usage
149
+ assert _load_usage() == {}
150
+
151
+ def test_save_and_load_roundtrip(self, temp_skills_dir, monkeypatch):
152
+ usage_file = temp_skills_dir / ".usage.json"
153
+ monkeypatch.setattr("backend.curator.USAGE_FILE", usage_file)
154
+ from backend.curator import _save_usage, _load_usage
155
+
156
+ data = {"test-skill": {"use_count": 42, "state": "active"}}
157
+ _save_usage(data)
158
+ loaded = _load_usage()
159
+ assert loaded == data
160
+
161
+ def test_save_creates_parent_dir(self, temp_skills_dir, monkeypatch):
162
+ nested = temp_skills_dir / "nested" / "sub" / ".usage.json"
163
+ monkeypatch.setattr("backend.curator.USAGE_FILE", nested)
164
+ from backend.curator import _save_usage
165
+ _save_usage({"x": {"state": "active"}})
166
+ assert nested.exists()
167
+
168
+
169
+ class TestIsAgentCreated:
170
+ def test_bundled_skill_returns_false(self, temp_skills_dir, monkeypatch):
171
+ monkeypatch.setattr("backend.curator.BUNDLED_MANIFEST", temp_skills_dir / ".bundled_manifest")
172
+ # Ensure bundled manifest exists
173
+ (temp_skills_dir / ".bundled_manifest").write_text("bundled-skill:abc123\n")
174
+ from backend.curator import _is_agent_created
175
+ assert _is_agent_created("bundled-skill") is False
176
+
177
+ def test_unknown_skill_returns_true(self, temp_skills_dir, monkeypatch):
178
+ monkeypatch.setattr("backend.curator.BUNDLED_MANIFEST", temp_skills_dir / ".bundled_manifest")
179
+ from backend.curator import _is_agent_created
180
+ assert _is_agent_created("my-custom-skill") is True
181
+
182
+ def test_no_manifest_returns_true(self, monkeypatch):
183
+ # No manifest at all
184
+ from backend.curator import _is_agent_created
185
+ assert _is_agent_created("anything") is True
186
+
187
+
188
+ class TestSkillExists:
189
+ def test_exists_in_primary(self, temp_skills_dir, monkeypatch):
190
+ skill_dir = temp_skills_dir / "my-skill"
191
+ skill_dir.mkdir()
192
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
193
+ from backend.curator import _skill_exists
194
+ assert _skill_exists("my-skill") is True
195
+
196
+ def test_exists_in_archive(self, temp_skills_dir, monkeypatch):
197
+ archive_dir = temp_skills_dir / ".archive"
198
+ archive_dir.mkdir(exist_ok=True)
199
+ (archive_dir / "archived-skill").mkdir()
200
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
201
+ monkeypatch.setattr("backend.curator.ARCHIVE_DIR", archive_dir)
202
+ from backend.curator import _skill_exists
203
+ assert _skill_exists("archived-skill") is True
204
+
205
+ def test_not_found(self, temp_skills_dir, monkeypatch):
206
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
207
+ from backend.curator import _skill_exists
208
+ assert _skill_exists("nonexistent") is False
209
+
210
+
211
+ class TestRecordUse:
212
+ def test_record_use_increments_counter(self, temp_skills_dir, monkeypatch):
213
+ # Create skill dir so _skill_exists passes
214
+ (temp_skills_dir / "test-skill").mkdir()
215
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
216
+ monkeypatch.setattr("backend.curator.USAGE_FILE", temp_skills_dir / ".usage.json")
217
+ # Remove old manifest so it's agent-created
218
+ if (temp_skills_dir / ".bundled_manifest").exists():
219
+ (temp_skills_dir / ".bundled_manifest").unlink()
220
+
221
+ from backend.curator import record_use, get_skills_usage
222
+
223
+ result = record_use("test-skill", "use")
224
+ assert result["status"] == "ok"
225
+ assert result["action"] == "use"
226
+
227
+ skills = get_skills_usage()
228
+ assert len(skills) == 1
229
+ assert skills[0]["name"] == "test-skill"
230
+ assert skills[0]["use_count"] == 1
231
+ assert skills[0]["state"] == "active"
232
+
233
+ def test_record_view_and_patch(self, temp_skills_dir, monkeypatch):
234
+ (temp_skills_dir / "test-skill").mkdir()
235
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
236
+ monkeypatch.setattr("backend.curator.USAGE_FILE", temp_skills_dir / ".usage.json")
237
+ if (temp_skills_dir / ".bundled_manifest").exists():
238
+ (temp_skills_dir / ".bundled_manifest").unlink()
239
+
240
+ from backend.curator import record_use, get_skills_usage
241
+
242
+ record_use("test-skill", "view")
243
+ record_use("test-skill", "patch")
244
+
245
+ skills = get_skills_usage()
246
+ assert skills[0]["view_count"] == 1
247
+ assert skills[0]["patch_count"] == 1
248
+
249
+ def test_unknown_skill_returns_error(self, temp_skills_dir, monkeypatch):
250
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
251
+ monkeypatch.setattr("backend.curator.USAGE_FILE", temp_skills_dir / ".usage.json")
252
+ from backend.curator import record_use
253
+ result = record_use("nonexistent", "use")
254
+ assert result["status"] == "error"
255
+
256
+ def test_bundled_skill_skips(self, temp_skills_dir, monkeypatch):
257
+ # Keep the manifest so bundled-skill is recognized
258
+ (temp_skills_dir / ".bundled_manifest").write_text("bundled-skill:abc123\n")
259
+ # Create dir so _skill_exists passes
260
+ (temp_skills_dir / "bundled-skill").mkdir()
261
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
262
+ monkeypatch.setattr("backend.curator.BUNDLED_MANIFEST", temp_skills_dir / ".bundled_manifest")
263
+ from backend.curator import record_use
264
+ result = record_use("bundled-skill", "use")
265
+ assert result["status"] == "skipped"
266
+
267
+
268
+ class TestPinUnpin:
269
+ def test_pin_skill(self, temp_skills_dir, monkeypatch):
270
+ (temp_skills_dir / "my-skill").mkdir()
271
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
272
+ monkeypatch.setattr("backend.curator.USAGE_FILE", temp_skills_dir / ".usage.json")
273
+ if (temp_skills_dir / ".bundled_manifest").exists():
274
+ (temp_skills_dir / ".bundled_manifest").unlink()
275
+
276
+ from backend.curator import pin_skill, get_status
277
+ result = pin_skill("my-skill")
278
+ assert result["status"] == "ok"
279
+ assert result["pinned"] is True
280
+
281
+ status = get_status()
282
+ assert status["stats"]["pinned"] == 1
283
+ assert "my-skill" in status["pinned_skills"]
284
+
285
+ def test_unpin_skill(self, temp_skills_dir, monkeypatch):
286
+ """Unpin a skill that exists in usage data."""
287
+ usage_file = temp_skills_dir / ".usage.json"
288
+ usage_file.write_text(json.dumps({
289
+ "pinned-skill": {
290
+ "use_count": 5, "view_count": 8, "patch_count": 1,
291
+ "state": "active", "pinned": True,
292
+ "created_at": "2026-02-10T09:00:00+00:00",
293
+ "last_used_at": "2026-04-25T16:45:00+00:00",
294
+ "last_viewed_at": None, "last_patched_at": None,
295
+ "archived_at": None,
296
+ }
297
+ }))
298
+ monkeypatch.setattr("backend.curator.USAGE_FILE", usage_file)
299
+ from backend.curator import unpin_skill
300
+ result = unpin_skill("pinned-skill")
301
+ assert result["status"] == "ok"
302
+ assert result["pinned"] is False
303
+
304
+ def test_pin_bundled_refuses(self, temp_skills_dir, monkeypatch):
305
+ (temp_skills_dir / "bundled-skill").mkdir()
306
+ (temp_skills_dir / ".bundled_manifest").write_text("bundled-skill:abc123\n")
307
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
308
+ monkeypatch.setattr("backend.curator.BUNDLED_MANIFEST", temp_skills_dir / ".bundled_manifest")
309
+ from backend.curator import pin_skill
310
+ result = pin_skill("bundled-skill")
311
+ assert result["status"] == "error"
312
+ assert "never touched" in result["message"]
313
+
314
+
315
+ class TestGetStatus:
316
+ def test_status_returns_stats(self, curator_module, temp_usage_file):
317
+ status = curator_module.get_status()
318
+ assert status["status"] == "ok"
319
+ assert status["stats"]["active"] >= 3 # active-skill, pinned-skill, bundled-skill
320
+ assert status["stats"]["stale"] == 1
321
+ assert status["stats"]["archived"] == 1
322
+ assert status["stats"]["pinned"] == 1
323
+
324
+ def test_status_returns_lru(self, curator_module):
325
+ status = curator_module.get_status()
326
+ assert len(status["least_recently_used"]) >= 1
327
+
328
+ def test_status_last_run_none_when_no_reports(self, curator_module):
329
+ status = curator_module.get_status()
330
+ assert status["last_run"]["timestamp"] is None
331
+
332
+
333
+ class TestGetSkillsUsage:
334
+ def test_returns_sorted_by_use(self, curator_module):
335
+ skills = curator_module.get_skills_usage()
336
+ assert len(skills) >= 4
337
+
338
+ # Should be sorted by use_count descending
339
+ counts = [s["use_count"] for s in skills]
340
+ assert counts == sorted(counts, reverse=True)
341
+
342
+ def test_each_skill_has_required_fields(self, curator_module):
343
+ skills = curator_module.get_skills_usage()
344
+ for s in skills:
345
+ assert "name" in s
346
+ assert "state" in s
347
+ assert "pinned" in s
348
+ assert "use_count" in s
349
+ assert "view_count" in s
350
+ assert "agent_created" in s
351
+
352
+ def test_bundled_skill_flagged(self, curator_module):
353
+ skills = curator_module.get_skills_usage()
354
+ bundled = [s for s in skills if s["name"] == "bundled-skill"]
355
+ if bundled:
356
+ assert bundled[0]["agent_created"] is False
357
+
358
+
359
+ class TestCuratorRun:
360
+ def test_run_marks_stale_skills(self, temp_skills_dir, monkeypatch):
361
+ """Test that skills unused for >30 days become stale."""
362
+ import datetime
363
+
364
+ # Create a skill with old usage data
365
+ usage = {
366
+ "old-skill": {
367
+ "use_count": 1,
368
+ "view_count": 1,
369
+ "patch_count": 0,
370
+ "state": "active",
371
+ "pinned": False,
372
+ "created_at": "2026-01-01T10:00:00+00:00",
373
+ "last_used_at": "2026-01-15T10:00:00+00:00",
374
+ "last_viewed_at": "2026-01-15T10:00:00+00:00",
375
+ "recent-skill": {
376
+ "use_count": 5,
377
+ "view_count": 10,
378
+ "patch_count": 0,
379
+ "state": "active",
380
+ "pinned": False,
381
+ "created_at": "2026-04-01T10:00:00+00:00",
382
+ "last_used_at": "2026-04-28T10:00:00+00:00",
383
+ "last_viewed_at": "2026-04-28T10:00:00+00:00",
384
+ },
385
+ }
386
+ }
387
+
388
+ usage_file = temp_skills_dir / ".usage.json"
389
+ usage_file.write_text(json.dumps(usage))
390
+
391
+ monkeypatch.setattr("backend.curator.SKILLS_DIR", temp_skills_dir)
392
+ monkeypatch.setattr("backend.curator.USAGE_FILE", usage_file)
393
+ monkeypatch.setattr("backend.curator.CURATOR_LOG_DIR", temp_skills_dir.parent / "logs" / "curator")
394
+ (temp_skills_dir.parent / "logs" / "curator").mkdir(parents=True, exist_ok=True)
395
+
396
+ from backend.curator import run_curator_review
397
+
398
+ result = run_curator_review(sync=True)
399
+ assert result["status"] == "ok"
400
+ assert "report_id" in result
401
+
402
+
403
+ # ═══════════════════════════════════════════════════════════════════════
404
+ # Integration Tests — API Endpoints
405
+ # ═══════════════════════════════════════════════════════════════════════
406
+
407
+
408
+ @pytest.fixture
409
+ def client(tmp_path, monkeypatch):
410
+ """Create a TestClient with patched curator paths and sample data."""
411
+ skills_dir = tmp_path / ".hermes" / "skills"
412
+ skills_dir.mkdir(parents=True)
413
+
414
+ # Create bundled manifest
415
+ (skills_dir / ".bundled_manifest").write_text("bundled-skill:abc123\n")
416
+
417
+ # Create .archive
418
+ (skills_dir / ".archive").mkdir()
419
+
420
+ # Write sample usage data
421
+ usage = {
422
+ "active-skill": {"use_count": 10, "view_count": 25, "patch_count": 2,
423
+ "state": "active", "pinned": False,
424
+ "created_at": "2026-03-01T14:20:00+00:00",
425
+ "last_used_at": "2026-04-28T18:12:03+00:00",
426
+ "last_viewed_at": "2026-04-28T09:44:17+00:00",
427
+ "last_patched_at": "2026-04-20T22:01:55+00:00",
428
+ "archived_at": None},
429
+ "pinned-skill": {"use_count": 5, "view_count": 8, "patch_count": 1,
430
+ "state": "active", "pinned": True,
431
+ "created_at": "2026-02-10T09:00:00+00:00",
432
+ "last_used_at": "2026-04-25T16:45:00+00:00",
433
+ "last_viewed_at": None, "last_patched_at": None,
434
+ "archived_at": None},
435
+ "stale-skill": {"use_count": 3, "view_count": 5, "patch_count": 0,
436
+ "state": "stale", "pinned": False,
437
+ "created_at": "2026-01-15T10:00:00+00:00",
438
+ "last_used_at": "2026-03-20T08:30:00+00:00",
439
+ "last_viewed_at": "2026-03-20T08:30:00+00:00",
440
+ "last_patched_at": None, "archived_at": None},
441
+ }
442
+ usage_file = skills_dir / ".usage.json"
443
+ usage_file.write_text(json.dumps(usage, indent=2))
444
+
445
+ # Create some skill dirs so _skill_exists passes
446
+ for s in ["active-skill", "pinned-skill", "bundled-skill"]:
447
+ (skills_dir / s).mkdir(exist_ok=True)
448
+
449
+ # First import the curator module so it exists, THEN monkeypatch
450
+ import backend.curator as curator_mod
451
+
452
+ # Monkeypatch the already-imported module
453
+ monkeypatch.setattr(curator_mod, "SKILLS_DIR", skills_dir)
454
+ monkeypatch.setattr(curator_mod, "USAGE_FILE", usage_file)
455
+ monkeypatch.setattr(curator_mod, "CURATOR_LOG_DIR", tmp_path / "logs" / "curator")
456
+ monkeypatch.setattr(curator_mod, "ARCHIVE_DIR", skills_dir / ".archive")
457
+ monkeypatch.setattr(curator_mod, "BUNDLED_MANIFEST", skills_dir / ".bundled_manifest")
458
+
459
+ # Ensure logs dir
460
+ (tmp_path / "logs" / "curator").mkdir(parents=True, exist_ok=True)
461
+
462
+ # Now clear the cached main module so it re-imports curator fresh
463
+ if "backend.main" in sys.modules:
464
+ del sys.modules["backend.main"]
465
+
466
+ # Import main — it will import the patched curator
467
+ from backend import main as backend_main
468
+
469
+ # Store skills_dir in module-level var for test access
470
+ curator_mod._test_skills_dir = skills_dir
471
+
472
+ return TestClient(backend_main.app)
473
+
474
+
475
+ class TestAPIEndpoints:
476
+ def _skills_dir(self, client) -> Path:
477
+ """Get the skills_dir from the module-level test var."""
478
+ import backend.curator
479
+ return backend.curator._test_skills_dir
480
+
481
+ def test_get_status(self, client):
482
+ resp = client.get("/api/curator/status")
483
+ assert resp.status_code == 200
484
+ data = resp.json()
485
+ assert data["status"] == "ok"
486
+ assert "stats" in data
487
+ assert "pinned_skills" in data
488
+ assert "least_recently_used" in data
489
+
490
+ def test_get_skills(self, client):
491
+ resp = client.get("/api/curator/skills")
492
+ assert resp.status_code == 200
493
+ data = resp.json()
494
+ assert "skills" in data
495
+ assert isinstance(data["skills"], list)
496
+
497
+ def test_pin_skill(self, client):
498
+ # Create skill dir in the same temp dir the client fixture uses
499
+ skills_dir = self._skills_dir(client)
500
+ (skills_dir / "my-custom").mkdir(exist_ok=True)
501
+ resp = client.post("/api/curator/pin/my-custom")
502
+ assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text}"
503
+ data = resp.json()
504
+ assert data["pinned"] is True
505
+
506
+ def test_unpin_skill(self, client):
507
+ resp = client.post("/api/curator/unpin/pinned-skill")
508
+ assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text}"
509
+
510
+ def test_run_curator(self, client):
511
+ resp = client.post("/api/curator/run")
512
+ assert resp.status_code == 200
513
+ data = resp.json()
514
+ assert data["status"] == "ok"
515
+ assert "report_id" in data
516
+
517
+ def test_get_reports(self, client):
518
+ resp = client.get("/api/curator/reports")
519
+ assert resp.status_code == 200
520
+ data = resp.json()
521
+ assert "reports" in data
522
+
523
+ def test_record_use(self, client):
524
+ skills_dir = self._skills_dir(client)
525
+ (skills_dir / "api-test-skill").mkdir(exist_ok=True)
526
+ resp = client.post(
527
+ "/api/curator/record-use",
528
+ json={"skill": "api-test-skill", "action": "use"},
529
+ )
530
+ assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text}"
531
+ data = resp.json()
532
+ assert data["status"] == "ok"
533
+
534
+ def test_record_use_missing_field(self, client):
535
+ resp = client.post("/api/curator/record-use", json={})
536
+ assert resp.status_code == 400
537
+
538
+ def test_record_use_invalid_action(self, client):
539
+ resp = client.post(
540
+ "/api/curator/record-use",
541
+ json={"skill": "test", "action": "invalid"},
542
+ )
543
+ assert resp.status_code == 400
544
+
545
+ def test_pin_bundled_returns_400(self, client):
546
+ resp = client.post("/api/curator/pin/bundled-skill")
547
+ assert resp.status_code == 400
548
+
549
+ def test_nonexistent_pin_returns_400(self, client):
550
+ resp = client.post("/api/curator/pin/nonexistent-skill")
551
+ assert resp.status_code == 400
552
+
553
+ def test_restore_nonexistent_returns_400(self, client):
554
+ resp = client.post("/api/curator/restore/nonexistent")
555
+ assert resp.status_code == 400
556
+
557
+
558
+ if __name__ == "__main__":
559
+ pytest.main(["-v", __file__])
@@ -0,0 +1,114 @@
1
+ """Tests for backend.mcp_server MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ from backend.mcp_server import server, handle_tool_call
8
+ from backend.storage.run_store import RunStore
9
+
10
+
11
+ def test_server_imports():
12
+ """Server should import successfully."""
13
+ assert server is not None
14
+ assert server.name == "genoma"
15
+
16
+
17
+ def test_server_has_instructions():
18
+ """Server should have proper instructions."""
19
+ assert "ingest_run" in server.instructions
20
+ assert "ingest_trace" in server.instructions
21
+ assert "query_runs" in server.instructions
22
+ assert "get_agent_stats" in server.instructions
23
+
24
+
25
+ def test_ingest_run_minimal():
26
+ """ingest_run should accept minimal required fields."""
27
+ async def run_test():
28
+ result = await handle_tool_call(
29
+ "ingest_run",
30
+ {
31
+ "run_id": "test-001",
32
+ "agent_name": "test-agent",
33
+ "started_at": "2026-05-19T00:00:00Z",
34
+ "task_name": "test-task",
35
+ "outcome": "success",
36
+ },
37
+ )
38
+ assert result.isError is False
39
+ assert "inserted" in result.content[0].text or "updated" in result.content[0].text
40
+ assert "test-001" in result.content[0].text
41
+
42
+ asyncio.run(run_test())
43
+
44
+
45
+ def test_ingest_run_with_collector():
46
+ """ingest_run should set collector='mcp-native' by default."""
47
+ async def run_test():
48
+ result = await handle_tool_call(
49
+ "ingest_run",
50
+ {
51
+ "run_id": "test-002",
52
+ "agent_name": "test",
53
+ "started_at": "2026-05-19T00:00:00Z",
54
+ "task_name": "test",
55
+ "outcome": "success",
56
+ },
57
+ )
58
+ assert result.isError is False
59
+ run = RunStore().get_run("test-002")
60
+ assert run.collector == "mcp-native"
61
+
62
+ asyncio.run(run_test())
63
+
64
+
65
+ def test_ingest_trace():
66
+ """ingest_trace should accept required fields."""
67
+ async def run_test():
68
+ result = await handle_tool_call(
69
+ "ingest_trace",
70
+ {
71
+ "agent": "test-agent",
72
+ "agent_version": "1.0.0",
73
+ "timestamp": "2026-05-19T00:00:00Z",
74
+ "task": "test-task",
75
+ "outcome": "success",
76
+ },
77
+ )
78
+ assert result.isError is False
79
+ assert "ingested" in result.content[0].text
80
+
81
+ asyncio.run(run_test())
82
+
83
+
84
+ def test_query_runs():
85
+ """query_runs should work on populated store."""
86
+ async def run_test():
87
+ result = await handle_tool_call("query_runs", {})
88
+ assert result.isError is False
89
+ content = result.content[0].text
90
+ assert "count" in content
91
+
92
+ asyncio.run(run_test())
93
+
94
+
95
+ def test_get_agent_stats():
96
+ """get_agent_stats should return agent list."""
97
+ async def run_test():
98
+ result = await handle_tool_call("get_agent_stats", {})
99
+ assert result.isError is False
100
+ content = result.content[0].text
101
+ assert "agents" in content
102
+
103
+ asyncio.run(run_test())
104
+
105
+
106
+ if __name__ == "__main__":
107
+ test_server_imports()
108
+ test_server_has_instructions()
109
+ test_ingest_run_minimal()
110
+ test_ingest_run_with_collector()
111
+ test_ingest_trace()
112
+ test_query_runs()
113
+ test_get_agent_stats()
114
+ print("✓ All tests passed")