@polymorphism-tech/morph-spec 8.34.2 → 8.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +22 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +16 -17
- package/framework/agents.json +197 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +236 -11
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/e2e-check.js +135 -22
- package/framework/hooks/shared/payload-utils.js +15 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +26 -5
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +18 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +33 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1483 -839
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +164 -68
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +61 -37
- package/framework/standards/ai-agents/context-providers.md +97 -24
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +119 -18
- package/framework/standards/ai-agents/mcp-tools.md +85 -32
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +39 -5
- package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
- package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
- package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
- package/framework/standards/ai-agents/modalities-vision.md +6 -2
- package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +38 -9
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +174 -39
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +27 -19
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +34 -3
- package/framework/standards/ai-agents/sweet-spot.md +23 -3
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +13 -0
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +3 -3
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +15 -2
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +148 -6
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +76 -4
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/finish.js +379 -24
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +9 -1
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +50 -4
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/git/pr-files.js +56 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +410 -33
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +583 -28
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +24 -0
- package/src/lib/worktree/worktree-audit.js +30 -1
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +11 -4
- package/src/utils/hooks-installer.js +29 -26
- package/src/utils/skills-installer.js +36 -29
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Serialization;
|
|
3
|
+
using Microsoft.Extensions.AI;
|
|
4
|
+
|
|
5
|
+
namespace Morph.AiKit.Evals;
|
|
6
|
+
|
|
7
|
+
/// <summary>
|
|
8
|
+
/// Um cenario de eval: as mensagens que vao ao modelo e as propriedades que a resposta tem
|
|
9
|
+
/// de ter.
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// <para>
|
|
13
|
+
/// <b>O dataset e versionado e vive em disco</b>, organizado como
|
|
14
|
+
/// <c>{agente}/{tenant}/{cenario}.json</c>. Os dois primeiros segmentos existem porque a
|
|
15
|
+
/// pergunta util quase nunca e "o produto regrediu?", e sim "o agente X regrediu para o
|
|
16
|
+
/// tenant Y?" — e um dataset plano nao consegue responder isso sem convencao de nome.
|
|
17
|
+
/// </para>
|
|
18
|
+
/// <para>
|
|
19
|
+
/// <b>Dado real de tenant e proibido aqui.</b> A cache guarda mensagens INTEIRAS e e
|
|
20
|
+
/// commitada: um segredo que entre no dataset entra no git. O fixture so le cenario deste
|
|
21
|
+
/// diretorio versionado — nunca de banco, nunca de variavel de ambiente.
|
|
22
|
+
/// </para>
|
|
23
|
+
/// </remarks>
|
|
24
|
+
public sealed record EvalScenario
|
|
25
|
+
{
|
|
26
|
+
/// <summary>Primeiro segmento do caminho no dataset.</summary>
|
|
27
|
+
public required string Agent { get; init; }
|
|
28
|
+
|
|
29
|
+
/// <summary>Segundo segmento do caminho no dataset.</summary>
|
|
30
|
+
public required string Tenant { get; init; }
|
|
31
|
+
|
|
32
|
+
/// <summary>
|
|
33
|
+
/// Nome do arquivo, sem extensao. Vira o <c>scenarioName</c> da cache, e por isso e o
|
|
34
|
+
/// que aparece na mensagem de miss.
|
|
35
|
+
/// </summary>
|
|
36
|
+
public required string Name { get; init; }
|
|
37
|
+
|
|
38
|
+
/// <summary>As mensagens enviadas ao modelo, na ordem.</summary>
|
|
39
|
+
public required IReadOnlyList<ChatMessage> Messages { get; init; }
|
|
40
|
+
|
|
41
|
+
/// <summary>As assercoes deterministicas que decidem pass/fail deste cenario.</summary>
|
|
42
|
+
public IReadOnlyList<EvalAssertion> Assertions { get; init; } = [];
|
|
43
|
+
|
|
44
|
+
/// <summary>
|
|
45
|
+
/// Identidade do cenario dentro da cache: <c>{agente}.{tenant}.{nome}</c>.
|
|
46
|
+
/// </summary>
|
|
47
|
+
/// <remarks>
|
|
48
|
+
/// <para>
|
|
49
|
+
/// Qualificado, e nao so <see cref="Name"/>, porque dois tenants tem cenarios com o
|
|
50
|
+
/// mesmo nome de proposito ("saudacao", "handoff") — e uma chave de cache que os
|
|
51
|
+
/// confundisse devolveria a resposta de um tenant para o outro.
|
|
52
|
+
/// </para>
|
|
53
|
+
/// <para>
|
|
54
|
+
/// <b>O separador e ponto, e nao barra, por uma MEDICAO.</b> A primeira versao usava
|
|
55
|
+
/// barra, na suposicao de que a biblioteca aninharia o nome do cenario em diretorios. Ela
|
|
56
|
+
/// nao aninha: <c>PathValidation.ValidatePathSegment</c> recusa o nome com
|
|
57
|
+
/// <c>ArgumentException: 'scenarioName' contains invalid path characters or directory
|
|
58
|
+
/// traversal sequences</c> — o nome do cenario e UM segmento de caminho, sempre. Medido
|
|
59
|
+
/// na v10.9.0 contra o SDK .NET 10.
|
|
60
|
+
/// </para>
|
|
61
|
+
/// </remarks>
|
|
62
|
+
public string CacheKey => $"{Agent}.{Tenant}.{Name}";
|
|
63
|
+
|
|
64
|
+
/// <summary>
|
|
65
|
+
/// Le TODOS os cenarios de um dataset, em ordem estavel (ordinal por caminho).
|
|
66
|
+
/// </summary>
|
|
67
|
+
/// <remarks>
|
|
68
|
+
/// Ordem estavel importa mais do que parece: e ela que faz duas maquinas gravarem a
|
|
69
|
+
/// mesma arvore de cache e produzirem o mesmo diff. Uma enumeracao dependente do
|
|
70
|
+
/// sistema de arquivos e um diff que muda sozinho.
|
|
71
|
+
/// </remarks>
|
|
72
|
+
/// <param name="datasetRoot">Raiz do dataset versionado.</param>
|
|
73
|
+
/// <exception cref="DirectoryNotFoundException">Quando a raiz nao existe — declarar um dataset ausente e erro de config, nao um dataset vazio.</exception>
|
|
74
|
+
public static IReadOnlyList<EvalScenario> LoadAll(string datasetRoot)
|
|
75
|
+
{
|
|
76
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
|
|
77
|
+
|
|
78
|
+
if (!Directory.Exists(datasetRoot))
|
|
79
|
+
{
|
|
80
|
+
throw new DirectoryNotFoundException(
|
|
81
|
+
$"eval-dataset-missing: nao existe dataset em \"{datasetRoot}\". "
|
|
82
|
+
+ "Um dataset ausente e erro de configuracao — uma suite de eval que avalia zero cenarios "
|
|
83
|
+
+ "e sai verde e o guardrail inerte que este kit existe para nao produzir.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
var files = Directory
|
|
87
|
+
.EnumerateFiles(datasetRoot, "*.json", SearchOption.AllDirectories)
|
|
88
|
+
.OrderBy(p => p.Replace('\\', '/'), StringComparer.Ordinal)
|
|
89
|
+
.ToList();
|
|
90
|
+
|
|
91
|
+
var scenarios = new List<EvalScenario>(files.Count);
|
|
92
|
+
foreach (var file in files)
|
|
93
|
+
{
|
|
94
|
+
scenarios.Add(Load(datasetRoot, file));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return scenarios;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// <summary>Le um cenario, derivando agente e tenant do caminho relativo a raiz.</summary>
|
|
101
|
+
public static EvalScenario Load(string datasetRoot, string file)
|
|
102
|
+
{
|
|
103
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
|
|
104
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(file);
|
|
105
|
+
|
|
106
|
+
var relative = Path.GetRelativePath(datasetRoot, file).Replace('\\', '/');
|
|
107
|
+
var segments = relative.Split('/');
|
|
108
|
+
if (segments.Length != 3)
|
|
109
|
+
{
|
|
110
|
+
throw new InvalidOperationException(
|
|
111
|
+
$"eval-dataset-shape: \"{relative}\" nao esta em {{agente}}/{{tenant}}/{{cenario}}.json. "
|
|
112
|
+
+ "O caminho E a identidade do cenario: aceitar outra forma tornaria a chave de cache ambigua.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var dto = JsonSerializer.Deserialize<ScenarioDto>(File.ReadAllText(file), JsonOptions)
|
|
116
|
+
?? throw new InvalidOperationException($"eval-dataset-shape: \"{relative}\" nao parseia como cenario.");
|
|
117
|
+
|
|
118
|
+
if (dto.Messages is null || dto.Messages.Count == 0)
|
|
119
|
+
{
|
|
120
|
+
throw new InvalidOperationException(
|
|
121
|
+
$"eval-dataset-shape: \"{relative}\" nao declara nenhuma mensagem — um cenario sem pergunta nao avalia nada.");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
var assertions = new List<EvalAssertion>();
|
|
125
|
+
var used = new HashSet<string>(StringComparer.Ordinal);
|
|
126
|
+
for (var i = 0; i < (dto.Assertions?.Count ?? 0); i++)
|
|
127
|
+
{
|
|
128
|
+
var raw = dto.Assertions![i];
|
|
129
|
+
// Nome default derivado do indice, e unicidade COBRADA: a biblioteca indexa
|
|
130
|
+
// metricas por nome, e um nome repetido faria a segunda assercao sobrescrever a
|
|
131
|
+
// primeira — um cenario que parece cobrar duas propriedades cobrando uma.
|
|
132
|
+
var name = string.IsNullOrWhiteSpace(raw.Name) ? $"{raw.Kind}-{i + 1}" : raw.Name!;
|
|
133
|
+
if (!used.Add(name))
|
|
134
|
+
{
|
|
135
|
+
throw new InvalidOperationException(
|
|
136
|
+
$"eval-dataset-shape: \"{relative}\" repete o nome de assercao \"{name}\" — "
|
|
137
|
+
+ "nomes de metrica sao unicos por cenario, senao uma assercao apaga a outra.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
assertions.Add(new EvalAssertion
|
|
141
|
+
{
|
|
142
|
+
Name = name,
|
|
143
|
+
Kind = ParseKind(raw.Kind, relative),
|
|
144
|
+
Target = raw.Target,
|
|
145
|
+
Value = raw.Value,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return new EvalScenario
|
|
150
|
+
{
|
|
151
|
+
Agent = segments[0],
|
|
152
|
+
Tenant = segments[1],
|
|
153
|
+
Name = Path.GetFileNameWithoutExtension(segments[2]),
|
|
154
|
+
Messages = [.. dto.Messages.Select(m => new ChatMessage(RoleOf(m.Role, relative), m.Text ?? string.Empty))],
|
|
155
|
+
Assertions = assertions,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
160
|
+
{
|
|
161
|
+
PropertyNameCaseInsensitive = true,
|
|
162
|
+
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
private static ChatRole RoleOf(string? role, string relative) => role?.ToLowerInvariant() switch
|
|
166
|
+
{
|
|
167
|
+
"system" => ChatRole.System,
|
|
168
|
+
"user" => ChatRole.User,
|
|
169
|
+
"assistant" => ChatRole.Assistant,
|
|
170
|
+
"tool" => ChatRole.Tool,
|
|
171
|
+
_ => throw new InvalidOperationException(
|
|
172
|
+
$"eval-dataset-shape: \"{relative}\" usa o papel de mensagem \"{role}\", que nao existe."),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
private static EvalAssertionKind ParseKind(string? kind, string relative)
|
|
176
|
+
=> Enum.TryParse<EvalAssertionKind>((kind ?? string.Empty).Replace("-", string.Empty, StringComparison.Ordinal), ignoreCase: true, out var parsed)
|
|
177
|
+
? parsed
|
|
178
|
+
: throw new InvalidOperationException(
|
|
179
|
+
$"eval-dataset-shape: \"{relative}\" declara a assercao \"{kind}\", que nao existe. "
|
|
180
|
+
+ $"Conhecidas: {string.Join(", ", Enum.GetNames<EvalAssertionKind>())}.");
|
|
181
|
+
|
|
182
|
+
private sealed record ScenarioDto
|
|
183
|
+
{
|
|
184
|
+
[JsonPropertyName("messages")]
|
|
185
|
+
public IReadOnlyList<MessageDto>? Messages { get; init; }
|
|
186
|
+
|
|
187
|
+
[JsonPropertyName("assertions")]
|
|
188
|
+
public IReadOnlyList<AssertionDto>? Assertions { get; init; }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private sealed record MessageDto
|
|
192
|
+
{
|
|
193
|
+
[JsonPropertyName("role")]
|
|
194
|
+
public string? Role { get; init; }
|
|
195
|
+
|
|
196
|
+
[JsonPropertyName("text")]
|
|
197
|
+
public string? Text { get; init; }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private sealed record AssertionDto
|
|
201
|
+
{
|
|
202
|
+
[JsonPropertyName("name")]
|
|
203
|
+
public string? Name { get; init; }
|
|
204
|
+
|
|
205
|
+
[JsonPropertyName("kind")]
|
|
206
|
+
public string? Kind { get; init; }
|
|
207
|
+
|
|
208
|
+
[JsonPropertyName("target")]
|
|
209
|
+
public string? Target { get; init; }
|
|
210
|
+
|
|
211
|
+
[JsonPropertyName("value")]
|
|
212
|
+
public string? Value { get; init; }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Serialization;
|
|
3
|
+
|
|
4
|
+
namespace Morph.AiKit.Evals;
|
|
5
|
+
|
|
6
|
+
/// <summary>Estado da arvore de cache nesta execucao, como o sumario o reporta.</summary>
|
|
7
|
+
public static class EvalCacheState
|
|
8
|
+
{
|
|
9
|
+
/// <summary>Todos os cenarios resolveram pela cache. Nenhuma chamada ao provider.</summary>
|
|
10
|
+
public const string Warm = "warm";
|
|
11
|
+
|
|
12
|
+
/// <summary>Ao menos um cenario foi ao provider — a arvore mudou e precisa ser commitada.</summary>
|
|
13
|
+
public const string Cold = "cold";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// <summary>Uma falha de eval, com nome — cenario, assercao e o motivo literal.</summary>
|
|
17
|
+
/// <remarks>
|
|
18
|
+
/// <para>
|
|
19
|
+
/// <b>Por que o sumario carrega isto, e nao so contadores.</b> Medido: degradar a resposta em
|
|
20
|
+
/// cache deixava a suite vermelha (correto) com a mensagem <c>Expected 0, Actual 1</c> —
|
|
21
|
+
/// verdadeira e inutil. Nada a jusante conseguia dizer QUAL cenario caiu nem por que, porque a
|
|
22
|
+
/// informacao morria dentro do fixture.
|
|
23
|
+
/// </para>
|
|
24
|
+
/// <para>
|
|
25
|
+
/// A rubrica <c>eval-de-modelo.md</c> exige, na banda 9-10, "derrubando o cenario e
|
|
26
|
+
/// NOMEANDO-O", e da como exemplo <c>eval-fail: triagem.acme.a-saudacao</c>. Sem este tipo a
|
|
27
|
+
/// propria rubrica seria inexequivel — o que e pior do que nao ter rubrica, porque parece
|
|
28
|
+
/// coberto.
|
|
29
|
+
/// </para>
|
|
30
|
+
/// </remarks>
|
|
31
|
+
/// <param name="Scenario">A chave do cenario (<c>{agente}.{tenant}.{nome}</c>).</param>
|
|
32
|
+
/// <param name="Assertion">O nome da assercao que reprovou.</param>
|
|
33
|
+
/// <param name="Kind">A regua usada, para distinguir duas assercoes homonimas de tipos distintos.</param>
|
|
34
|
+
/// <param name="Reason">O motivo literal produzido pela assercao.</param>
|
|
35
|
+
public sealed record EvalFailureDetail(
|
|
36
|
+
[property: JsonPropertyName("scenario")] string Scenario,
|
|
37
|
+
[property: JsonPropertyName("assertion")] string Assertion,
|
|
38
|
+
[property: JsonPropertyName("kind")] string Kind,
|
|
39
|
+
[property: JsonPropertyName("reason")] string Reason)
|
|
40
|
+
{
|
|
41
|
+
/// <summary>Uma linha legivel, no formato que a rubrica cita.</summary>
|
|
42
|
+
public override string ToString()
|
|
43
|
+
=> $"eval-fail: {Scenario} — {Assertion} ({Kind}): {Reason}";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// O sumario que o fixture escreve no fim da suite, e que o CLI le.
|
|
48
|
+
/// </summary>
|
|
49
|
+
/// <remarks>
|
|
50
|
+
/// <para>
|
|
51
|
+
/// <b>Por que ARQUIVO e nao uma linha marcada no stdout.</b> O runner de teste captura a
|
|
52
|
+
/// saida de console por teste, e o que chega ao stdout do processo depende da configuracao
|
|
53
|
+
/// do runner e do logger. Um contrato que quebra quando alguem liga um logger e um contrato
|
|
54
|
+
/// ruim — e a falha seria SILENCIOSA (sumario ausente, contagem desconhecida, no verde pelo
|
|
55
|
+
/// codigo de saida). O arquivo tem outra virtude: quem escolhe o caminho e o CLI, entao dois
|
|
56
|
+
/// nos de eval no mesmo <c>verify</c> nao colidem.
|
|
57
|
+
/// </para>
|
|
58
|
+
/// <para>
|
|
59
|
+
/// <b>Do outro lado, o leitor trata sumario ausente ou ilegivel como DESCONHECIDO, jamais
|
|
60
|
+
/// como zero.</b> Um sumario que nao parseia nao pode fabricar <c>scenarios: 0</c> e
|
|
61
|
+
/// transformar uma baseline em queda inventada.
|
|
62
|
+
/// </para>
|
|
63
|
+
/// </remarks>
|
|
64
|
+
public sealed record EvalRunSummary
|
|
65
|
+
{
|
|
66
|
+
/// <summary>Quantos cenarios foram avaliados.</summary>
|
|
67
|
+
[JsonPropertyName("scenarios")]
|
|
68
|
+
public required int Scenarios { get; init; }
|
|
69
|
+
|
|
70
|
+
/// <summary>Quantos passaram em TODAS as suas assercoes deterministicas.</summary>
|
|
71
|
+
[JsonPropertyName("passed")]
|
|
72
|
+
public required int Passed { get; init; }
|
|
73
|
+
|
|
74
|
+
/// <summary>Quantos falharam em ao menos uma assercao deterministica.</summary>
|
|
75
|
+
[JsonPropertyName("failed")]
|
|
76
|
+
public required int Failed { get; init; }
|
|
77
|
+
|
|
78
|
+
/// <summary><see cref="EvalCacheState.Warm"/> ou <see cref="EvalCacheState.Cold"/>.</summary>
|
|
79
|
+
[JsonPropertyName("cache")]
|
|
80
|
+
public required string Cache { get; init; }
|
|
81
|
+
|
|
82
|
+
/// <summary>Veredito do carimbo, em minusculas (<c>ok</c>, <c>missing</c>, ...).</summary>
|
|
83
|
+
[JsonPropertyName("stamp")]
|
|
84
|
+
public required string Stamp { get; init; }
|
|
85
|
+
|
|
86
|
+
/// <summary>
|
|
87
|
+
/// Quantas vezes a cache falhou e a chamada desceu ao cliente interno. Em modo offline
|
|
88
|
+
/// com cache quente e ZERO — e esse zero e a prova de que nenhuma chamada paga foi feita.
|
|
89
|
+
/// </summary>
|
|
90
|
+
[JsonPropertyName("misses")]
|
|
91
|
+
public required int Misses { get; init; }
|
|
92
|
+
|
|
93
|
+
/// <summary>O <c>ProviderName</c> usado, o mesmo que entrou na chave.</summary>
|
|
94
|
+
[JsonPropertyName("provider")]
|
|
95
|
+
public required string Provider { get; init; }
|
|
96
|
+
|
|
97
|
+
/// <summary>O <c>DefaultModelId</c> usado, o mesmo que entrou na chave.</summary>
|
|
98
|
+
[JsonPropertyName("model")]
|
|
99
|
+
public string? Model { get; init; }
|
|
100
|
+
|
|
101
|
+
/// <summary>
|
|
102
|
+
/// Metricas de JUIZO (avaliadores de qualidade), nome para valor. Reportadas, nunca
|
|
103
|
+
/// decisivas: com a cache quente elas sao deterministicas, mas a variancia volta na
|
|
104
|
+
/// regravacao, e um gate que dependesse disso nasceria destinado a ser desligado.
|
|
105
|
+
/// </summary>
|
|
106
|
+
[JsonPropertyName("quality")]
|
|
107
|
+
public IReadOnlyDictionary<string, double> Quality { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
|
|
108
|
+
|
|
109
|
+
/// <summary>
|
|
110
|
+
/// As falhas, uma por assercao reprovada, com nome. Vazia num run verde.
|
|
111
|
+
/// </summary>
|
|
112
|
+
/// <remarks>
|
|
113
|
+
/// Lista, e nao um booleano ou um contador, porque um cenario pode quebrar em mais de uma
|
|
114
|
+
/// assercao e as duas informam coisas diferentes: uma resposta vazia derruba
|
|
115
|
+
/// <c>Contains</c> E <c>JsonHasKey</c>, e saber disso e a diferenca entre "o prompt mudou"
|
|
116
|
+
/// e "o modelo parou de responder".
|
|
117
|
+
/// </remarks>
|
|
118
|
+
[JsonPropertyName("failures")]
|
|
119
|
+
public IReadOnlyList<EvalFailureDetail> Failures { get; init; } = [];
|
|
120
|
+
|
|
121
|
+
/// <summary>
|
|
122
|
+
/// As falhas em texto, uma por linha — a mensagem que um teste ou o CLI mostra.
|
|
123
|
+
/// </summary>
|
|
124
|
+
/// <remarks>
|
|
125
|
+
/// Devolve frase propria quando nao ha falha alguma, em vez de string vazia: uma mensagem
|
|
126
|
+
/// de assercao vazia e indistinguivel de uma mensagem que ninguem preencheu.
|
|
127
|
+
/// </remarks>
|
|
128
|
+
public string DescribeFailures()
|
|
129
|
+
=> Failures.Count == 0
|
|
130
|
+
? "nenhuma falha de eval registrada"
|
|
131
|
+
: string.Join(Environment.NewLine, Failures.Select(f => f.ToString()));
|
|
132
|
+
|
|
133
|
+
private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
|
|
134
|
+
|
|
135
|
+
/// <summary>Grava o sumario. Cria o diretorio se preciso; sobrescreve se ja existe.</summary>
|
|
136
|
+
public void WriteTo(string path)
|
|
137
|
+
{
|
|
138
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
|
139
|
+
|
|
140
|
+
var dir = Path.GetDirectoryName(Path.GetFullPath(path));
|
|
141
|
+
if (!string.IsNullOrEmpty(dir))
|
|
142
|
+
{
|
|
143
|
+
Directory.CreateDirectory(dir);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
File.WriteAllText(path, JsonSerializer.Serialize(this, Json));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// <summary>Le um sumario gravado. Devolve nulo para arquivo ausente ou ilegivel — DESCONHECIDO, jamais zero.</summary>
|
|
150
|
+
public static EvalRunSummary? ReadFrom(string path)
|
|
151
|
+
{
|
|
152
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
|
153
|
+
|
|
154
|
+
if (!File.Exists(path))
|
|
155
|
+
{
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try
|
|
160
|
+
{
|
|
161
|
+
return JsonSerializer.Deserialize<EvalRunSummary>(File.ReadAllText(path), Json);
|
|
162
|
+
}
|
|
163
|
+
catch (JsonException)
|
|
164
|
+
{
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
using Microsoft.Extensions.AI;
|
|
2
|
+
|
|
3
|
+
namespace Morph.AiKit.Evals;
|
|
4
|
+
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// O <see cref="IChatClient"/> que RECUSA no miss: a peca que torna possivel rodar evals no
|
|
7
|
+
/// CI sem uma unica credencial.
|
|
8
|
+
/// </summary>
|
|
9
|
+
/// <remarks>
|
|
10
|
+
/// <para>
|
|
11
|
+
/// <b>Onde ele fica.</b> A biblioteca de Evaluation embrulha o cliente que recebe num
|
|
12
|
+
/// cliente de cache. No HIT, o cliente interno nunca e invocado. No MISS, a chamada desce
|
|
13
|
+
/// para o interno — e o interno, aqui, e este. Logo: com a cache quente ele nao e chamado
|
|
14
|
+
/// nenhuma vez, e quando e chamado a resposta certa e "faltou cenario na cache", nao uma
|
|
15
|
+
/// chamada de rede.
|
|
16
|
+
/// </para>
|
|
17
|
+
/// <para>
|
|
18
|
+
/// <b>Por que ele replica <c>ProviderName</c> e <c>DefaultModelId</c>.</b> Esses dois
|
|
19
|
+
/// campos do <see cref="ChatClientMetadata"/> entram na chave de cache. Se o cliente
|
|
20
|
+
/// offline se apresentasse com outro nome, a chave computada na leitura seria outra e TODO
|
|
21
|
+
/// cenario daria miss — uma cache quente indistinguivel de uma cache vazia. Eles sao
|
|
22
|
+
/// parametros, e nao constantes, exatamente porque tem de reproduzir o run que GRAVOU.
|
|
23
|
+
/// </para>
|
|
24
|
+
/// <para>
|
|
25
|
+
/// <b><see cref="ChatClientMetadata.ProviderUri"/> e deliberadamente nulo.</b> O endpoint
|
|
26
|
+
/// NAO entra na chave — medido contra a v10.9.0, contra o que a documentacao afirma. E essa
|
|
27
|
+
/// ausencia que permite ao cliente offline nao ter endpoint nenhum: ele nao tem para onde
|
|
28
|
+
/// ligar, e nao precisa ter.
|
|
29
|
+
/// </para>
|
|
30
|
+
/// <para>
|
|
31
|
+
/// <b><see cref="Invocations"/> e a prova, e ela e um contador, nao um sniffer.</b> "Zero
|
|
32
|
+
/// chamadas de rede" nao se mede sem instrumentar a rede; "zero invocacoes do cliente
|
|
33
|
+
/// interno" se mede aqui, e e suficiente porque o cliente interno e o UNICO caminho para a
|
|
34
|
+
/// rede nesta montagem. O contador sobe ANTES do lancamento, para que uma tentativa
|
|
35
|
+
/// interrompida ainda apareca.
|
|
36
|
+
/// </para>
|
|
37
|
+
/// </remarks>
|
|
38
|
+
public sealed class OfflineChatClient : IChatClient
|
|
39
|
+
{
|
|
40
|
+
private readonly Lock _gate = new();
|
|
41
|
+
private readonly Func<string> _currentScenario;
|
|
42
|
+
private readonly string _cachePath;
|
|
43
|
+
private readonly string _envFile;
|
|
44
|
+
private int _invocations;
|
|
45
|
+
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// Cria o cliente offline replicando a identidade do run que gravou a cache.
|
|
48
|
+
/// </summary>
|
|
49
|
+
/// <param name="providerName">Mesmo <c>ProviderName</c> do run que gravou. Entra na chave.</param>
|
|
50
|
+
/// <param name="defaultModelId">Mesmo <c>DefaultModelId</c> do run que gravou. Entra na chave.</param>
|
|
51
|
+
/// <param name="cachePath">Raiz da arvore de cache, so para a mensagem de miss.</param>
|
|
52
|
+
/// <param name="envFile">Arquivo de credencial esperado, so para a mensagem de miss.</param>
|
|
53
|
+
/// <param name="currentScenario">
|
|
54
|
+
/// Le o nome do cenario em execucao no momento da chamada. E uma funcao, e nao um
|
|
55
|
+
/// valor, porque um fixture roda N cenarios com o MESMO cliente: um nome capturado na
|
|
56
|
+
/// construcao nomearia sempre o primeiro.
|
|
57
|
+
/// </param>
|
|
58
|
+
public OfflineChatClient(
|
|
59
|
+
string providerName,
|
|
60
|
+
string? defaultModelId,
|
|
61
|
+
string cachePath,
|
|
62
|
+
string envFile,
|
|
63
|
+
Func<string> currentScenario)
|
|
64
|
+
{
|
|
65
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(providerName);
|
|
66
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(cachePath);
|
|
67
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(envFile);
|
|
68
|
+
ArgumentNullException.ThrowIfNull(currentScenario);
|
|
69
|
+
|
|
70
|
+
ProviderName = providerName;
|
|
71
|
+
DefaultModelId = defaultModelId;
|
|
72
|
+
_cachePath = cachePath;
|
|
73
|
+
_envFile = envFile;
|
|
74
|
+
_currentScenario = currentScenario;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// <summary>O <c>ProviderName</c> replicado — o mesmo que gravou a cache.</summary>
|
|
78
|
+
public string ProviderName { get; }
|
|
79
|
+
|
|
80
|
+
/// <summary>O <c>DefaultModelId</c> replicado — o mesmo que gravou a cache.</summary>
|
|
81
|
+
public string? DefaultModelId { get; }
|
|
82
|
+
|
|
83
|
+
/// <summary>
|
|
84
|
+
/// Quantas vezes a cache falhou e a chamada desceu ate aqui. Num run com a cache quente
|
|
85
|
+
/// e ZERO, e essa e a prova de que nenhuma chamada paga foi feita.
|
|
86
|
+
/// </summary>
|
|
87
|
+
public int Invocations
|
|
88
|
+
{
|
|
89
|
+
get { lock (_gate) { return _invocations; } }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// <inheritdoc />
|
|
93
|
+
public Task<ChatResponse> GetResponseAsync(
|
|
94
|
+
IEnumerable<ChatMessage> messages,
|
|
95
|
+
ChatOptions? options = null,
|
|
96
|
+
CancellationToken cancellationToken = default)
|
|
97
|
+
=> throw Miss();
|
|
98
|
+
|
|
99
|
+
/// <inheritdoc />
|
|
100
|
+
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
101
|
+
IEnumerable<ChatMessage> messages,
|
|
102
|
+
ChatOptions? options = null,
|
|
103
|
+
CancellationToken cancellationToken = default)
|
|
104
|
+
=> throw Miss();
|
|
105
|
+
|
|
106
|
+
/// <inheritdoc />
|
|
107
|
+
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
108
|
+
{
|
|
109
|
+
ArgumentNullException.ThrowIfNull(serviceType);
|
|
110
|
+
|
|
111
|
+
if (serviceKey is not null)
|
|
112
|
+
{
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (serviceType == typeof(ChatClientMetadata))
|
|
117
|
+
{
|
|
118
|
+
// providerUri nulo de proposito: o endpoint nao entra na chave de cache, e um
|
|
119
|
+
// cliente que nunca liga para lugar nenhum nao deve anunciar um destino.
|
|
120
|
+
return new ChatClientMetadata(ProviderName, providerUri: null, DefaultModelId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return serviceType.IsInstanceOfType(this) ? this : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// <inheritdoc />
|
|
127
|
+
public void Dispose()
|
|
128
|
+
{
|
|
129
|
+
// Nada a liberar: este cliente nao abre socket, nao le ambiente e nao guarda estado
|
|
130
|
+
// fora do contador. E o ponto.
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private EvalCacheMissException Miss()
|
|
134
|
+
{
|
|
135
|
+
lock (_gate)
|
|
136
|
+
{
|
|
137
|
+
_invocations++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new EvalCacheMissException(_currentScenario(), _cachePath, _envFile);
|
|
141
|
+
}
|
|
142
|
+
}
|