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.
- package/.brv/.obsidian/app.json +1 -0
- package/.brv/.obsidian/appearance.json +1 -0
- package/.brv/.obsidian/core-plugins.json +33 -0
- package/.brv/.obsidian/graph.json +22 -0
- package/.brv/.obsidian/workspace.json +195 -0
- package/.brv/Sin ti/314/201tulo 1.canvas" +1 -0
- package/.brv/Sin ti/314/201tulo 2.canvas" +1 -0
- package/.brv/Sin ti/314/201tulo.canvas" +1 -0
- package/.brv/_queue_status.json +1 -0
- package/.brv/config.json +5 -0
- package/.brv/context-tree/_index.md +60 -0
- package/.brv/context-tree/_manifest.json +165 -0
- package/.brv/context-tree/backend/_index.md +24 -0
- package/.brv/context-tree/backend/backend/_index.md +40 -0
- package/.brv/context-tree/backend/backend/init.abstract.md +0 -0
- package/.brv/context-tree/backend/backend/init.md +27 -0
- package/.brv/context-tree/backend/backend/init.overview.md +29 -0
- package/.brv/context-tree/backend/backend/job_tracker.abstract.md +1 -0
- package/.brv/context-tree/backend/backend/job_tracker.md +273 -0
- package/.brv/context-tree/backend/backend/job_tracker.overview.md +31 -0
- package/.brv/context-tree/backend/backend/main.abstract.md +0 -0
- package/.brv/context-tree/backend/backend/main.md +1292 -0
- package/.brv/context-tree/backend/backend/main.overview.md +30 -0
- package/.brv/context-tree/backend/backend/requirements.abstract.md +1 -0
- package/.brv/context-tree/backend/backend/requirements.md +37 -0
- package/.brv/context-tree/backend/backend/requirements.overview.md +28 -0
- package/.brv/context-tree/docs/_index.md +37 -0
- package/.brv/context-tree/docs/api/_index.md +54 -0
- package/.brv/context-tree/docs/api/context.md +11 -0
- package/.brv/context-tree/docs/api/hermes_api_openapi_specification.abstract.md +0 -0
- package/.brv/context-tree/docs/api/hermes_api_openapi_specification.md +468 -0
- package/.brv/context-tree/docs/api/hermes_api_openapi_specification.overview.md +44 -0
- package/.brv/context-tree/frontend/_index.md +48 -0
- package/.brv/context-tree/frontend/hermes_dashboard/_index.md +31 -0
- package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.abstract.md +0 -0
- package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.md +41 -0
- package/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.overview.md +34 -0
- package/.brv/context-tree/frontend/src/_index.md +53 -0
- package/.brv/context-tree/frontend/src/components/_index.md +52 -0
- package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.abstract.md +0 -0
- package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.md +161 -0
- package/.brv/context-tree/frontend/src/components/sidebar_navigation_component.overview.md +32 -0
- package/.brv/context-tree/frontend/src/context.md +10 -0
- package/.brv/context-tree/frontend/src/functioncallingpage.abstract.md +0 -0
- package/.brv/context-tree/frontend/src/functioncallingpage.md +34 -0
- package/.brv/context-tree/frontend/src/functioncallingpage.overview.md +26 -0
- package/.brv/context-tree/frontend/src/lib/_index.md +48 -0
- package/.brv/context-tree/frontend/src/lib/api_client_library.abstract.md +1 -0
- package/.brv/context-tree/frontend/src/lib/api_client_library.md +403 -0
- package/.brv/context-tree/frontend/src/lib/api_client_library.overview.md +69 -0
- package/.brv/context-tree/frontend/src/page.abstract.md +0 -0
- package/.brv/context-tree/frontend/src/page.md +103 -0
- package/.brv/context-tree/frontend/src/page.overview.md +7 -0
- package/.brv/context-tree/frontend/src/settingspage.abstract.md +0 -0
- package/.brv/context-tree/frontend/src/settingspage.md +124 -0
- package/.brv/context-tree/frontend/src/settingspage.overview.md +34 -0
- package/.brv/context-tree/frontend/src/sidebar.abstract.md +0 -0
- package/.brv/context-tree/frontend/src/sidebar.md +170 -0
- package/.brv/context-tree/frontend/src/sidebar.overview.md +25 -0
- package/.brv/context-tree/meta/_index.md +24 -0
- package/.brv/context-tree/meta/curation_context/_index.md +24 -0
- package/.brv/context-tree/meta/curation_context/empty_context.abstract.md +4 -0
- package/.brv/context-tree/meta/curation_context/empty_context.md +35 -0
- package/.brv/context-tree/meta/curation_context/empty_context.overview.md +20 -0
- package/.brv/dream-log/drm-1777341062653.json +33 -0
- package/.brv/dream-state.json +8 -0
- package/.brv/dream.lock +0 -0
- package/.brv/review-backups/docs/api/hermes_api_openapi_specification.md +468 -0
- package/.claude/settings.local.json +7 -0
- package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/app.json +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/appearance.json +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/core-plugins.json +33 -0
- package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/graph.json +22 -0
- package/.claude/worktrees/phase-2-mcp/.brv/.obsidian/workspace.json +195 -0
- package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo 1.canvas" +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo 2.canvas" +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/Sin t/303/255tulo.canvas" +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/_queue_status.json +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/config.json +5 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/_index.md +60 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/_manifest.json +165 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/_index.md +24 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/_index.md +40 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.md +27 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/init.overview.md +29 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.abstract.md +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.md +273 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/job_tracker.overview.md +31 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.md +1292 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/main.overview.md +30 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.abstract.md +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.md +37 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/backend/backend/requirements.overview.md +28 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/_index.md +37 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/_index.md +54 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/context.md +11 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.md +468 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/docs/api/hermes_api_openapi_specification.overview.md +44 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/_index.md +48 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/_index.md +31 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.md +41 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/hermes_dashboard/architecture_overview.overview.md +34 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/_index.md +53 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/_index.md +52 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.md +161 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/components/sidebar_navigation_component.overview.md +32 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/context.md +10 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.md +34 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/functioncallingpage.overview.md +26 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/_index.md +48 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.abstract.md +1 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.md +403 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/lib/api_client_library.overview.md +69 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.md +103 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/page.overview.md +7 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.md +124 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/settingspage.overview.md +34 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.abstract.md +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.md +170 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/frontend/src/sidebar.overview.md +25 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/_index.md +24 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/_index.md +24 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.abstract.md +4 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.md +35 -0
- package/.claude/worktrees/phase-2-mcp/.brv/context-tree/meta/curation_context/empty_context.overview.md +20 -0
- package/.claude/worktrees/phase-2-mcp/.brv/dream-log/drm-1777341062653.json +33 -0
- package/.claude/worktrees/phase-2-mcp/.brv/dream-state.json +8 -0
- package/.claude/worktrees/phase-2-mcp/.brv/dream.lock +0 -0
- package/.claude/worktrees/phase-2-mcp/.brv/review-backups/docs/api/hermes_api_openapi_specification.md +468 -0
- package/.claude/worktrees/phase-2-mcp/.claude/settings.local.json +13 -0
- package/.claude/worktrees/phase-2-mcp/.kilocode/package-lock.json +378 -0
- package/.claude/worktrees/phase-2-mcp/.kilocode/package.json +5 -0
- package/.claude/worktrees/phase-2-mcp/AGENTS.md +5 -0
- package/.claude/worktrees/phase-2-mcp/CLAUDE.md +29 -0
- package/.claude/worktrees/phase-2-mcp/QA_AUDIT_PLAN.md +156 -0
- package/.claude/worktrees/phase-2-mcp/README.md +316 -0
- package/.claude/worktrees/phase-2-mcp/agent-agnostic-evolution-dashboard.md +405 -0
- package/.claude/worktrees/phase-2-mcp/backend/__init__.py +0 -0
- package/.claude/worktrees/phase-2-mcp/backend/collectors/__init__.py +0 -0
- package/.claude/worktrees/phase-2-mcp/backend/collectors/claude_code_collector.py +277 -0
- package/.claude/worktrees/phase-2-mcp/backend/collectors/hermes_collector.py +68 -0
- package/.claude/worktrees/phase-2-mcp/backend/curator.py +512 -0
- package/.claude/worktrees/phase-2-mcp/backend/eval/__init__.py +19 -0
- package/.claude/worktrees/phase-2-mcp/backend/eval/engine.py +116 -0
- package/.claude/worktrees/phase-2-mcp/backend/eval/scorers.py +201 -0
- package/.claude/worktrees/phase-2-mcp/backend/generate_dataset.py +86 -0
- package/.claude/worktrees/phase-2-mcp/backend/job_tracker.py +232 -0
- package/.claude/worktrees/phase-2-mcp/backend/main.py +1746 -0
- package/.claude/worktrees/phase-2-mcp/backend/mcp_server.py +250 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/__init__.py +24 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/cycle_orchestrator.py +270 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/delta_validator.py +191 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/dspy_compiler.py +315 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/gepa_strategist.py +213 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/models.py +260 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/skill_deployer.py +195 -0
- package/.claude/worktrees/phase-2-mcp/backend/promethean/trace_ingestion.py +142 -0
- package/.claude/worktrees/phase-2-mcp/backend/requirements.txt +6 -0
- package/.claude/worktrees/phase-2-mcp/backend/sdd_evolve.py +459 -0
- package/.claude/worktrees/phase-2-mcp/backend/skill_detector.py +227 -0
- package/.claude/worktrees/phase-2-mcp/backend/skill_registry.py +289 -0
- package/.claude/worktrees/phase-2-mcp/backend/storage/__init__.py +5 -0
- package/.claude/worktrees/phase-2-mcp/backend/storage/run_store.py +393 -0
- package/.claude/worktrees/phase-2-mcp/backend/storage/schema.sql +99 -0
- package/.claude/worktrees/phase-2-mcp/backend/validate_evolution.py +267 -0
- package/.claude/worktrees/phase-2-mcp/components.json +28 -0
- package/.claude/worktrees/phase-2-mcp/docs/api/hermes-api.openapi.yaml +438 -0
- package/.claude/worktrees/phase-2-mcp/docs/hero.svg +148 -0
- package/.claude/worktrees/phase-2-mcp/eslint.config.mjs +18 -0
- package/.claude/worktrees/phase-2-mcp/install.sh +245 -0
- package/.claude/worktrees/phase-2-mcp/next-env.d.ts +6 -0
- package/.claude/worktrees/phase-2-mcp/next.config.ts +32 -0
- package/.claude/worktrees/phase-2-mcp/package-lock.json +11936 -0
- package/.claude/worktrees/phase-2-mcp/package.json +41 -0
- package/.claude/worktrees/phase-2-mcp/pnpm-workspace.yaml +4 -0
- package/.claude/worktrees/phase-2-mcp/postcss.config.mjs +7 -0
- package/.claude/worktrees/phase-2-mcp/public/file.svg +1 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Bold.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Heavy.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Medium.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Regular.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Display-Semibold.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Bold.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Heavy.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Medium.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Regular.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/fonts/SF-Pro-Text-Semibold.otf +0 -0
- package/.claude/worktrees/phase-2-mcp/public/globe.svg +1 -0
- package/.claude/worktrees/phase-2-mcp/public/next.svg +1 -0
- package/.claude/worktrees/phase-2-mcp/public/theme-preview.html +257 -0
- package/.claude/worktrees/phase-2-mcp/public/vercel.svg +1 -0
- package/.claude/worktrees/phase-2-mcp/public/window.svg +1 -0
- package/.claude/worktrees/phase-2-mcp/run.sh +26 -0
- package/.claude/worktrees/phase-2-mcp/skills-lock.json +10 -0
- package/.claude/worktrees/phase-2-mcp/specs/event-schema.md +223 -0
- package/.claude/worktrees/phase-2-mcp/specs/examples/run.jsonl +3 -0
- package/.claude/worktrees/phase-2-mcp/src/app/api/[...path]/route.ts +55 -0
- package/.claude/worktrees/phase-2-mcp/src/app/api/auth/token/route.ts +22 -0
- package/.claude/worktrees/phase-2-mcp/src/app/evolution/page.tsx +589 -0
- package/.claude/worktrees/phase-2-mcp/src/app/favicon.ico +0 -0
- package/.claude/worktrees/phase-2-mcp/src/app/globals.css +321 -0
- package/.claude/worktrees/phase-2-mcp/src/app/layout.tsx +63 -0
- package/.claude/worktrees/phase-2-mcp/src/app/page.tsx +70 -0
- package/.claude/worktrees/phase-2-mcp/src/app/skills/page.tsx +369 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ApiConfigCard.tsx +199 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.css +1 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.d.ts +1 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ColorBends.jsx +1 -0
- package/.claude/worktrees/phase-2-mcp/src/components/CoreLoopToggle.tsx +111 -0
- package/.claude/worktrees/phase-2-mcp/src/components/EnvironmentStatus.tsx +176 -0
- package/.claude/worktrees/phase-2-mcp/src/components/EvolutionBackground.tsx +1 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ReactQueryProvider.tsx +24 -0
- package/.claude/worktrees/phase-2-mcp/src/components/Sidebar.tsx +247 -0
- package/.claude/worktrees/phase-2-mcp/src/components/SkillDiffViewer.tsx +154 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ThemeAwareBackground.tsx +67 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ThemeToggle.tsx +54 -0
- package/.claude/worktrees/phase-2-mcp/src/components/WelcomeHero.tsx +77 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/ClickSpark.tsx +116 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/CountUp.tsx +98 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/DarkSelect.tsx +95 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/DecryptedText.tsx +161 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/ElectricBorder.tsx +184 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/GlitchText.tsx +34 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/ShinyText.tsx +55 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/SpotlightCard.tsx +42 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/TextType.tsx +95 -0
- package/.claude/worktrees/phase-2-mcp/src/components/bits/index.ts +9 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/CuratorPage.tsx +632 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/DatasetPage.tsx +271 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/EvolutionPage.tsx +676 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/FunctionCallingPage.tsx +1 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/LogsPage.tsx +272 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/MetricsPage.tsx +246 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/OverviewPage.tsx +420 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/SettingsPage.tsx +88 -0
- package/.claude/worktrees/phase-2-mcp/src/components/pages/SkillStudioPage.tsx +376 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/animated-theme-toggler.tsx +97 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/button.tsx +67 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/card.tsx +103 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/input.tsx +19 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/separator.tsx +28 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/sheet.tsx +147 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/sidebar.tsx +702 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/skeleton.tsx +13 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/theme-toggle.tsx +272 -0
- package/.claude/worktrees/phase-2-mcp/src/components/ui/tooltip.tsx +57 -0
- package/.claude/worktrees/phase-2-mcp/src/hooks/use-mobile.ts +19 -0
- package/.claude/worktrees/phase-2-mcp/src/lib/api.ts +455 -0
- package/.claude/worktrees/phase-2-mcp/src/lib/queryClient.ts +12 -0
- package/.claude/worktrees/phase-2-mcp/src/lib/utils.ts +6 -0
- package/.claude/worktrees/phase-2-mcp/stitch/agent_dashboard/DESIGN_SPEC.md +521 -0
- package/.claude/worktrees/phase-2-mcp/stitch/agent_dashboard/prototype.html +676 -0
- package/.claude/worktrees/phase-2-mcp/stitch/curator_workspace/code.html +448 -0
- package/.claude/worktrees/phase-2-mcp/stitch/curator_workspace/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/datasets/code.html +479 -0
- package/.claude/worktrees/phase-2-mcp/stitch/datasets/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/evolution_history/code.html +461 -0
- package/.claude/worktrees/phase-2-mcp/stitch/evolution_history/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/hermes_dashboard/DESIGN.md +192 -0
- package/.claude/worktrees/phase-2-mcp/stitch/hermes_dashboard/DESIGN_SPEC.md +455 -0
- package/.claude/worktrees/phase-2-mcp/stitch/hermes_overview/code.html +399 -0
- package/.claude/worktrees/phase-2-mcp/stitch/hermes_overview/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/live_logs/code.html +324 -0
- package/.claude/worktrees/phase-2-mcp/stitch/live_logs/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/skill_hub/code.html +596 -0
- package/.claude/worktrees/phase-2-mcp/stitch/skill_hub/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/system_metrics/code.html +527 -0
- package/.claude/worktrees/phase-2-mcp/stitch/system_metrics/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/stitch/system_settings/code.html +257 -0
- package/.claude/worktrees/phase-2-mcp/stitch/system_settings/screen.png +0 -0
- package/.claude/worktrees/phase-2-mcp/test_dashboard.py +201 -0
- package/.claude/worktrees/phase-2-mcp/tests/collectors/__init__.py +0 -0
- package/.claude/worktrees/phase-2-mcp/tests/collectors/fixtures/sample_session.jsonl +7 -0
- package/.claude/worktrees/phase-2-mcp/tests/collectors/test_claude_code_collector.py +171 -0
- package/.claude/worktrees/phase-2-mcp/tests/collectors/test_hermes_collector.py +167 -0
- package/.claude/worktrees/phase-2-mcp/tests/eval/test_engine.py +234 -0
- package/.claude/worktrees/phase-2-mcp/tests/eval/test_scorers.py +249 -0
- package/.claude/worktrees/phase-2-mcp/tests/storage/__init__.py +0 -0
- package/.claude/worktrees/phase-2-mcp/tests/storage/test_run_store.py +359 -0
- package/.claude/worktrees/phase-2-mcp/tests/test_curator.py +559 -0
- package/.claude/worktrees/phase-2-mcp/tests/test_mcp_server.py +114 -0
- package/.claude/worktrees/phase-2-mcp/tsconfig.json +34 -0
- package/.env.example +72 -0
- package/.kilocode/package-lock.json +378 -0
- package/.kilocode/package.json +5 -0
- package/AGENTS.md +5 -0
- package/CLAUDE.md +29 -0
- package/QA_AUDIT_PLAN.md +156 -0
- package/README.md +355 -0
- package/agent-agnostic-evolution-dashboard.md +405 -0
- package/backend/__init__.py +0 -0
- package/backend/collectors/__init__.py +0 -0
- package/backend/collectors/claude_code_collector.py +277 -0
- package/backend/collectors/hermes_collector.py +68 -0
- package/backend/curator.py +512 -0
- package/backend/eval/__init__.py +19 -0
- package/backend/eval/engine.py +116 -0
- package/backend/eval/scorers.py +201 -0
- package/backend/generate_dataset.py +86 -0
- package/backend/job_tracker.py +232 -0
- package/backend/main.py +1746 -0
- package/backend/mcp_server.py +250 -0
- package/backend/promethean/__init__.py +24 -0
- package/backend/promethean/cycle_orchestrator.py +270 -0
- package/backend/promethean/delta_validator.py +191 -0
- package/backend/promethean/dspy_compiler.py +315 -0
- package/backend/promethean/gepa_strategist.py +213 -0
- package/backend/promethean/models.py +260 -0
- package/backend/promethean/skill_deployer.py +195 -0
- package/backend/promethean/trace_ingestion.py +142 -0
- package/backend/requirements.txt +6 -0
- package/backend/sdd_evolve.py +459 -0
- package/backend/skill_detector.py +227 -0
- package/backend/skill_registry.py +289 -0
- package/backend/storage/__init__.py +5 -0
- package/backend/storage/run_store.py +393 -0
- package/backend/storage/schema.sql +99 -0
- package/backend/validate_evolution.py +267 -0
- package/bin/genoma.js +250 -0
- package/components.json +28 -0
- package/docs/api/hermes-api.openapi.yaml +438 -0
- package/docs/hero.svg +148 -0
- package/eslint.config.mjs +18 -0
- package/install.sh +245 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +32 -0
- package/package.json +46 -0
- package/pnpm-workspace.yaml +4 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/fonts/SF-Pro-Display-Bold.otf +0 -0
- package/public/fonts/SF-Pro-Display-Heavy.otf +0 -0
- package/public/fonts/SF-Pro-Display-Medium.otf +0 -0
- package/public/fonts/SF-Pro-Display-Regular.otf +0 -0
- package/public/fonts/SF-Pro-Display-Semibold.otf +0 -0
- package/public/fonts/SF-Pro-Text-Bold.otf +0 -0
- package/public/fonts/SF-Pro-Text-Heavy.otf +0 -0
- package/public/fonts/SF-Pro-Text-Medium.otf +0 -0
- package/public/fonts/SF-Pro-Text-Regular.otf +0 -0
- package/public/fonts/SF-Pro-Text-Semibold.otf +0 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/theme-preview.html +257 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/run.sh +26 -0
- package/scripts/postinstall.js +50 -0
- package/skills-lock.json +10 -0
- package/specs/event-schema.md +223 -0
- package/specs/examples/run.jsonl +3 -0
- package/src/app/api/[...path]/route.ts +55 -0
- package/src/app/api/auth/token/route.ts +22 -0
- package/src/app/evolution/page.tsx +589 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +321 -0
- package/src/app/layout.tsx +63 -0
- package/src/app/page.tsx +70 -0
- package/src/app/skills/page.tsx +369 -0
- package/src/components/ApiConfigCard.tsx +199 -0
- package/src/components/ColorBends.css +1 -0
- package/src/components/ColorBends.d.ts +1 -0
- package/src/components/ColorBends.jsx +1 -0
- package/src/components/CoreLoopToggle.tsx +111 -0
- package/src/components/EnvironmentStatus.tsx +176 -0
- package/src/components/EvolutionBackground.tsx +1 -0
- package/src/components/ReactQueryProvider.tsx +24 -0
- package/src/components/Sidebar.tsx +247 -0
- package/src/components/SkillDiffViewer.tsx +154 -0
- package/src/components/ThemeAwareBackground.tsx +67 -0
- package/src/components/ThemeToggle.tsx +54 -0
- package/src/components/WelcomeHero.tsx +77 -0
- package/src/components/bits/ClickSpark.tsx +116 -0
- package/src/components/bits/CountUp.tsx +98 -0
- package/src/components/bits/DarkSelect.tsx +95 -0
- package/src/components/bits/DecryptedText.tsx +161 -0
- package/src/components/bits/ElectricBorder.tsx +184 -0
- package/src/components/bits/GlitchText.tsx +34 -0
- package/src/components/bits/ShinyText.tsx +55 -0
- package/src/components/bits/SpotlightCard.tsx +42 -0
- package/src/components/bits/TextType.tsx +95 -0
- package/src/components/bits/index.ts +9 -0
- package/src/components/pages/CuratorPage.tsx +632 -0
- package/src/components/pages/DatasetPage.tsx +271 -0
- package/src/components/pages/EvolutionPage.tsx +676 -0
- package/src/components/pages/FunctionCallingPage.tsx +1 -0
- package/src/components/pages/LogsPage.tsx +272 -0
- package/src/components/pages/MetricsPage.tsx +246 -0
- package/src/components/pages/OverviewPage.tsx +420 -0
- package/src/components/pages/SettingsPage.tsx +88 -0
- package/src/components/pages/SkillStudioPage.tsx +376 -0
- package/src/components/ui/animated-theme-toggler.tsx +97 -0
- package/src/components/ui/button.tsx +67 -0
- package/src/components/ui/card.tsx +103 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +702 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/theme-toggle.tsx +272 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/api.ts +455 -0
- package/src/lib/queryClient.ts +12 -0
- package/src/lib/utils.ts +6 -0
- package/stitch/agent_dashboard/DESIGN_SPEC.md +521 -0
- package/stitch/agent_dashboard/prototype.html +676 -0
- package/stitch/curator_workspace/code.html +448 -0
- package/stitch/curator_workspace/screen.png +0 -0
- package/stitch/datasets/code.html +479 -0
- package/stitch/datasets/screen.png +0 -0
- package/stitch/evolution_history/code.html +461 -0
- package/stitch/evolution_history/screen.png +0 -0
- package/stitch/hermes_dashboard/DESIGN.md +192 -0
- package/stitch/hermes_dashboard/DESIGN_SPEC.md +455 -0
- package/stitch/hermes_overview/code.html +399 -0
- package/stitch/hermes_overview/screen.png +0 -0
- package/stitch/live_logs/code.html +324 -0
- package/stitch/live_logs/screen.png +0 -0
- package/stitch/skill_hub/code.html +596 -0
- package/stitch/skill_hub/screen.png +0 -0
- package/stitch/system_metrics/code.html +527 -0
- package/stitch/system_metrics/screen.png +0 -0
- package/stitch/system_settings/code.html +257 -0
- package/stitch/system_settings/screen.png +0 -0
- package/test_dashboard.py +201 -0
- package/tests/collectors/__init__.py +0 -0
- package/tests/collectors/fixtures/sample_session.jsonl +7 -0
- package/tests/collectors/test_claude_code_collector.py +171 -0
- package/tests/collectors/test_hermes_collector.py +167 -0
- package/tests/eval/test_engine.py +234 -0
- package/tests/eval/test_scorers.py +249 -0
- package/tests/storage/__init__.py +0 -0
- package/tests/storage/test_run_store.py +359 -0
- package/tests/test_curator.py +559 -0
- package/tests/test_e2e_npm.py +621 -0
- package/tests/test_mcp_server.py +114 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
②③ DIAGNOSTICA + FORMULA — GEPA Strategic Layer
|
|
3
|
+
|
|
4
|
+
GEPA (Goal → Execution → Plan → Action) analyzes anomalies detected by trace ingestion
|
|
5
|
+
and formulates SkillGenesis packets for DSPy compilation.
|
|
6
|
+
|
|
7
|
+
This is the "brain" of the Promethean Cycle — it decides WHAT to learn.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .models import SkillGenesisPacket, CyclePhase, CycleState
|
|
18
|
+
from .trace_ingestion import get_ingestor
|
|
19
|
+
|
|
20
|
+
SKILLS_DIR = Path.home() / ".hermes" / "skills"
|
|
21
|
+
MEMORY_DIR = Path.home() / ".hermes" / "memory"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GEPAStrategist:
|
|
25
|
+
"""Strategic layer: diagnoses skill gaps and formulates learning objectives."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.ingestor = get_ingestor()
|
|
29
|
+
|
|
30
|
+
# ── Diagnosis ──────────────────────────────────────────────────
|
|
31
|
+
def diagnose(self, anomalies: list[dict]) -> list[dict]:
|
|
32
|
+
"""Analyze anomalies and determine if they represent skill gaps.
|
|
33
|
+
|
|
34
|
+
Returns list of gap diagnoses with:
|
|
35
|
+
- gap_id: unique identifier
|
|
36
|
+
- root_cause: identified root cause
|
|
37
|
+
- existing_skills: skills that partially cover this (if any)
|
|
38
|
+
- confidence: 0-1 probability that a new skill would help
|
|
39
|
+
- recommended_action: "compile_skill" | "monitor" | "ignore"
|
|
40
|
+
"""
|
|
41
|
+
gaps = []
|
|
42
|
+
|
|
43
|
+
for anomaly in anomalies:
|
|
44
|
+
sig = anomaly["error_signature"]
|
|
45
|
+
occurrences = anomaly["occurrences"]
|
|
46
|
+
agents = anomaly.get("agents_affected", [])
|
|
47
|
+
|
|
48
|
+
# Check if any existing skill covers this
|
|
49
|
+
existing = self._find_covering_skills(sig)
|
|
50
|
+
|
|
51
|
+
# Determine root cause from error pattern
|
|
52
|
+
root_cause = self._infer_root_cause(sig, anomaly.get("sample_traces", []))
|
|
53
|
+
|
|
54
|
+
# Calculate confidence based on:
|
|
55
|
+
# - Number of occurrences (more = more confident)
|
|
56
|
+
# - Number of agents affected (cross-agent = more confident)
|
|
57
|
+
# - Whether existing skills partially cover it
|
|
58
|
+
occ_factor = min(occurrences / 10, 1.0) # Capped at 10 occurrences
|
|
59
|
+
agent_factor = min(len(agents) / 4, 1.0) if agents else 0.3
|
|
60
|
+
coverage_factor = 0.0 if existing else 0.5 # No coverage = higher need
|
|
61
|
+
|
|
62
|
+
confidence = round((occ_factor * 0.4 + agent_factor * 0.3 + coverage_factor * 0.3), 2)
|
|
63
|
+
|
|
64
|
+
# Decide action
|
|
65
|
+
if confidence > 0.5 and not existing:
|
|
66
|
+
action = "compile_skill"
|
|
67
|
+
elif confidence > 0.3:
|
|
68
|
+
action = "monitor"
|
|
69
|
+
else:
|
|
70
|
+
action = "ignore"
|
|
71
|
+
|
|
72
|
+
gaps.append({
|
|
73
|
+
"gap_id": f"gap_{sig[:20].replace(' ', '_')}",
|
|
74
|
+
"error_signature": sig,
|
|
75
|
+
"root_cause": root_cause,
|
|
76
|
+
"occurrences": occurrences,
|
|
77
|
+
"agents_affected": agents,
|
|
78
|
+
"existing_skills": existing,
|
|
79
|
+
"confidence": confidence,
|
|
80
|
+
"recommended_action": action,
|
|
81
|
+
"diagnosed_at": datetime.now().isoformat(),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return gaps
|
|
85
|
+
|
|
86
|
+
# ── Formulation ─────────────────────────────────────────────────
|
|
87
|
+
def formulate(self, gap: dict, cycle_state: CycleState) -> Optional[SkillGenesisPacket]:
|
|
88
|
+
"""Formulate a SkillGenesis packet from a confirmed gap.
|
|
89
|
+
|
|
90
|
+
Only creates packets for gaps with recommended_action == 'compile_skill'.
|
|
91
|
+
"""
|
|
92
|
+
if gap["recommended_action"] != "compile_skill":
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
sig = gap["error_signature"]
|
|
96
|
+
root_cause = gap["root_cause"]
|
|
97
|
+
|
|
98
|
+
# Extract dataset from trace storage
|
|
99
|
+
dataset_path = self.ingestor.extract_dataset(sig, limit=50)
|
|
100
|
+
|
|
101
|
+
# Infer the DSPy signature from the error pattern
|
|
102
|
+
signature = self._infer_signature(sig, root_cause)
|
|
103
|
+
|
|
104
|
+
# Generate intent description
|
|
105
|
+
intent = f"Autonomous resolution for: {root_cause}. Detected from {gap['occurrences']} failures across {len(gap['agents_affected'])} agents."
|
|
106
|
+
|
|
107
|
+
# Select metric based on error type
|
|
108
|
+
metric = self._select_metric(sig)
|
|
109
|
+
|
|
110
|
+
# Calculate dynamic threshold based on confidence
|
|
111
|
+
threshold = max(0.10, gap["confidence"] * 0.30)
|
|
112
|
+
|
|
113
|
+
packet = SkillGenesisPacket(
|
|
114
|
+
intent=intent,
|
|
115
|
+
signature=signature,
|
|
116
|
+
dataset_path=dataset_path,
|
|
117
|
+
metric=metric,
|
|
118
|
+
threshold=threshold,
|
|
119
|
+
target_agent=",".join(gap["agents_affected"]) if gap["agents_affected"] else "all",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
cycle_state.genesis_packets.append(packet)
|
|
123
|
+
return packet
|
|
124
|
+
|
|
125
|
+
def formulate_all(self, gaps: list[dict], cycle_state: CycleState) -> list[SkillGenesisPacket]:
|
|
126
|
+
"""Formulate packets for all actionable gaps."""
|
|
127
|
+
packets = []
|
|
128
|
+
for gap in gaps:
|
|
129
|
+
packet = self.formulate(gap, cycle_state)
|
|
130
|
+
if packet:
|
|
131
|
+
packets.append(packet)
|
|
132
|
+
return packets
|
|
133
|
+
|
|
134
|
+
# ── Internal: Skill Discovery ───────────────────────────────────
|
|
135
|
+
def _find_covering_skills(self, error_signature: str) -> list[str]:
|
|
136
|
+
"""Find existing skills that might cover this error pattern."""
|
|
137
|
+
if not SKILLS_DIR.exists():
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
keywords = set(re.findall(r'[a-z]+', error_signature.lower()))
|
|
141
|
+
covering = []
|
|
142
|
+
|
|
143
|
+
for skill_dir in SKILLS_DIR.rglob("SKILL.md"):
|
|
144
|
+
try:
|
|
145
|
+
content = skill_dir.read_text()[:500].lower()
|
|
146
|
+
matches = sum(1 for kw in keywords if kw in content)
|
|
147
|
+
if matches >= 2:
|
|
148
|
+
covering.append(skill_dir.parent.name)
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
return covering
|
|
153
|
+
|
|
154
|
+
# ── Internal: Root Cause Inference ──────────────────────────────
|
|
155
|
+
def _infer_root_cause(self, error_signature: str, sample_traces: list[str]) -> str:
|
|
156
|
+
"""Infer root cause from error signature and sample traces."""
|
|
157
|
+
patterns = {
|
|
158
|
+
r"timeout|timed?.*out": "Operation timeout — likely resource or network constraint",
|
|
159
|
+
r"auth|unauthorized|forbidden|401|403": "Authentication or authorization failure",
|
|
160
|
+
r"migrat|schema|drizzle.*error": "Database migration or schema mismatch",
|
|
161
|
+
r"build.*fail|compil|syntax|type.*error": "Build or compilation failure",
|
|
162
|
+
r"import.*error|module.*not.*found|no.*module": "Missing dependency or import path error",
|
|
163
|
+
r"deploy|eas|expo.*build": "Deployment pipeline failure",
|
|
164
|
+
r"docker|container|sandbox|e2b": "Sandbox or container orchestration failure",
|
|
165
|
+
r"network|dns|refused|unreachable|econnrefused": "Network connectivity failure",
|
|
166
|
+
r"memory|oom|killed|heap": "Memory exhaustion",
|
|
167
|
+
r"permission|denied|eacces": "File system permission error",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for pattern, cause in patterns.items():
|
|
171
|
+
if re.search(pattern, error_signature, re.IGNORECASE):
|
|
172
|
+
return cause
|
|
173
|
+
|
|
174
|
+
return f"Uncategorized operational failure: {error_signature[:80]}"
|
|
175
|
+
|
|
176
|
+
# ── Internal: Signature Inference ───────────────────────────────
|
|
177
|
+
def _infer_signature(self, error_signature: str, root_cause: str) -> str:
|
|
178
|
+
"""Infer a DSPy Signature string from error context."""
|
|
179
|
+
if re.search(r"build|deploy|eas|dockerfile", error_signature, re.IGNORECASE):
|
|
180
|
+
return "error_log, build_context → fixed_config, explanation"
|
|
181
|
+
elif re.search(r"migrat|schema|drizzle", error_signature, re.IGNORECASE):
|
|
182
|
+
return "migration_error, schema_snapshot → fix_commands, root_cause"
|
|
183
|
+
elif re.search(r"auth|unauthorized|forbidden", error_signature, re.IGNORECASE):
|
|
184
|
+
return "auth_error, config_snapshot → fix_steps, updated_config"
|
|
185
|
+
elif re.search(r"import|module.*not.*found", error_signature, re.IGNORECASE):
|
|
186
|
+
return "import_error, package_json → fix_commands, dependency_name"
|
|
187
|
+
elif re.search(r"network|dns|refused|timeout", error_signature, re.IGNORECASE):
|
|
188
|
+
return "network_error, service_context → recovery_steps, root_cause"
|
|
189
|
+
else:
|
|
190
|
+
return "error_log, context → fix_steps, explanation"
|
|
191
|
+
|
|
192
|
+
# ── Internal: Metric Selection ──────────────────────────────────
|
|
193
|
+
def _select_metric(self, error_signature: str) -> str:
|
|
194
|
+
"""Select the most appropriate metric for this type of error."""
|
|
195
|
+
if re.search(r"timeout|slow|performance", error_signature, re.IGNORECASE):
|
|
196
|
+
return "delta_resolution_time"
|
|
197
|
+
elif re.search(r"build|deploy|eas", error_signature, re.IGNORECASE):
|
|
198
|
+
return "delta_build_success_rate"
|
|
199
|
+
elif re.search(r"migrat|schema", error_signature, re.IGNORECASE):
|
|
200
|
+
return "delta_migration_success_rate"
|
|
201
|
+
else:
|
|
202
|
+
return "delta_resolution_rate"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ── Singleton ───────────────────────────────────────────────────────
|
|
206
|
+
_strategist: Optional[GEPAStrategist] = None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_strategist() -> GEPAStrategist:
|
|
210
|
+
global _strategist
|
|
211
|
+
if _strategist is None:
|
|
212
|
+
_strategist = GEPAStrategist()
|
|
213
|
+
return _strategist
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Promethean Models — Data structures for the Ciclo Prometeico.
|
|
3
|
+
|
|
4
|
+
SkillGenesis Packet: The interface between GEPA (strategy) and DSPy (compilation).
|
|
5
|
+
TraceRecord: Standardized trace from any AI agent.
|
|
6
|
+
CycleState: Full state machine for the 7-phase cycle.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from dataclasses import dataclass, field, asdict
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Optional, Any
|
|
14
|
+
import json
|
|
15
|
+
import uuid
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ── Phase Enum ──────────────────────────────────────────────────────
|
|
19
|
+
class CyclePhase(str, Enum):
|
|
20
|
+
PERCIBE = "perceive"
|
|
21
|
+
DIAGNOSTICA = "diagnose"
|
|
22
|
+
FORMULA = "formulate"
|
|
23
|
+
COMPILA = "compile"
|
|
24
|
+
VALIDA = "validate"
|
|
25
|
+
DESPLIEGA = "deploy"
|
|
26
|
+
OBSERVA = "observe"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── Trace Record (any agent → dashboard) ───────────────────────────
|
|
30
|
+
@dataclass
|
|
31
|
+
class TraceRecord:
|
|
32
|
+
"""Standardized trace emitted by any AI agent."""
|
|
33
|
+
agent: str # "claude-code", "opencode", "codex", "hermes"
|
|
34
|
+
agent_version: str
|
|
35
|
+
timestamp: str # ISO 8601
|
|
36
|
+
task: str # Description of what was attempted
|
|
37
|
+
outcome: str # "success" | "failure" | "partial"
|
|
38
|
+
error_signature: Optional[str] = None # Unique error fingerprint
|
|
39
|
+
context: dict = field(default_factory=dict) # files_touched, commands, stack_trace
|
|
40
|
+
resolution: Optional[str] = None # How it was resolved (null if unresolved → gap)
|
|
41
|
+
trace_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
|
42
|
+
|
|
43
|
+
def to_json(self) -> str:
|
|
44
|
+
return json.dumps(asdict(self), indent=2, default=str)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_json(cls, data: str | dict) -> "TraceRecord":
|
|
48
|
+
if isinstance(data, str):
|
|
49
|
+
data = json.loads(data)
|
|
50
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
51
|
+
|
|
52
|
+
def to_canonical(self) -> CanonicalRun:
|
|
53
|
+
"""Convert TraceRecord to vendor-neutral CanonicalRun schema."""
|
|
54
|
+
errors = []
|
|
55
|
+
if self.error_signature:
|
|
56
|
+
errors = [{"signature": self.error_signature, "message": None, "stack_excerpt": None, "count": 1}]
|
|
57
|
+
|
|
58
|
+
return CanonicalRun(
|
|
59
|
+
run_id=self.trace_id,
|
|
60
|
+
agent_name=self.agent,
|
|
61
|
+
agent_version=self.agent_version,
|
|
62
|
+
collector="hermes-trace-ingestor",
|
|
63
|
+
collector_version="0.1.0",
|
|
64
|
+
started_at=self.timestamp,
|
|
65
|
+
ended_at=self.timestamp,
|
|
66
|
+
task_name=self.task,
|
|
67
|
+
outcome=self.outcome,
|
|
68
|
+
provider="hermes",
|
|
69
|
+
errors=errors,
|
|
70
|
+
context=self.context or {},
|
|
71
|
+
resolution=self.resolution,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ── SkillGenesis Packet (GEPA → DSPy) ──────────────────────────────
|
|
76
|
+
@dataclass
|
|
77
|
+
class SkillGenesisPacket:
|
|
78
|
+
"""GEPA formulates this. DSPy consumes it. A skill is born."""
|
|
79
|
+
intent: str # What the skill should do (human-readable)
|
|
80
|
+
signature: str # "input_fields → output_fields" (DSPy Signature format)
|
|
81
|
+
dataset_path: str # Path to training dataset (JSONL)
|
|
82
|
+
metric: str # Metric name for optimization
|
|
83
|
+
threshold: float = 0.15 # Minimum delta to accept (e.g., 0.15 = 15% improvement)
|
|
84
|
+
holdout: float = 0.2 # Fraction of data reserved for validation
|
|
85
|
+
optimizers: list[str] = field(default_factory=lambda: ["GEPA", "BootstrapFinetune"])
|
|
86
|
+
strategy: str = "p -> w -> p" # BetterTogether strategy string
|
|
87
|
+
target_agent: str = "all" # Which agent(s) this skill targets
|
|
88
|
+
max_attempts: int = 3 # Max re-compilation attempts if validation fails
|
|
89
|
+
packet_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
|
90
|
+
|
|
91
|
+
def to_dict(self) -> dict:
|
|
92
|
+
return asdict(self)
|
|
93
|
+
|
|
94
|
+
def to_json(self) -> str:
|
|
95
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_dict(cls, data: dict) -> "SkillGenesisPacket":
|
|
99
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── Compilation Result (DSPy → GEPA) ───────────────────────────────
|
|
103
|
+
@dataclass
|
|
104
|
+
class CompilationResult:
|
|
105
|
+
"""DSPy returns this after compiling a skill."""
|
|
106
|
+
packet_id: str
|
|
107
|
+
success: bool
|
|
108
|
+
skill_name: str # Generated skill name
|
|
109
|
+
skill_path: str # Where the skill was saved
|
|
110
|
+
iterations: int # Number of optimization iterations
|
|
111
|
+
best_score: float # Best metric score achieved
|
|
112
|
+
delta: float # Improvement over baseline
|
|
113
|
+
threshold_met: bool # delta >= threshold?
|
|
114
|
+
attempt: int = 1 # Which attempt (1 to max_attempts)
|
|
115
|
+
error: Optional[str] = None # Error message if failed
|
|
116
|
+
compiled_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ── Cycle State ─────────────────────────────────────────────────────
|
|
120
|
+
@dataclass
|
|
121
|
+
class CycleState:
|
|
122
|
+
"""Full state of a Promethean Cycle execution."""
|
|
123
|
+
cycle_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
|
124
|
+
phase: CyclePhase = CyclePhase.PERCIBE
|
|
125
|
+
traces_ingested: int = 0
|
|
126
|
+
anomalies_detected: int = 0
|
|
127
|
+
genesis_packets: list[SkillGenesisPacket] = field(default_factory=list)
|
|
128
|
+
compilation_results: list[CompilationResult] = field(default_factory=list)
|
|
129
|
+
skills_deployed: int = 0
|
|
130
|
+
started_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
131
|
+
completed_at: Optional[str] = None
|
|
132
|
+
errors: list[str] = field(default_factory=list)
|
|
133
|
+
|
|
134
|
+
def advance(self, new_phase: CyclePhase):
|
|
135
|
+
self.phase = new_phase
|
|
136
|
+
if new_phase == CyclePhase.OBSERVA:
|
|
137
|
+
self.completed_at = datetime.now().isoformat()
|
|
138
|
+
|
|
139
|
+
def summary(self) -> dict:
|
|
140
|
+
return {
|
|
141
|
+
"cycle_id": self.cycle_id,
|
|
142
|
+
"phase": self.phase.value,
|
|
143
|
+
"traces_ingested": self.traces_ingested,
|
|
144
|
+
"anomalies_detected": self.anomalies_detected,
|
|
145
|
+
"genesis_packets": len(self.genesis_packets),
|
|
146
|
+
"skills_compiled": len([r for r in self.compilation_results if r.success]),
|
|
147
|
+
"skills_deployed": self.skills_deployed,
|
|
148
|
+
"started_at": self.started_at,
|
|
149
|
+
"completed_at": self.completed_at,
|
|
150
|
+
"errors": self.errors,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ── Metric Snapshot (for delta calculation) ─────────────────────────
|
|
155
|
+
@dataclass
|
|
156
|
+
class MetricSnapshot:
|
|
157
|
+
"""Before/after metric snapshot for a skill evaluation."""
|
|
158
|
+
skill_name: str
|
|
159
|
+
baseline: dict # {"success_rate": 0.67, "avg_time": 98, ...}
|
|
160
|
+
evolved: dict # {"success_rate": 0.94, "avg_time": 61, ...}
|
|
161
|
+
deltas: dict # {"success_rate": +0.27, "avg_time": -0.38}
|
|
162
|
+
threshold: float
|
|
163
|
+
passed: bool
|
|
164
|
+
dataset_size: int
|
|
165
|
+
holdout_size: int
|
|
166
|
+
evaluated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ── Canonical Run Schema (Agent-Agnostic Telemetry) ───────────────────
|
|
170
|
+
@dataclass
|
|
171
|
+
class ToolCallRecord:
|
|
172
|
+
"""Record of a single tool invocation."""
|
|
173
|
+
id: str
|
|
174
|
+
name: str
|
|
175
|
+
input_summary: Optional[str] = None
|
|
176
|
+
duration_ms: Optional[int] = None
|
|
177
|
+
result_summary: Optional[str] = None
|
|
178
|
+
error: Optional[str] = None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class FileTouchRecord:
|
|
183
|
+
"""Record of file read/write/delete operation."""
|
|
184
|
+
path: str
|
|
185
|
+
action: str = "write" # "read" | "write" | "delete"
|
|
186
|
+
size_bytes: Optional[int] = None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class RunMetrics:
|
|
191
|
+
"""Aggregated execution metrics for a run."""
|
|
192
|
+
input_tokens: Optional[int] = None
|
|
193
|
+
output_tokens: Optional[int] = None
|
|
194
|
+
cache_tokens: Optional[int] = None
|
|
195
|
+
latency_ms: Optional[int] = None
|
|
196
|
+
cost_usd: Optional[float] = None
|
|
197
|
+
tool_call_count: int = 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class CanonicalRun:
|
|
202
|
+
"""Vendor-neutral canonical run event. Maps from any agent's native format."""
|
|
203
|
+
# Required fields
|
|
204
|
+
run_id: str
|
|
205
|
+
agent_name: str
|
|
206
|
+
collector: str
|
|
207
|
+
started_at: str
|
|
208
|
+
task_name: str
|
|
209
|
+
outcome: str # "success" | "failure" | "partial" | "unknown"
|
|
210
|
+
|
|
211
|
+
# Optional fields
|
|
212
|
+
agent_version: Optional[str] = None
|
|
213
|
+
provider: Optional[str] = None
|
|
214
|
+
model: Optional[str] = None
|
|
215
|
+
repo: Optional[str] = None
|
|
216
|
+
session_id: Optional[str] = None
|
|
217
|
+
ended_at: Optional[str] = None
|
|
218
|
+
tool_calls: list[ToolCallRecord] = field(default_factory=list)
|
|
219
|
+
files_touched: list[FileTouchRecord] = field(default_factory=list)
|
|
220
|
+
artifacts: list[dict] = field(default_factory=list)
|
|
221
|
+
errors: list[dict] = field(default_factory=list)
|
|
222
|
+
metrics: Optional[RunMetrics] = None
|
|
223
|
+
eval_scores: list[dict] = field(default_factory=list)
|
|
224
|
+
improvement_candidates: list[dict] = field(default_factory=list)
|
|
225
|
+
context: dict = field(default_factory=dict)
|
|
226
|
+
resolution: Optional[str] = None
|
|
227
|
+
collector_version: str = "0.1.0"
|
|
228
|
+
|
|
229
|
+
def to_dict(self) -> dict:
|
|
230
|
+
"""Convert to dict, handling nested dataclasses."""
|
|
231
|
+
data = asdict(self)
|
|
232
|
+
# Convert nested dataclasses to dicts
|
|
233
|
+
if self.metrics:
|
|
234
|
+
data["metrics"] = asdict(self.metrics)
|
|
235
|
+
if self.tool_calls:
|
|
236
|
+
data["tool_calls"] = [asdict(tc) for tc in self.tool_calls]
|
|
237
|
+
if self.files_touched:
|
|
238
|
+
data["files_touched"] = [asdict(ft) for ft in self.files_touched]
|
|
239
|
+
return data
|
|
240
|
+
|
|
241
|
+
def to_json(self) -> str:
|
|
242
|
+
return json.dumps(self.to_dict(), indent=2, default=str)
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def from_dict(cls, data: dict) -> "CanonicalRun":
|
|
246
|
+
"""Reconstruct from dict, handling nested dataclasses."""
|
|
247
|
+
# Reconstruct nested objects
|
|
248
|
+
if data.get("metrics") and not isinstance(data["metrics"], RunMetrics):
|
|
249
|
+
data["metrics"] = RunMetrics(**data["metrics"])
|
|
250
|
+
if data.get("tool_calls"):
|
|
251
|
+
data["tool_calls"] = [
|
|
252
|
+
ToolCallRecord(**tc) if isinstance(tc, dict) else tc
|
|
253
|
+
for tc in data["tool_calls"]
|
|
254
|
+
]
|
|
255
|
+
if data.get("files_touched"):
|
|
256
|
+
data["files_touched"] = [
|
|
257
|
+
FileTouchRecord(**ft) if isinstance(ft, dict) else ft
|
|
258
|
+
for ft in data["files_touched"]
|
|
259
|
+
]
|
|
260
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
⑥ DESPLIEGA — Skill Deployer Module
|
|
3
|
+
|
|
4
|
+
Auto-registers compiled and validated skills into the skill registry.
|
|
5
|
+
Creates SKILL.md files and makes them available to all agents.
|
|
6
|
+
|
|
7
|
+
This is the "birth" — the skill becomes real and accessible.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import shutil
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from .models import SkillGenesisPacket, CompilationResult, MetricSnapshot
|
|
17
|
+
|
|
18
|
+
SKILLS_DIR = Path.home() / ".hermes" / "skills"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SkillDeployer:
|
|
22
|
+
"""Deploys validated skills to the multi-agent skill registry."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
SKILLS_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
self.deployment_log = Path.home() / ".hermes" / "traces" / "deployments.jsonl"
|
|
27
|
+
|
|
28
|
+
# ── Deploy ──────────────────────────────────────────────────────
|
|
29
|
+
def deploy(
|
|
30
|
+
self,
|
|
31
|
+
packet: SkillGenesisPacket,
|
|
32
|
+
compilation: CompilationResult,
|
|
33
|
+
validation: MetricSnapshot,
|
|
34
|
+
) -> dict:
|
|
35
|
+
"""Deploy a validated skill to the registry.
|
|
36
|
+
|
|
37
|
+
Returns deployment info with paths and status.
|
|
38
|
+
"""
|
|
39
|
+
if not validation.passed:
|
|
40
|
+
return {
|
|
41
|
+
"deployed": False,
|
|
42
|
+
"skill_name": compilation.skill_name,
|
|
43
|
+
"reason": f"Validation failed. Delta {validation.deltas} < threshold {packet.threshold}",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Determine skill category
|
|
47
|
+
category = self._categorize(packet)
|
|
48
|
+
|
|
49
|
+
# Create skill directory
|
|
50
|
+
skill_dir = SKILLS_DIR / category / compilation.skill_name
|
|
51
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
# Generate SKILL.md
|
|
54
|
+
skill_md = self._generate_skill_md(packet, compilation, validation)
|
|
55
|
+
(skill_dir / "SKILL.md").write_text(skill_md)
|
|
56
|
+
|
|
57
|
+
# Copy compiled artifacts if they exist
|
|
58
|
+
compiled_path = Path(compilation.skill_path)
|
|
59
|
+
if compiled_path.exists():
|
|
60
|
+
for f in compiled_path.glob("*"):
|
|
61
|
+
if f.name != "SKILL.md":
|
|
62
|
+
dest = skill_dir / f.name
|
|
63
|
+
if f.is_dir():
|
|
64
|
+
shutil.copytree(f, dest, dirs_exist_ok=True)
|
|
65
|
+
else:
|
|
66
|
+
shutil.copy2(f, dest)
|
|
67
|
+
|
|
68
|
+
# Log deployment
|
|
69
|
+
deploy_info = {
|
|
70
|
+
"deployed": True,
|
|
71
|
+
"skill_name": compilation.skill_name,
|
|
72
|
+
"category": category,
|
|
73
|
+
"skill_dir": str(skill_dir),
|
|
74
|
+
"packet_id": packet.packet_id,
|
|
75
|
+
"intent": packet.intent,
|
|
76
|
+
"delta": validation.deltas,
|
|
77
|
+
"threshold": packet.threshold,
|
|
78
|
+
"passed": validation.passed,
|
|
79
|
+
"deployed_at": datetime.now().isoformat(),
|
|
80
|
+
"target_agent": packet.target_agent,
|
|
81
|
+
"compilation_iteration": compilation.attempt,
|
|
82
|
+
"dspy_score": compilation.best_score,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
with open(self.deployment_log, "a") as f:
|
|
86
|
+
f.write(__import__('json').dumps(deploy_info) + "\n")
|
|
87
|
+
|
|
88
|
+
return deploy_info
|
|
89
|
+
|
|
90
|
+
# ── SKILL.md Generation ─────────────────────────────────────────
|
|
91
|
+
def _generate_skill_md(
|
|
92
|
+
self,
|
|
93
|
+
packet: SkillGenesisPacket,
|
|
94
|
+
compilation: CompilationResult,
|
|
95
|
+
validation: MetricSnapshot,
|
|
96
|
+
) -> str:
|
|
97
|
+
"""Generate a professional SKILL.md for the new skill."""
|
|
98
|
+
return f"""---
|
|
99
|
+
name: {compilation.skill_name}
|
|
100
|
+
category: software-development
|
|
101
|
+
description: Auto-generated by Promethean Cycle. {packet.intent}
|
|
102
|
+
auto_generated: true
|
|
103
|
+
promethean_packet_id: {packet.packet_id}
|
|
104
|
+
generated_at: {datetime.now().isoformat()}
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# {compilation.skill_name.replace('-', ' ').title()}
|
|
108
|
+
|
|
109
|
+
> 🤖 Auto-compiled by the Promethean Cycle (GEPA ⊕ DSPy)
|
|
110
|
+
> **Intent:** {packet.intent}
|
|
111
|
+
> **Delta:** {validation.deltas}
|
|
112
|
+
> **Threshold:** {packet.threshold} → {"✅ PASSED" if validation.passed else "❌ FAILED"}
|
|
113
|
+
|
|
114
|
+
## When to Use
|
|
115
|
+
|
|
116
|
+
This skill was automatically generated in response to detected operational failures.
|
|
117
|
+
Use when encountering errors matching the pattern:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
{packet.error_signature if hasattr(packet, 'error_signature') else packet.intent[:100]}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Validation Results
|
|
124
|
+
|
|
125
|
+
| Metric | Baseline | Evolved | Delta |
|
|
126
|
+
|--------|----------|---------|-------|
|
|
127
|
+
{chr(10).join(f"| {k} | {validation.baseline.get(k, 'N/A')} | {validation.evolved.get(k, 'N/A')} | {v} |" for k, v in validation.deltas.items())}
|
|
128
|
+
|
|
129
|
+
- **Dataset size:** {validation.dataset_size}
|
|
130
|
+
- **Holdout size:** {validation.holdout_size}
|
|
131
|
+
- **DSPy Best Score:** {compilation.best_score}
|
|
132
|
+
- **Compilation iterations:** {compilation.iterations}
|
|
133
|
+
|
|
134
|
+
## Target Agents
|
|
135
|
+
|
|
136
|
+
{packet.target_agent}
|
|
137
|
+
|
|
138
|
+
## Compilation Strategy
|
|
139
|
+
|
|
140
|
+
BetterTogether: `{packet.strategy}`
|
|
141
|
+
|
|
142
|
+
## Signature
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
{packet.signature}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Notes
|
|
149
|
+
|
|
150
|
+
This skill was born from the Promethean Cycle — the dashboard detected a capability gap,
|
|
151
|
+
GEPA formulated a learning objective, DSPy compiled the solution, and the delta validator
|
|
152
|
+
confirmed its effectiveness. No human wrote this skill. The system learned it.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
# ── Categorization ──────────────────────────────────────────────
|
|
156
|
+
def _categorize(self, packet: SkillGenesisPacket) -> str:
|
|
157
|
+
"""Determine the best category for this skill."""
|
|
158
|
+
intent_lower = packet.intent.lower()
|
|
159
|
+
if any(kw in intent_lower for kw in ["deploy", "build", "docker", "ci", "pipeline"]):
|
|
160
|
+
return "devops"
|
|
161
|
+
elif any(kw in intent_lower for kw in ["database", "migration", "drizzle", "sql"]):
|
|
162
|
+
return "backend-patterns"
|
|
163
|
+
elif any(kw in intent_lower for kw in ["test", "validation", "qa", "verify"]):
|
|
164
|
+
return "testing"
|
|
165
|
+
elif any(kw in intent_lower for kw in ["auth", "login", "session", "oauth"]):
|
|
166
|
+
return "security"
|
|
167
|
+
elif any(kw in intent_lower for kw in ["expo", "react-native", "mobile", "app"]):
|
|
168
|
+
return "software-development"
|
|
169
|
+
else:
|
|
170
|
+
return "software-development"
|
|
171
|
+
|
|
172
|
+
# ── Registry Update ─────────────────────────────────────────────
|
|
173
|
+
def get_deployment_history(self, limit: int = 20) -> list[dict]:
|
|
174
|
+
"""Get recent deployment history."""
|
|
175
|
+
if not self.deployment_log.exists():
|
|
176
|
+
return []
|
|
177
|
+
|
|
178
|
+
deployments = []
|
|
179
|
+
with open(self.deployment_log) as f:
|
|
180
|
+
for line in f:
|
|
181
|
+
if line.strip():
|
|
182
|
+
deployments.append(__import__('json').loads(line))
|
|
183
|
+
|
|
184
|
+
return deployments[-limit:]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ── Singleton ───────────────────────────────────────────────────────
|
|
188
|
+
_deployer: Optional[SkillDeployer] = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_deployer() -> SkillDeployer:
|
|
192
|
+
global _deployer
|
|
193
|
+
if _deployer is None:
|
|
194
|
+
_deployer = SkillDeployer()
|
|
195
|
+
return _deployer
|