@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,91 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Microsoft.Extensions.AI;
|
|
3
|
+
|
|
4
|
+
namespace Morph.AiKit.Agents;
|
|
5
|
+
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// O agente <b>como dado</b>: tudo o que a <c>AgentFactory</c> (T10) precisa para
|
|
8
|
+
/// materializar um <c>AIAgent</c>, e nada mais. Nao tem comportamento de execucao, nao
|
|
9
|
+
/// abre conexao, nao le configuracao — o que permite descrever um agente em teste, em
|
|
10
|
+
/// JSON ou em banco sem arrastar infraestrutura junto.
|
|
11
|
+
/// </summary>
|
|
12
|
+
/// <remarks>
|
|
13
|
+
/// <para>
|
|
14
|
+
/// <b>O modelo nao aparece aqui.</b> So o <see cref="Alias"/>, que e resolvido pelo
|
|
15
|
+
/// <c>ModelRegistry</c>. E a regra do standard <c>ai-agents-providers-model-registry</c>:
|
|
16
|
+
/// agentes referenciam alias, nunca id de modelo — trocar de provider e mudanca de um
|
|
17
|
+
/// arquivo so.
|
|
18
|
+
/// </para>
|
|
19
|
+
/// </remarks>
|
|
20
|
+
public sealed record AgentSpec
|
|
21
|
+
{
|
|
22
|
+
private readonly string _alias = string.Empty;
|
|
23
|
+
private readonly string _instructions = string.Empty;
|
|
24
|
+
|
|
25
|
+
/// <summary>
|
|
26
|
+
/// Chave no <c>ModelRegistry</c>. Nunca um id de modelo.
|
|
27
|
+
/// Vazio ou so espaco e rejeitado na construcao: um alias em branco viraria uma falha
|
|
28
|
+
/// de resolucao ilegivel varias camadas adiante.
|
|
29
|
+
/// </summary>
|
|
30
|
+
public required string Alias
|
|
31
|
+
{
|
|
32
|
+
get => _alias;
|
|
33
|
+
init
|
|
34
|
+
{
|
|
35
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(Alias));
|
|
36
|
+
_alias = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// <summary>Prompt de sistema do agente. Vazio ou so espaco e rejeitado na construcao.</summary>
|
|
41
|
+
public required string Instructions
|
|
42
|
+
{
|
|
43
|
+
get => _instructions;
|
|
44
|
+
init
|
|
45
|
+
{
|
|
46
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(Instructions));
|
|
47
|
+
_instructions = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// <summary>
|
|
52
|
+
/// Protocolo desta spec. <b>Nulo = usa o <c>api:</c> do alias</b> — o caso normal.
|
|
53
|
+
/// Preenchido, vence o alias (ver <see cref="ResolveApi"/>).
|
|
54
|
+
/// </summary>
|
|
55
|
+
public AgentApi? Api { get; init; }
|
|
56
|
+
|
|
57
|
+
/// <summary>Tools do agente. Vazio por padrao — nunca nulo.</summary>
|
|
58
|
+
public IReadOnlyList<AITool> Tools { get; init; } = [];
|
|
59
|
+
|
|
60
|
+
/// <summary>
|
|
61
|
+
/// Schema JSON da saida estruturada. Nulo = saida livre.
|
|
62
|
+
/// <para>
|
|
63
|
+
/// <b>Armadilha de tempo de vida:</b> um <see cref="JsonElement"/> so e valido enquanto
|
|
64
|
+
/// o <see cref="JsonDocument"/> que o originou estiver vivo. Guarde aqui um
|
|
65
|
+
/// <see cref="JsonElement.Clone"/> quando o documento for descartado — senao o schema
|
|
66
|
+
/// morre silenciosamente entre a montagem da spec e o uso dela.
|
|
67
|
+
/// </para>
|
|
68
|
+
/// </summary>
|
|
69
|
+
public JsonElement? OutputSchema { get; init; }
|
|
70
|
+
|
|
71
|
+
/// <summary>Bag unico de opcoes. Nunca nulo; o default nao envia opcao nenhuma.</summary>
|
|
72
|
+
public AgentSpecOptions Options { get; init; } = new();
|
|
73
|
+
|
|
74
|
+
/// <summary>Nome legivel do agente (telemetria, logs). Nulo = sem nome.</summary>
|
|
75
|
+
public string? Name { get; init; }
|
|
76
|
+
|
|
77
|
+
/// <summary>Descricao legivel do agente. Nulo = sem descricao.</summary>
|
|
78
|
+
public string? Description { get; init; }
|
|
79
|
+
|
|
80
|
+
/// <summary>
|
|
81
|
+
/// Precedencia entre o <c>api:</c> da spec e o <c>api:</c> do alias, num lugar so.
|
|
82
|
+
/// <b>A spec vence quando presente; o alias vence quando a spec e nula.</b>
|
|
83
|
+
/// </summary>
|
|
84
|
+
/// <remarks>
|
|
85
|
+
/// A regra mora aqui, e nao dentro da <c>AgentFactory</c>, porque divergencia silenciosa
|
|
86
|
+
/// entre spec e registry e exatamente o bug que o <c>api:</c> por alias existe para
|
|
87
|
+
/// eliminar — e uma regra copiada em dois lugares e uma regra que ja divergiu.
|
|
88
|
+
/// </remarks>
|
|
89
|
+
/// <param name="aliasApi">O <c>api:</c> declarado no alias resolvido.</param>
|
|
90
|
+
public AgentApi ResolveApi(AgentApi aliasApi) => Api ?? aliasApi;
|
|
91
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
using Microsoft.Extensions.AI;
|
|
2
|
+
|
|
3
|
+
namespace Morph.AiKit.Agents;
|
|
4
|
+
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// UM UNICO bag de opcoes do agente — a forma copiada do <c>AgentFrameworkToolkit</c>
|
|
7
|
+
/// sem o pacote (decisao D2). Em vez de N objetos de configuracao espalhados por
|
|
8
|
+
/// camadas, tudo o que um agente pode ajustar vive aqui, como dado.
|
|
9
|
+
/// </summary>
|
|
10
|
+
/// <remarks>
|
|
11
|
+
/// <para>
|
|
12
|
+
/// <b>Regra estrutural, e a razao de o tipo existir:</b> toda propriedade e nula por
|
|
13
|
+
/// padrao, e <b>nulo significa "nao envia ao provider"</b> — nunca "envia o default do
|
|
14
|
+
/// framework". E isso que impede o kit de mudar, por omissao, o comportamento de um
|
|
15
|
+
/// modelo. O canario da regra e
|
|
16
|
+
/// <c>AgentSpecTests.FreshOptionsProduceChatOptionsIndistinguishableFromPristine</c>:
|
|
17
|
+
/// uma <see cref="AgentSpecOptions"/> recem-construida produz um <see cref="ChatOptions"/>
|
|
18
|
+
/// indistinguivel de <c>new ChatOptions()</c>, propriedade por propriedade, por reflexao.
|
|
19
|
+
/// </para>
|
|
20
|
+
/// <para><b>Duas familias de opcao convivem aqui, e a fronteira e explicita:</b></para>
|
|
21
|
+
/// <list type="table">
|
|
22
|
+
/// <listheader><term>Familia</term><description>Para onde vai</description></listheader>
|
|
23
|
+
/// <item>
|
|
24
|
+
/// <term>Nivel de requisicao</term>
|
|
25
|
+
/// <description>
|
|
26
|
+
/// <see cref="Temperature"/>, <see cref="TopP"/>, <see cref="MaxOutputTokens"/>,
|
|
27
|
+
/// <see cref="Seed"/>, <see cref="StopSequences"/>, <see cref="ToolMode"/>,
|
|
28
|
+
/// <see cref="ReasoningEffort"/> e <see cref="ServiceTier"/> viram
|
|
29
|
+
/// <see cref="ChatOptions"/> em <see cref="ApplyTo"/>.
|
|
30
|
+
/// </description>
|
|
31
|
+
/// </item>
|
|
32
|
+
/// <item>
|
|
33
|
+
/// <term>Nivel de agente / pipeline</term>
|
|
34
|
+
/// <description>
|
|
35
|
+
/// <see cref="MaxToolIterations"/>, <see cref="AllowConcurrentToolInvocation"/>,
|
|
36
|
+
/// <see cref="TimeoutSeconds"/> e <see cref="EnableSensitiveTelemetry"/> NAO existem
|
|
37
|
+
/// em <see cref="ChatOptions"/>. Sao lidas diretamente pela <c>AgentFactory</c> (T10)
|
|
38
|
+
/// ao montar o pipeline e pela telemetria (T12). O teste
|
|
39
|
+
/// <c>AgentLevelKnobsNeverLeakIntoChatOptions</c> pina essa fronteira.
|
|
40
|
+
/// </description>
|
|
41
|
+
/// </item>
|
|
42
|
+
/// </list>
|
|
43
|
+
/// </remarks>
|
|
44
|
+
public sealed record AgentSpecOptions
|
|
45
|
+
{
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// Chave usada em <see cref="ChatOptions.AdditionalProperties"/> para transportar
|
|
48
|
+
/// <see cref="ServiceTier"/>. Ver a nota de fato em <see cref="ServiceTier"/>.
|
|
49
|
+
/// </summary>
|
|
50
|
+
public const string ServiceTierPropertyName = "service_tier";
|
|
51
|
+
|
|
52
|
+
/// <summary>Amostragem. Nulo = o provider decide.</summary>
|
|
53
|
+
public float? Temperature { get; init; }
|
|
54
|
+
|
|
55
|
+
/// <summary>Nucleus sampling. Nulo = o provider decide.</summary>
|
|
56
|
+
public float? TopP { get; init; }
|
|
57
|
+
|
|
58
|
+
/// <summary>Teto de tokens de saida. Nulo = o provider decide.</summary>
|
|
59
|
+
public int? MaxOutputTokens { get; init; }
|
|
60
|
+
|
|
61
|
+
/// <summary>Semente de amostragem, quando o provider suporta. Nulo = nao envia.</summary>
|
|
62
|
+
public long? Seed { get; init; }
|
|
63
|
+
|
|
64
|
+
/// <summary>
|
|
65
|
+
/// Sequencias de parada. Nulo = nao envia. Lista vazia NAO e tratada como nulo: e
|
|
66
|
+
/// enviada como veio. "Nulo nao envia; nao-nulo envia o que voce escreveu" e a regra
|
|
67
|
+
/// inteira, sem excecao escondida.
|
|
68
|
+
/// </summary>
|
|
69
|
+
public IReadOnlyList<string>? StopSequences { get; init; }
|
|
70
|
+
|
|
71
|
+
/// <summary>Modo de tool calling (<c>Auto</c>, <c>None</c>, <c>RequireAny</c>...). Nulo = nao envia.</summary>
|
|
72
|
+
public ChatToolMode? ToolMode { get; init; }
|
|
73
|
+
|
|
74
|
+
/// <summary>
|
|
75
|
+
/// Esforco de raciocinio. Tipado com o enum de primeira classe do
|
|
76
|
+
/// <c>Microsoft.Extensions.AI</c> 10.9.0 (<c>None/Low/Medium/High/ExtraHigh</c>) em vez
|
|
77
|
+
/// de string, porque e exatamente esse valor que o limite de tool calling da Chat
|
|
78
|
+
/// Completions cita — ver <c>ModelAliasConstraints.ToolCallingRequiresReasoningEffortNone</c>.
|
|
79
|
+
/// Nulo = nao envia: vale o default do provider, e qual e ele o kit NAO afirma saber.
|
|
80
|
+
/// </summary>
|
|
81
|
+
public Microsoft.Extensions.AI.ReasoningEffort? ReasoningEffort { get; init; }
|
|
82
|
+
|
|
83
|
+
/// <summary>
|
|
84
|
+
/// Tier de servico (ex.: <c>"flex"</c>, <c>"priority"</c>).
|
|
85
|
+
/// <b>Nota de fato, nao de gosto:</b> o <see cref="ChatOptions"/> do
|
|
86
|
+
/// <c>Microsoft.Extensions.AI</c> 10.9.0 NAO tem propriedade de service tier — apurado
|
|
87
|
+
/// por reflexao sobre o assembly do pin, nao por memoria. Quando definido, o valor
|
|
88
|
+
/// viaja em <see cref="ChatOptions.AdditionalProperties"/> sob a chave
|
|
89
|
+
/// <see cref="ServiceTierPropertyName"/>, e so chega ao fio se o adaptador do provider
|
|
90
|
+
/// ler dali ou se a <c>AgentFactory</c> montar um
|
|
91
|
+
/// <see cref="ChatOptions.RawRepresentationFactory"/>. Nulo = nao envia.
|
|
92
|
+
/// </summary>
|
|
93
|
+
public string? ServiceTier { get; init; }
|
|
94
|
+
|
|
95
|
+
/// <summary>
|
|
96
|
+
/// Teto de voltas de tool calling. <b>Nivel de agente</b>: vira
|
|
97
|
+
/// <c>FunctionInvokingChatClient.MaximumIterationsPerRequest</c> na T10, nunca
|
|
98
|
+
/// <see cref="ChatOptions"/>. Nulo = default do pipeline.
|
|
99
|
+
/// </summary>
|
|
100
|
+
public int? MaxToolIterations { get; init; }
|
|
101
|
+
|
|
102
|
+
/// <summary>
|
|
103
|
+
/// Permite invocar tools concorrentemente numa mesma volta. <b>Nivel de agente</b>
|
|
104
|
+
/// (<c>FunctionInvokingChatClient.AllowConcurrentInvocation</c>). Nulo = default do pipeline.
|
|
105
|
+
/// </summary>
|
|
106
|
+
public bool? AllowConcurrentToolInvocation { get; init; }
|
|
107
|
+
|
|
108
|
+
/// <summary>
|
|
109
|
+
/// Timeout da invocacao, em segundos. <b>Nivel de agente</b>: vira
|
|
110
|
+
/// <c>CancellationToken</c> na T10. Nulo = sem timeout proprio.
|
|
111
|
+
/// </summary>
|
|
112
|
+
public int? TimeoutSeconds { get; init; }
|
|
113
|
+
|
|
114
|
+
/// <summary>
|
|
115
|
+
/// Liga a captura de conteudo sensivel (prompt e resposta) nos spans.
|
|
116
|
+
/// <b>Nivel de agente</b> — insumo da T12. A diferenca entre nulo e <c>false</c> e
|
|
117
|
+
/// preservada de proposito: nulo e "nao opinei", <c>false</c> e "decidi que nao".
|
|
118
|
+
/// </summary>
|
|
119
|
+
public bool? EnableSensitiveTelemetry { get; init; }
|
|
120
|
+
|
|
121
|
+
/// <summary>
|
|
122
|
+
/// Produz um <see cref="ChatOptions"/> novo com — e SOMENTE com — as opcoes de nivel
|
|
123
|
+
/// de requisicao que foram definidas.
|
|
124
|
+
/// </summary>
|
|
125
|
+
public ChatOptions ToChatOptions() => ApplyTo(new ChatOptions());
|
|
126
|
+
|
|
127
|
+
/// <summary>
|
|
128
|
+
/// Aplica as opcoes de nivel de requisicao sobre um <see cref="ChatOptions"/> existente.
|
|
129
|
+
/// Propriedade nula aqui NAO toca no destino, o que permite a <c>AgentFactory</c>
|
|
130
|
+
/// preencher <c>Tools</c> e <c>ResponseFormat</c> antes ou depois sem risco de perde-los.
|
|
131
|
+
/// </summary>
|
|
132
|
+
/// <param name="options">Destino. Nao pode ser nulo.</param>
|
|
133
|
+
/// <returns>O proprio <paramref name="options"/>, para encadeamento.</returns>
|
|
134
|
+
public ChatOptions ApplyTo(ChatOptions options)
|
|
135
|
+
{
|
|
136
|
+
ArgumentNullException.ThrowIfNull(options);
|
|
137
|
+
|
|
138
|
+
if (Temperature is { } temperature)
|
|
139
|
+
{
|
|
140
|
+
options.Temperature = temperature;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (TopP is { } topP)
|
|
144
|
+
{
|
|
145
|
+
options.TopP = topP;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (MaxOutputTokens is { } maxOutputTokens)
|
|
149
|
+
{
|
|
150
|
+
options.MaxOutputTokens = maxOutputTokens;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (Seed is { } seed)
|
|
154
|
+
{
|
|
155
|
+
options.Seed = seed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (StopSequences is not null)
|
|
159
|
+
{
|
|
160
|
+
options.StopSequences = [.. StopSequences];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (ToolMode is not null)
|
|
164
|
+
{
|
|
165
|
+
options.ToolMode = ToolMode;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (ReasoningEffort is { } effort)
|
|
169
|
+
{
|
|
170
|
+
options.Reasoning = new ReasoningOptions { Effort = effort };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (ServiceTier is not null)
|
|
174
|
+
{
|
|
175
|
+
options.AdditionalProperties ??= new AdditionalPropertiesDictionary();
|
|
176
|
+
options.AdditionalProperties[ServiceTierPropertyName] = ServiceTier;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return options;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
namespace Morph.AiKit;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// Ancora de tipo do assembly. Existe para que o esqueleto tenha uma superficie
|
|
5
|
+
/// publica verificavel antes das tasks T7-T14 — e para que qualquer consumidor
|
|
6
|
+
/// (teste, DI, reflexao) tenha um <c>typeof</c> estavel para apontar ao assembly
|
|
7
|
+
/// sem depender de um tipo de dominio que ainda nao existe.
|
|
8
|
+
/// </summary>
|
|
9
|
+
public sealed class AiKitAssemblyMarker
|
|
10
|
+
{
|
|
11
|
+
private AiKitAssemblyMarker()
|
|
12
|
+
{
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
using Microsoft.Agents.AI;
|
|
2
|
+
using Microsoft.Extensions.AI;
|
|
3
|
+
|
|
4
|
+
namespace Morph.AiKit.Conversation;
|
|
5
|
+
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// O <see cref="ChatHistoryProvider"/> da casa: o historico de conversa vive no acervo
|
|
8
|
+
/// <b>do projeto</b> (<see cref="IConversationStore"/>), nao dentro da
|
|
9
|
+
/// <see cref="AgentSession"/>.
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// <para>
|
|
13
|
+
/// <b>A decisao que este arquivo torna compilavel.</b> O caminho embutido do MAF
|
|
14
|
+
/// (<c>InMemoryChatHistoryProvider</c>) guarda a conversa no estado da sessao, o que resolve
|
|
15
|
+
/// um chat de demonstracao e nao resolve um CRM multi-tenant: a conversa precisa sobreviver
|
|
16
|
+
/// ao restart, ser lida por relatorio, ser filtrada por tenant e ser apagada a pedido do
|
|
17
|
+
/// titular. Aqui a <see cref="AgentSession"/> guarda apenas <b>a chave</b> da conversa; o
|
|
18
|
+
/// conteudo mora onde o projeto puder consulta-lo — tipicamente uma coluna <c>jsonb</c>.
|
|
19
|
+
/// </para>
|
|
20
|
+
/// <para>
|
|
21
|
+
/// <b>A invariante que o teste cobra:</b> o provider e <b>uma instancia compartilhada por
|
|
22
|
+
/// todas as sessoes</b>. Por isso este tipo <b>nao tem campo de estado por conversa</b> — o
|
|
23
|
+
/// unico estado por sessao vive em <see cref="ProviderSessionState{TState}"/>, dentro da
|
|
24
|
+
/// propria <see cref="AgentSession"/>. Um campo de instancia guardando "a conversa atual"
|
|
25
|
+
/// e como o historico de um usuario aparece na tela de outro, e a falha nao aparece em teste
|
|
26
|
+
/// de uma sessao so: aparece em producao, sob concorrencia, como vazamento de dado alheio.
|
|
27
|
+
/// </para>
|
|
28
|
+
/// <para>
|
|
29
|
+
/// <b>De onde vem a chave da conversa</b>, nesta ordem:
|
|
30
|
+
/// <list type="number">
|
|
31
|
+
/// <item>o que o projeto amarrou com <see cref="BindConversation"/> — o caso do CRM, em que
|
|
32
|
+
/// a conversa ja existe como registro antes de o agente entrar;</item>
|
|
33
|
+
/// <item>o <c>ConversationId</c> da sessao, quando o proprio provider de modelo gerencia a
|
|
34
|
+
/// conversa (<see cref="ChatClientAgentSession"/>);</item>
|
|
35
|
+
/// <item>um id novo, <see cref="Guid.CreateVersion7"/> — ordenavel no tempo, ao contrario de
|
|
36
|
+
/// <c>Guid.NewGuid</c>, que fragmenta indice de banco.</item>
|
|
37
|
+
/// </list>
|
|
38
|
+
/// </para>
|
|
39
|
+
/// </remarks>
|
|
40
|
+
public sealed class ConversationStore : ChatHistoryProvider
|
|
41
|
+
{
|
|
42
|
+
/// <summary>
|
|
43
|
+
/// Chave sob a qual a identidade da conversa e guardada no estado da sessao. Fixa e
|
|
44
|
+
/// publica porque ela viaja na serializacao da sessao: mudar esta string quebra a
|
|
45
|
+
/// retomada de toda conversa ja persistida.
|
|
46
|
+
/// </summary>
|
|
47
|
+
public const string ConversationIdStateKey = "morph.conversation.id";
|
|
48
|
+
|
|
49
|
+
private readonly IConversationStore _store;
|
|
50
|
+
private readonly ProviderSessionState<string> _conversationId;
|
|
51
|
+
|
|
52
|
+
/// <summary>Monta o provider sobre um acervo.</summary>
|
|
53
|
+
/// <param name="store">Onde o historico e lido e gravado. Nunca nulo.</param>
|
|
54
|
+
public ConversationStore(IConversationStore store)
|
|
55
|
+
{
|
|
56
|
+
ArgumentNullException.ThrowIfNull(store);
|
|
57
|
+
|
|
58
|
+
_store = store;
|
|
59
|
+
_conversationId = new ProviderSessionState<string>(NewConversationId, ConversationIdStateKey);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// <inheritdoc />
|
|
63
|
+
/// <remarks>
|
|
64
|
+
/// Declara a chave que este provider ocupa no estado da sessao — e o que permite ao MAF
|
|
65
|
+
/// serializar e retomar uma sessao sem adivinhar o que e de quem.
|
|
66
|
+
/// </remarks>
|
|
67
|
+
public override IReadOnlyList<string> StateKeys => [ConversationIdStateKey];
|
|
68
|
+
|
|
69
|
+
/// <summary>
|
|
70
|
+
/// Amarra a sessao a uma conversa que <b>ja existe</b> no acervo do projeto. E o caminho
|
|
71
|
+
/// do CRM: a conversa nasce como registro de negocio, e o agente entra nela.
|
|
72
|
+
/// </summary>
|
|
73
|
+
/// <remarks>
|
|
74
|
+
/// Chamar isto depois de a sessao ja ter carregado historico troca a conversa sob os pes
|
|
75
|
+
/// da sessao. Amarre logo apos <c>CreateSessionAsync</c>.
|
|
76
|
+
/// </remarks>
|
|
77
|
+
/// <param name="session">A sessao.</param>
|
|
78
|
+
/// <param name="conversationId">A chave da conversa no acervo.</param>
|
|
79
|
+
public void BindConversation(AgentSession session, string conversationId)
|
|
80
|
+
{
|
|
81
|
+
ArgumentNullException.ThrowIfNull(session);
|
|
82
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
|
|
83
|
+
|
|
84
|
+
_conversationId.SaveState(session, conversationId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// <summary>
|
|
88
|
+
/// A chave da conversa desta sessao, criando-a se ainda nao houver. Le e escreve
|
|
89
|
+
/// <b>na sessao</b>, jamais num campo deste objeto.
|
|
90
|
+
/// </summary>
|
|
91
|
+
public string GetConversationId(AgentSession session)
|
|
92
|
+
{
|
|
93
|
+
ArgumentNullException.ThrowIfNull(session);
|
|
94
|
+
|
|
95
|
+
return _conversationId.GetOrInitializeState(session);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// <inheritdoc />
|
|
99
|
+
/// <remarks>
|
|
100
|
+
/// Devolve o historico guardado; as mensagens desta volta sao acrescentadas pelo agente
|
|
101
|
+
/// depois. Repetir aqui as mensagens do pedido duplicaria o turno corrente no prompt.
|
|
102
|
+
/// </remarks>
|
|
103
|
+
protected override async ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(
|
|
104
|
+
InvokingContext context,
|
|
105
|
+
CancellationToken cancellationToken = default)
|
|
106
|
+
{
|
|
107
|
+
ArgumentNullException.ThrowIfNull(context);
|
|
108
|
+
|
|
109
|
+
return await _store
|
|
110
|
+
.LoadAsync(GetConversationId(RequireSession(context.Session)), cancellationToken)
|
|
111
|
+
.ConfigureAwait(false);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// <inheritdoc />
|
|
115
|
+
/// <remarks>
|
|
116
|
+
/// <para>
|
|
117
|
+
/// Grava o pedido e a resposta <b>desta</b> volta, nessa ordem — e so eles. Regravar o
|
|
118
|
+
/// historico inteiro a cada volta faria a conversa crescer em progressao geometrica.
|
|
119
|
+
/// </para>
|
|
120
|
+
/// <para>
|
|
121
|
+
/// <b>Volta que falhou nao e gravada.</b> Com <c>InvokeException</c> preenchida nao ha
|
|
122
|
+
/// resposta; persistir o pedido sozinho deixaria a conversa terminando numa pergunta que
|
|
123
|
+
/// o modelo nunca respondeu, e a proxima volta a mandaria de novo — o usuario veria a si
|
|
124
|
+
/// mesmo repetido.
|
|
125
|
+
/// </para>
|
|
126
|
+
/// </remarks>
|
|
127
|
+
protected override async ValueTask StoreChatHistoryAsync(
|
|
128
|
+
InvokedContext context,
|
|
129
|
+
CancellationToken cancellationToken = default)
|
|
130
|
+
{
|
|
131
|
+
ArgumentNullException.ThrowIfNull(context);
|
|
132
|
+
|
|
133
|
+
if (context.InvokeException is not null)
|
|
134
|
+
{
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Os dois lados sao anulaveis no contrato do MAF (a resposta some quando a volta
|
|
139
|
+
// falhou, e o pedido pode vir vazio numa continuacao). Tratados como ausencia, nunca
|
|
140
|
+
// desreferenciados.
|
|
141
|
+
List<ChatMessage> round =
|
|
142
|
+
[
|
|
143
|
+
.. context.RequestMessages ?? [],
|
|
144
|
+
.. context.ResponseMessages ?? [],
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
if (round.Count == 0)
|
|
148
|
+
{
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await _store
|
|
153
|
+
.AppendAsync(GetConversationId(RequireSession(context.Session)), round, cancellationToken)
|
|
154
|
+
.ConfigureAwait(false);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// <summary>
|
|
158
|
+
/// O MAF declara <c>InvokingContext.Session</c> como anulavel, e uma invocacao sem
|
|
159
|
+
/// sessao nao tem onde guardar a identidade da conversa. As duas saidas silenciosas sao
|
|
160
|
+
/// piores que a recusa: inventar um id grava uma linha orfa que ninguem consegue ler de
|
|
161
|
+
/// novo, e usar uma chave fixa junta conversas de tenants diferentes no mesmo balde —
|
|
162
|
+
/// que e o vazamento que este provider existe para impedir.
|
|
163
|
+
/// </summary>
|
|
164
|
+
private static AgentSession RequireSession(AgentSession? session)
|
|
165
|
+
=> session ?? throw new InvalidOperationException(
|
|
166
|
+
"ConversationStore foi invocado sem AgentSession. Sem sessao nao ha identidade de " +
|
|
167
|
+
"conversa: crie a sessao com CreateSessionAsync e passe-a em RunAsync. Persistir " +
|
|
168
|
+
"sem chave gravaria conversa orfa ou misturaria conversas de usuarios diferentes.");
|
|
169
|
+
|
|
170
|
+
private static string NewConversationId(AgentSession? session)
|
|
171
|
+
=> session is ChatClientAgentSession { ConversationId: { Length: > 0 } serviceManaged }
|
|
172
|
+
? serviceManaged
|
|
173
|
+
: Guid.CreateVersion7().ToString("n");
|
|
174
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
using Microsoft.Extensions.AI;
|
|
2
|
+
|
|
3
|
+
namespace Morph.AiKit.Conversation;
|
|
4
|
+
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// Onde o historico de conversa fica guardado. <b>A costura entre o kit e o banco do
|
|
7
|
+
/// projeto</b> — e o unico ponto do <see cref="ConversationStore"/> que um projeto precisa
|
|
8
|
+
/// implementar para trocar memoria por tabela.
|
|
9
|
+
/// </summary>
|
|
10
|
+
/// <remarks>
|
|
11
|
+
/// <para>
|
|
12
|
+
/// <b>A decisao da casa que este tipo torna compilavel:</b> a conversa vive no armazenamento
|
|
13
|
+
/// <b>do projeto</b>, nao dentro da <c>AgentSession</c>. Num CRM multi-tenant a conversa e
|
|
14
|
+
/// um registro de negocio — ela precisa sobreviver ao restart do processo, ser lida por
|
|
15
|
+
/// relatorio, ser filtrada por tenant e ser apagada a pedido do titular. Nada disso e
|
|
16
|
+
/// possivel quando o historico so existe na memoria de uma sessao.
|
|
17
|
+
/// </para>
|
|
18
|
+
/// <para>
|
|
19
|
+
/// <b>O kit nao conhece banco.</b> Esta interface e deliberadamente pequena e sem tipo de
|
|
20
|
+
/// infraestrutura na assinatura: nenhum <c>DbContext</c>, nenhuma conexao, nenhum
|
|
21
|
+
/// <c>jsonb</c>. A implementacao de producao vive no projeto (handler falando com o
|
|
22
|
+
/// <c>ApplicationDbContext</c> direto, sem camada de service e sem repository generico);
|
|
23
|
+
/// a implementacao de teste e <see cref="InMemoryConversationStore"/>.
|
|
24
|
+
/// </para>
|
|
25
|
+
/// <para>
|
|
26
|
+
/// <b>Contrato de identidade:</b> <c>conversationId</c> e a chave do acervo, opaca para o
|
|
27
|
+
/// kit. Num CRM ela costuma ser o id da linha de conversa — e por isso
|
|
28
|
+
/// <see cref="ConversationStore.BindConversation"/> existe: e como o projeto amarra uma
|
|
29
|
+
/// sessao a uma conversa que ja existe, em vez de deixar o kit inventar um id.
|
|
30
|
+
/// </para>
|
|
31
|
+
/// <para>
|
|
32
|
+
/// <b>Contrato de concorrencia:</b> uma unica instancia atende todas as sessoes ao mesmo
|
|
33
|
+
/// tempo. A implementacao precisa ser segura para uso concorrente e, acima de tudo,
|
|
34
|
+
/// <b>nunca deixar mensagem de uma conversa aparecer em outra</b> — a chave e o unico
|
|
35
|
+
/// isolamento que existe aqui.
|
|
36
|
+
/// </para>
|
|
37
|
+
/// </remarks>
|
|
38
|
+
public interface IConversationStore
|
|
39
|
+
{
|
|
40
|
+
/// <summary>
|
|
41
|
+
/// O historico da conversa, na ordem cronologica. Conversa inexistente devolve lista
|
|
42
|
+
/// <b>vazia</b>, nunca nulo e nunca excecao: uma primeira volta e o caso normal.
|
|
43
|
+
/// </summary>
|
|
44
|
+
ValueTask<IReadOnlyList<ChatMessage>> LoadAsync(string conversationId, CancellationToken cancellationToken);
|
|
45
|
+
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// Acrescenta as mensagens de uma volta ao fim da conversa. Chamado uma vez por volta,
|
|
48
|
+
/// com o pedido e a resposta daquela volta — nunca com o historico inteiro.
|
|
49
|
+
/// </summary>
|
|
50
|
+
ValueTask AppendAsync(
|
|
51
|
+
string conversationId,
|
|
52
|
+
IReadOnlyList<ChatMessage> messages,
|
|
53
|
+
CancellationToken cancellationToken);
|
|
54
|
+
}
|
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
using System.Collections.Concurrent;
|
|
2
|
+
using Microsoft.Extensions.AI;
|
|
3
|
+
|
|
4
|
+
namespace Morph.AiKit.Conversation;
|
|
5
|
+
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// <see cref="IConversationStore"/> em memoria. <b>Para teste e para prototipo</b> — nao
|
|
8
|
+
/// para producao: o historico morre com o processo, e um CRM que perde a conversa no deploy
|
|
9
|
+
/// nao tem conversa.
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// <para>
|
|
13
|
+
/// Existe para que a suite do kit prove o <see cref="ConversationStore"/> <b>sem banco</b>,
|
|
14
|
+
/// e para que um projeto tenha um exemplar de referencia do contrato antes de escrever a
|
|
15
|
+
/// versao com <c>jsonb</c>.
|
|
16
|
+
/// </para>
|
|
17
|
+
/// <para>
|
|
18
|
+
/// <b>Seguro sob concorrencia por construcao</b>, e nao por acidente: uma
|
|
19
|
+
/// <see cref="ConcurrentDictionary{TKey, TValue}"/> por conversa, e a lista de cada conversa
|
|
20
|
+
/// sempre mutada sob o lock daquela conversa. Sem isso, o teste de duas sessoes concorrentes
|
|
21
|
+
/// mediria a falta de trava aqui em vez do isolamento do provider.
|
|
22
|
+
/// </para>
|
|
23
|
+
/// </remarks>
|
|
24
|
+
public sealed class InMemoryConversationStore : IConversationStore
|
|
25
|
+
{
|
|
26
|
+
private readonly ConcurrentDictionary<string, List<ChatMessage>> _conversations =
|
|
27
|
+
new(StringComparer.Ordinal);
|
|
28
|
+
|
|
29
|
+
/// <summary>As conversas que existem no acervo, para inspecao em teste.</summary>
|
|
30
|
+
public IReadOnlyCollection<string> ConversationIds => _conversations.Keys.ToArray();
|
|
31
|
+
|
|
32
|
+
/// <inheritdoc />
|
|
33
|
+
public ValueTask<IReadOnlyList<ChatMessage>> LoadAsync(
|
|
34
|
+
string conversationId,
|
|
35
|
+
CancellationToken cancellationToken)
|
|
36
|
+
{
|
|
37
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
|
|
38
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
39
|
+
|
|
40
|
+
if (!_conversations.TryGetValue(conversationId, out var messages))
|
|
41
|
+
{
|
|
42
|
+
return ValueTask.FromResult<IReadOnlyList<ChatMessage>>([]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
lock (messages)
|
|
46
|
+
{
|
|
47
|
+
return ValueTask.FromResult<IReadOnlyList<ChatMessage>>([.. messages]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// <inheritdoc />
|
|
52
|
+
public ValueTask AppendAsync(
|
|
53
|
+
string conversationId,
|
|
54
|
+
IReadOnlyList<ChatMessage> messages,
|
|
55
|
+
CancellationToken cancellationToken)
|
|
56
|
+
{
|
|
57
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
|
|
58
|
+
ArgumentNullException.ThrowIfNull(messages);
|
|
59
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
60
|
+
|
|
61
|
+
if (messages.Count == 0)
|
|
62
|
+
{
|
|
63
|
+
return ValueTask.CompletedTask;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
var conversation = _conversations.GetOrAdd(conversationId, _ => []);
|
|
67
|
+
|
|
68
|
+
lock (conversation)
|
|
69
|
+
{
|
|
70
|
+
conversation.AddRange(messages);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ValueTask.CompletedTask;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
using Microsoft.Extensions.AI;
|
|
2
|
+
|
|
3
|
+
namespace Morph.AiKit.Evals;
|
|
4
|
+
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// Conta quantas vezes o cliente interno foi realmente invocado, e repassa tudo o mais
|
|
7
|
+
/// intacto.
|
|
8
|
+
/// </summary>
|
|
9
|
+
/// <remarks>
|
|
10
|
+
/// <para>
|
|
11
|
+
/// <b>Por que existe.</b> Nos modos que TEM cliente real (<see cref="EvalMode.Refresh"/> e
|
|
12
|
+
/// <see cref="EvalMode.Online"/>) nao ha <see cref="OfflineChatClient"/> para contar, e sem
|
|
13
|
+
/// contador o sumario nao consegue dizer se a arvore de cache mudou. "A cache esta fria" e
|
|
14
|
+
/// uma afirmacao com consequencia (a arvore precisa ser commitada), e afirmacao com
|
|
15
|
+
/// consequencia se mede.
|
|
16
|
+
/// </para>
|
|
17
|
+
/// <para>
|
|
18
|
+
/// <b><see cref="GetService"/> repassa VERBATIM</b>, e isso e load-bearing: e por ele que o
|
|
19
|
+
/// <c>ChatClientMetadata</c> — com o <c>ProviderName</c> e o <c>DefaultModelId</c> que
|
|
20
|
+
/// entram na CHAVE de cache — chega a camada de cache. Um wrapper que respondesse por conta
|
|
21
|
+
/// propria aqui gravaria a arvore sob uma identidade diferente da que a leitura offline
|
|
22
|
+
/// replica, e todo cenario daria miss.
|
|
23
|
+
/// </para>
|
|
24
|
+
/// </remarks>
|
|
25
|
+
public sealed class CountingChatClient(IChatClient inner) : IChatClient
|
|
26
|
+
{
|
|
27
|
+
private readonly IChatClient _inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
|
28
|
+
private readonly Lock _gate = new();
|
|
29
|
+
private int _calls;
|
|
30
|
+
|
|
31
|
+
/// <summary>Quantas chamadas atravessaram ate o cliente real.</summary>
|
|
32
|
+
public int Calls
|
|
33
|
+
{
|
|
34
|
+
get { lock (_gate) { return _calls; } }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// <inheritdoc />
|
|
38
|
+
public Task<ChatResponse> GetResponseAsync(
|
|
39
|
+
IEnumerable<ChatMessage> messages,
|
|
40
|
+
ChatOptions? options = null,
|
|
41
|
+
CancellationToken cancellationToken = default)
|
|
42
|
+
{
|
|
43
|
+
Count();
|
|
44
|
+
return _inner.GetResponseAsync(messages, options, cancellationToken);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// <inheritdoc />
|
|
48
|
+
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
49
|
+
IEnumerable<ChatMessage> messages,
|
|
50
|
+
ChatOptions? options = null,
|
|
51
|
+
CancellationToken cancellationToken = default)
|
|
52
|
+
{
|
|
53
|
+
Count();
|
|
54
|
+
return _inner.GetStreamingResponseAsync(messages, options, cancellationToken);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// <inheritdoc />
|
|
58
|
+
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
59
|
+
=> _inner.GetService(serviceType, serviceKey);
|
|
60
|
+
|
|
61
|
+
/// <inheritdoc />
|
|
62
|
+
public void Dispose() => _inner.Dispose();
|
|
63
|
+
|
|
64
|
+
private void Count()
|
|
65
|
+
{
|
|
66
|
+
lock (_gate)
|
|
67
|
+
{
|
|
68
|
+
_calls++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|