@polymorphism-tech/morph-spec 8.34.2 → 8.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -3
- 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 +234 -9
- 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/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/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 +140 -5
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +8 -1
- 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 +333 -21
- 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/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,349 @@
|
|
|
1
|
+
using System.Security.Cryptography;
|
|
2
|
+
using System.Text;
|
|
3
|
+
using System.Text.Json;
|
|
4
|
+
using System.Text.Json.Serialization;
|
|
5
|
+
using Microsoft.Extensions.AI.Evaluation.Reporting;
|
|
6
|
+
using Microsoft.Extensions.Caching.Distributed;
|
|
7
|
+
|
|
8
|
+
namespace Morph.AiKit.Evals;
|
|
9
|
+
|
|
10
|
+
/// <summary>
|
|
11
|
+
/// A cache de respostas em disco do kit, com caminhos CURTOS o bastante para serem
|
|
12
|
+
/// commitados e instalados no Windows.
|
|
13
|
+
/// </summary>
|
|
14
|
+
/// <remarks>
|
|
15
|
+
/// <para>
|
|
16
|
+
/// <b>Por que não usamos o <c>DiskBasedResponseCacheProvider</c> da biblioteca.</b> Ele nomeia
|
|
17
|
+
/// o diretório de cada entrada com o hash inteiro em hexadecimal — <b>96 caracteres</b>. Somado
|
|
18
|
+
/// ao resto (<c>cache/{cenário}/{iteração}/</c>, mais o nome do arquivo), o caminho relativo
|
|
19
|
+
/// chega a <b>225</b> caracteres; com a raiz deste repositório (73) o absoluto dá <b>298</b>,
|
|
20
|
+
/// contra o teto de <b>260</b> do <c>MAX_PATH</c> do Windows.
|
|
21
|
+
/// </para>
|
|
22
|
+
/// <para>
|
|
23
|
+
/// <b>O sintoma foi pior que o erro.</b> O git não recusou barulhento: ele avisou
|
|
24
|
+
/// <c>could not open directory ... Filename too long</c> e commitou <b>1 de 9</b> arquivos — só
|
|
25
|
+
/// o carimbo. A árvore de cache "commitada" não existia no commit, e o canário de CI
|
|
26
|
+
/// (<c>git diff --exit-code</c>) reportava LIMPO porque o git nunca enxergou os arquivos.
|
|
27
|
+
/// Um guarda que não consegue observar o objeto reporta verde por construção.
|
|
28
|
+
/// </para>
|
|
29
|
+
/// <para>
|
|
30
|
+
/// <b>O que muda, e só isso:</b> o segmento de hash passa a ser um PREFIXO de
|
|
31
|
+
/// <see cref="KeyLength"/> caracteres do mesmo hash. O resto do layout é o da biblioteca —
|
|
32
|
+
/// <c>cache/{cenário}/{iteração}/{chave}/{entry.json,contents.data}</c>.
|
|
33
|
+
/// </para>
|
|
34
|
+
/// <para>
|
|
35
|
+
/// <b>Truncar é seguro AQUI por construção, e não por contagem de bits.</b> O
|
|
36
|
+
/// <c>entry.json</c> guarda a chave INTEIRA, e a leitura compara: prefixo que colide com outra
|
|
37
|
+
/// chave devolve <b>miss</b>, nunca a resposta errada. Um miss é caro (regravar) e visível
|
|
38
|
+
/// (o modo offline reprova nomeando o cenário); uma resposta trocada seria um eval verde sobre
|
|
39
|
+
/// a resposta de outro cenário — a única falha que este kit não pode ter.
|
|
40
|
+
/// </para>
|
|
41
|
+
/// <para>
|
|
42
|
+
/// <b>Expiração NÃO é destrutiva.</b> A implementação da biblioteca apaga o diretório da chave
|
|
43
|
+
/// ao ler uma entrada vencida — numa árvore commitada isso significa `git status` sujo e miss
|
|
44
|
+
/// em massa num dia em que ninguém mexeu em nada. Aqui, entrada vencida devolve <c>null</c> e
|
|
45
|
+
/// não toca em disco. O piso de TTL de um ano continua valendo como segunda linha de defesa:
|
|
46
|
+
/// duas proteções para o mesmo tiro é barato quando o tiro apaga trabalho commitado.
|
|
47
|
+
/// </para>
|
|
48
|
+
/// <para>
|
|
49
|
+
/// <b><see cref="ShortPathResponseCache.Refresh"/> é um no-op deliberado.</b> A validade é
|
|
50
|
+
/// absoluta a partir da gravação, e não deslizante. Se <c>Refresh</c> reescrevesse o
|
|
51
|
+
/// <c>entry.json</c>, uma LEITURA sujaria a árvore — e o invariante "um run offline não escreve
|
|
52
|
+
/// um byte" cairia, junto com o canário que o cobra.
|
|
53
|
+
/// </para>
|
|
54
|
+
/// </remarks>
|
|
55
|
+
public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCacheProvider
|
|
56
|
+
{
|
|
57
|
+
/// <summary>
|
|
58
|
+
/// Quantos caracteres do hash nomeiam o diretório da entrada.
|
|
59
|
+
/// </summary>
|
|
60
|
+
/// <remarks>
|
|
61
|
+
/// 16 hexadecimais = 64 bits. O número não precisa ser à prova de colisão sozinho: a
|
|
62
|
+
/// verificação da chave inteira no <c>entry.json</c> transforma qualquer colisão num miss.
|
|
63
|
+
/// Ele precisa apenas ser curto — e 16 derruba o pior caminho relativo de 225 para 145 (medido).
|
|
64
|
+
/// </remarks>
|
|
65
|
+
public const int KeyLength = 16;
|
|
66
|
+
|
|
67
|
+
/// <summary>Subdiretório que separa as entradas do carimbo, na raiz da árvore.</summary>
|
|
68
|
+
/// <remarks>
|
|
69
|
+
/// Mantido (a biblioteca também o usa) porque é ele que torna <see cref="ResetAsync"/>
|
|
70
|
+
/// trivialmente seguro: zerar a cache é apagar UM diretório, sem risco de levar o carimbo
|
|
71
|
+
/// junto. Custa seis caracteres e remove uma classe inteira de engano.
|
|
72
|
+
/// </remarks>
|
|
73
|
+
public const string EntriesDirectory = "cache";
|
|
74
|
+
|
|
75
|
+
private readonly string _root;
|
|
76
|
+
private readonly TimeSpan _timeToLive;
|
|
77
|
+
private readonly TimeProvider _clock;
|
|
78
|
+
|
|
79
|
+
/// <summary>Cria o provedor sobre uma raiz de cache.</summary>
|
|
80
|
+
/// <param name="storageRootPath">Raiz da árvore (a mesma que carrega o carimbo).</param>
|
|
81
|
+
/// <param name="timeToLive">Validade absoluta das entradas, contada da gravação.</param>
|
|
82
|
+
/// <param name="clock">Relógio injetado — a expiração é uma leitura de relógio.</param>
|
|
83
|
+
public ShortPathResponseCacheProvider(string storageRootPath, TimeSpan timeToLive, TimeProvider? clock = null)
|
|
84
|
+
{
|
|
85
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(storageRootPath);
|
|
86
|
+
|
|
87
|
+
_root = storageRootPath;
|
|
88
|
+
_timeToLive = timeToLive;
|
|
89
|
+
_clock = clock ?? TimeProvider.System;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// <summary>Onde as entradas vivem, dentro da raiz.</summary>
|
|
93
|
+
public string EntriesRoot => Path.Combine(_root, EntriesDirectory);
|
|
94
|
+
|
|
95
|
+
/// <inheritdoc />
|
|
96
|
+
public ValueTask<IDistributedCache> GetCacheAsync(
|
|
97
|
+
string scenarioName,
|
|
98
|
+
string iterationName,
|
|
99
|
+
CancellationToken cancellationToken = default)
|
|
100
|
+
{
|
|
101
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(scenarioName);
|
|
102
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(iterationName);
|
|
103
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
104
|
+
|
|
105
|
+
IDistributedCache cache = new ShortPathResponseCache(
|
|
106
|
+
Path.Combine(EntriesRoot, scenarioName, iterationName), scenarioName, iterationName, _timeToLive, _clock);
|
|
107
|
+
|
|
108
|
+
return ValueTask.FromResult(cache);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// <inheritdoc />
|
|
112
|
+
/// <remarks>Apaga TODAS as entradas e preserva o carimbo. Raiz ausente é sucesso: não há o que zerar.</remarks>
|
|
113
|
+
public ValueTask ResetAsync(CancellationToken cancellationToken = default)
|
|
114
|
+
{
|
|
115
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
116
|
+
|
|
117
|
+
if (Directory.Exists(EntriesRoot))
|
|
118
|
+
{
|
|
119
|
+
Directory.Delete(EntriesRoot, recursive: true);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return ValueTask.CompletedTask;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// <inheritdoc />
|
|
126
|
+
/// <remarks>
|
|
127
|
+
/// Este é o ÚNICO caminho que apaga entrada vencida, e ele só roda quando alguém o chama de
|
|
128
|
+
/// propósito. A leitura nunca apaga — ver as observações do tipo.
|
|
129
|
+
/// </remarks>
|
|
130
|
+
public ValueTask DeleteExpiredCacheEntriesAsync(CancellationToken cancellationToken = default)
|
|
131
|
+
{
|
|
132
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
133
|
+
|
|
134
|
+
if (!Directory.Exists(EntriesRoot))
|
|
135
|
+
{
|
|
136
|
+
return ValueTask.CompletedTask;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var now = _clock.GetUtcNow();
|
|
140
|
+
foreach (var entryFile in Directory.EnumerateFiles(EntriesRoot, ShortPathResponseCache.EntryFileName, SearchOption.AllDirectories))
|
|
141
|
+
{
|
|
142
|
+
var entry = ShortPathResponseCache.ReadEntry(entryFile);
|
|
143
|
+
if (entry is not null && entry.Expiration <= now)
|
|
144
|
+
{
|
|
145
|
+
Directory.Delete(Path.GetDirectoryName(entryFile)!, recursive: true);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return ValueTask.CompletedTask;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// <summary>
|
|
154
|
+
/// A cache de UM par (cenário, iteração). Ver <see cref="ShortPathResponseCacheProvider"/> para
|
|
155
|
+
/// o porquê de ela existir.
|
|
156
|
+
/// </summary>
|
|
157
|
+
internal sealed class ShortPathResponseCache : IDistributedCache
|
|
158
|
+
{
|
|
159
|
+
/// <summary>Metadados da entrada. Nome idêntico ao da biblioteca — o layout é o mesmo.</summary>
|
|
160
|
+
internal const string EntryFileName = "entry.json";
|
|
161
|
+
|
|
162
|
+
/// <summary>Payload cru da resposta. Nome idêntico ao da biblioteca.</summary>
|
|
163
|
+
internal const string ContentsFileName = "contents.data";
|
|
164
|
+
|
|
165
|
+
private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
|
|
166
|
+
|
|
167
|
+
private readonly string _directory;
|
|
168
|
+
private readonly string _scenarioName;
|
|
169
|
+
private readonly string _iterationName;
|
|
170
|
+
private readonly TimeSpan _timeToLive;
|
|
171
|
+
private readonly TimeProvider _clock;
|
|
172
|
+
|
|
173
|
+
internal ShortPathResponseCache(
|
|
174
|
+
string directory, string scenarioName, string iterationName, TimeSpan timeToLive, TimeProvider clock)
|
|
175
|
+
{
|
|
176
|
+
_directory = directory;
|
|
177
|
+
_scenarioName = scenarioName;
|
|
178
|
+
_iterationName = iterationName;
|
|
179
|
+
_timeToLive = timeToLive;
|
|
180
|
+
_clock = clock;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// <summary>
|
|
184
|
+
/// O prefixo do hash que nomeia o diretório da entrada.
|
|
185
|
+
/// </summary>
|
|
186
|
+
/// <remarks>
|
|
187
|
+
/// A chave que a biblioteca entrega já é um hash hexadecimal; pegar o prefixo dela mantém o
|
|
188
|
+
/// nome CORRELACIONÁVEL com a chave real (é literalmente o começo dela), o que uma segunda
|
|
189
|
+
/// função de hash destruiria. Uma chave que não seja hexadecimal — improvável, mas não
|
|
190
|
+
/// impossível se a biblioteca mudar — é reduzida por SHA-256 antes de truncar, para que o
|
|
191
|
+
/// nome continue sendo um segmento de caminho válido em qualquer sistema de arquivos.
|
|
192
|
+
/// </remarks>
|
|
193
|
+
internal static string ShortKey(string key)
|
|
194
|
+
{
|
|
195
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
|
196
|
+
|
|
197
|
+
var usable = key.All(Uri.IsHexDigit)
|
|
198
|
+
? key
|
|
199
|
+
: Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(key)));
|
|
200
|
+
|
|
201
|
+
return usable.Length <= ShortPathResponseCacheProvider.KeyLength
|
|
202
|
+
? usable
|
|
203
|
+
: usable[..ShortPathResponseCacheProvider.KeyLength];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// <inheritdoc />
|
|
207
|
+
public byte[]? Get(string key)
|
|
208
|
+
{
|
|
209
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
|
210
|
+
|
|
211
|
+
var dir = Path.Combine(_directory, ShortKey(key));
|
|
212
|
+
var entryPath = Path.Combine(dir, EntryFileName);
|
|
213
|
+
var contentsPath = Path.Combine(dir, ContentsFileName);
|
|
214
|
+
|
|
215
|
+
var entry = ReadEntry(entryPath);
|
|
216
|
+
if (entry is null || !File.Exists(contentsPath))
|
|
217
|
+
{
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// A chave INTEIRA decide. O nome do diretório é um prefixo, e um prefixo pode colidir:
|
|
222
|
+
// sem esta comparação, a colisão devolveria a resposta de OUTRO cenário — a única falha
|
|
223
|
+
// que um eval não pode ter. Com ela, colisão vira miss, que é caro e visível.
|
|
224
|
+
if (!string.Equals(entry.Key, key, StringComparison.Ordinal))
|
|
225
|
+
{
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Vencida: devolve miss e NÃO apaga nada. Apagar durante a leitura é o que faz uma
|
|
230
|
+
// árvore commitada sumir sozinha e sujar o git status de quem só rodou os testes.
|
|
231
|
+
if (entry.Expiration <= _clock.GetUtcNow())
|
|
232
|
+
{
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return File.ReadAllBytes(contentsPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// <inheritdoc />
|
|
240
|
+
public Task<byte[]?> GetAsync(string key, CancellationToken token = default)
|
|
241
|
+
{
|
|
242
|
+
token.ThrowIfCancellationRequested();
|
|
243
|
+
return Task.FromResult(Get(key));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// <inheritdoc />
|
|
247
|
+
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
|
|
248
|
+
{
|
|
249
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
|
250
|
+
ArgumentNullException.ThrowIfNull(value);
|
|
251
|
+
|
|
252
|
+
var dir = Path.Combine(_directory, ShortKey(key));
|
|
253
|
+
Directory.CreateDirectory(dir);
|
|
254
|
+
|
|
255
|
+
var now = _clock.GetUtcNow();
|
|
256
|
+
var entry = new CacheEntry
|
|
257
|
+
{
|
|
258
|
+
ScenarioName = _scenarioName,
|
|
259
|
+
IterationName = _iterationName,
|
|
260
|
+
Key = key,
|
|
261
|
+
Creation = now,
|
|
262
|
+
Expiration = now + _timeToLive,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
File.WriteAllBytes(Path.Combine(dir, ContentsFileName), value);
|
|
266
|
+
// Quebras normalizadas para LF e uma quebra final: estes arquivos são COMMITADOS, e
|
|
267
|
+
// CRLF numa máquina com LF noutra é diff que não é diff.
|
|
268
|
+
File.WriteAllText(
|
|
269
|
+
Path.Combine(dir, EntryFileName),
|
|
270
|
+
JsonSerializer.Serialize(entry, Json).ReplaceLineEndings("\n") + "\n");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// <inheritdoc />
|
|
274
|
+
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
|
|
275
|
+
{
|
|
276
|
+
token.ThrowIfCancellationRequested();
|
|
277
|
+
Set(key, value, options);
|
|
278
|
+
return Task.CompletedTask;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// <inheritdoc />
|
|
282
|
+
/// <remarks>
|
|
283
|
+
/// NO-OP deliberado. A validade é absoluta a partir da gravação, nunca deslizante: se este
|
|
284
|
+
/// método reescrevesse o <c>entry.json</c>, uma LEITURA passaria a escrever, e o invariante
|
|
285
|
+
/// "um run offline não muda um byte da árvore" — com o canário que o cobra — cairia junto.
|
|
286
|
+
/// </remarks>
|
|
287
|
+
public void Refresh(string key) { }
|
|
288
|
+
|
|
289
|
+
/// <inheritdoc />
|
|
290
|
+
public Task RefreshAsync(string key, CancellationToken token = default) => Task.CompletedTask;
|
|
291
|
+
|
|
292
|
+
/// <inheritdoc />
|
|
293
|
+
public void Remove(string key)
|
|
294
|
+
{
|
|
295
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
|
296
|
+
|
|
297
|
+
var dir = Path.Combine(_directory, ShortKey(key));
|
|
298
|
+
if (Directory.Exists(dir))
|
|
299
|
+
{
|
|
300
|
+
Directory.Delete(dir, recursive: true);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/// <inheritdoc />
|
|
305
|
+
public Task RemoveAsync(string key, CancellationToken token = default)
|
|
306
|
+
{
|
|
307
|
+
token.ThrowIfCancellationRequested();
|
|
308
|
+
Remove(key);
|
|
309
|
+
return Task.CompletedTask;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// <summary>Lê o <c>entry.json</c>. Ausente ou ilegível devolve nulo — um miss, nunca uma exceção.</summary>
|
|
313
|
+
internal static CacheEntry? ReadEntry(string entryPath)
|
|
314
|
+
{
|
|
315
|
+
if (!File.Exists(entryPath))
|
|
316
|
+
{
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try
|
|
321
|
+
{
|
|
322
|
+
return JsonSerializer.Deserialize<CacheEntry>(File.ReadAllText(entryPath), Json);
|
|
323
|
+
}
|
|
324
|
+
catch (JsonException)
|
|
325
|
+
{
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// <summary>O que o <c>entry.json</c> carrega. Sem caminho absoluto nenhum: a árvore é commitada.</summary>
|
|
331
|
+
internal sealed record CacheEntry
|
|
332
|
+
{
|
|
333
|
+
[JsonPropertyName("scenarioName")]
|
|
334
|
+
public required string ScenarioName { get; init; }
|
|
335
|
+
|
|
336
|
+
[JsonPropertyName("iterationName")]
|
|
337
|
+
public required string IterationName { get; init; }
|
|
338
|
+
|
|
339
|
+
/// <summary>A chave INTEIRA. É ela que torna o prefixo truncado seguro.</summary>
|
|
340
|
+
[JsonPropertyName("key")]
|
|
341
|
+
public required string Key { get; init; }
|
|
342
|
+
|
|
343
|
+
[JsonPropertyName("creation")]
|
|
344
|
+
public required DateTimeOffset Creation { get; init; }
|
|
345
|
+
|
|
346
|
+
[JsonPropertyName("expiration")]
|
|
347
|
+
public required DateTimeOffset Expiration { get; init; }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
namespace Morph.AiKit.Media;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// <b>O UNICO ponto de entrada publico da geracao de midia.</b>
|
|
5
|
+
/// </summary>
|
|
6
|
+
/// <remarks>
|
|
7
|
+
/// <para>
|
|
8
|
+
/// Leia as assinaturas: nenhuma delas devolve bytes, stream, <c>BinaryData</c> ou URL de
|
|
9
|
+
/// provider. <see cref="EnqueueAsync"/> devolve um <see cref="MediaJobId"/> e
|
|
10
|
+
/// <see cref="GetAsync"/> devolve o <see cref="MediaJob"/> com o estado dele. Isso e
|
|
11
|
+
/// deliberado, e e o mecanismo — nao a explicacao — da regra dura de
|
|
12
|
+
/// <c>ai-agents/media-pipeline.md</c>:
|
|
13
|
+
/// </para>
|
|
14
|
+
/// <para>
|
|
15
|
+
/// <b>Nunca gere midia dentro de um turno de conversa.</b> Latencia documentada de ate 2
|
|
16
|
+
/// minutos em imagem e de 11 s a 6 min em video, custo por chamada, e zero revisao possivel
|
|
17
|
+
/// dentro do turno. O turno enfileira e confirma; a midia volta depois, revisada.
|
|
18
|
+
/// </para>
|
|
19
|
+
/// <para>
|
|
20
|
+
/// O tipo que efetivamente produz bytes (<c>IMediaProvider</c> e os tres adaptadores de
|
|
21
|
+
/// provider) e <c>internal</c> ao assembly, com <c>InternalsVisibleTo</c> liberando apenas
|
|
22
|
+
/// <c>Morph.AiKit.Tests</c>. Um handler de mensagem no projeto consumidor <b>nao consegue
|
|
23
|
+
/// nomear</b> esse tipo: o codigo nao compila. E a diferenca entre "o standard diz para nao
|
|
24
|
+
/// fazer" e "o compilador recusa", e ha um teste de acessibilidade por reflexao que a
|
|
25
|
+
/// mantem verdadeira.
|
|
26
|
+
/// </para>
|
|
27
|
+
/// </remarks>
|
|
28
|
+
public interface IMediaGenerator
|
|
29
|
+
{
|
|
30
|
+
/// <summary>
|
|
31
|
+
/// Aceita o pedido e devolve o id do job. <b>Nunca devolve bytes.</b>
|
|
32
|
+
/// </summary>
|
|
33
|
+
/// <remarks>
|
|
34
|
+
/// A chave de idempotencia de PEDIDO e consultada aqui, antes de qualquer chamada paga:
|
|
35
|
+
/// um pedido identico a outro ja atendido reusa o job existente em vez de pagar de
|
|
36
|
+
/// novo. O teto de custo tambem recusa aqui — depois da chamada, um alerta chega tarde.
|
|
37
|
+
/// </remarks>
|
|
38
|
+
Task<MediaResult<MediaJobId>> EnqueueAsync(MediaRequest request, CancellationToken cancellationToken = default);
|
|
39
|
+
|
|
40
|
+
/// <summary>O job, com estado, caminho e proveniencia. Job inexistente e recusa nomeada.</summary>
|
|
41
|
+
Task<MediaResult<MediaJob>> GetAsync(MediaJobId id, CancellationToken cancellationToken = default);
|
|
42
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
namespace Morph.AiKit.Media;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// O que PRODUZ BYTES. <b><c>internal</c> ao assembly, e e isso que prende a regra dura.</b>
|
|
5
|
+
/// </summary>
|
|
6
|
+
/// <remarks>
|
|
7
|
+
/// <para>
|
|
8
|
+
/// <c>ai-agents/media-pipeline.md</c> diz: <i>nunca gere midia dentro de um turno de
|
|
9
|
+
/// conversa</i>. Uma frase num standard nao prende nada — o que prende e este modificador.
|
|
10
|
+
/// </para>
|
|
11
|
+
/// <para>
|
|
12
|
+
/// Este tipo e os tres adaptadores de provider sao <c>internal</c>, com
|
|
13
|
+
/// <c>InternalsVisibleTo</c> liberando apenas <c>Morph.AiKit.Tests</c>. Um handler de
|
|
14
|
+
/// mensagem no projeto consumidor <b>nao consegue nomear</b> o tipo que produz bytes: o
|
|
15
|
+
/// codigo nao compila. A unica superficie publica e
|
|
16
|
+
/// <see cref="IMediaGenerator.EnqueueAsync"/>, que devolve um <see cref="MediaJobId"/>.
|
|
17
|
+
/// </para>
|
|
18
|
+
/// <para>
|
|
19
|
+
/// Um teste de acessibilidade por reflexao (<c>MediaAccessibilityTests</c>) mantem esta
|
|
20
|
+
/// afirmacao verdadeira: tornar o tipo publico derruba a suite. Um canario que ninguem viu
|
|
21
|
+
/// morder pode ser o canario errado, entao a mutacao foi rodada.
|
|
22
|
+
/// </para>
|
|
23
|
+
/// </remarks>
|
|
24
|
+
internal interface IMediaProvider
|
|
25
|
+
{
|
|
26
|
+
/// <summary>Familia do SDK: <c>openai</c>, <c>google</c>.</summary>
|
|
27
|
+
string Provider { get; }
|
|
28
|
+
|
|
29
|
+
/// <summary>Se este adaptador atende esse tipo de midia.</summary>
|
|
30
|
+
bool Supports(MediaKind kind);
|
|
31
|
+
|
|
32
|
+
/// <summary>
|
|
33
|
+
/// Produz os bytes. Bloqueio de moderacao volta como
|
|
34
|
+
/// <see cref="MediaErrorCode.PolicyBlocked"/> — terminal, nunca retryable; falha
|
|
35
|
+
/// tecnica volta como <see cref="MediaErrorCode.ProviderFailure"/>.
|
|
36
|
+
/// </summary>
|
|
37
|
+
/// <param name="request">O pedido, ja validado.</param>
|
|
38
|
+
/// <param name="binding">
|
|
39
|
+
/// O alias resolvido. Viaja como parametro, e nao no construtor, porque um adaptador
|
|
40
|
+
/// atende N aliases do mesmo provider e uma instancia por alias multiplicaria clientes
|
|
41
|
+
/// sem nenhum ganho.
|
|
42
|
+
/// </param>
|
|
43
|
+
/// <param name="cancellationToken">—</param>
|
|
44
|
+
Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
45
|
+
MediaRequest request,
|
|
46
|
+
MediaAliasBinding binding,
|
|
47
|
+
CancellationToken cancellationToken);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// <summary>
|
|
51
|
+
/// Constroi os clientes de SDK dos providers de midia. <b>A costura do subsistema</b>: o
|
|
52
|
+
/// unico ponto em que o kit toca um provider de verdade.
|
|
53
|
+
/// </summary>
|
|
54
|
+
/// <remarks>
|
|
55
|
+
/// <para>
|
|
56
|
+
/// Publica de proposito, e pela mesma razao de <c>IProviderClientFactory</c>: e assim que a
|
|
57
|
+
/// suite do kit roda <b>sem rede e sem chave de API</b> — em teste ninguem implementa esta
|
|
58
|
+
/// interface, porque os testes usam um <c>IMediaProvider</c> fake e nunca chegam aqui.
|
|
59
|
+
/// </para>
|
|
60
|
+
/// <para>
|
|
61
|
+
/// Devolver <c>null</c> significa "este provider nao e meu": o catalogo entao procura outro
|
|
62
|
+
/// adaptador, e se nenhum atender, o job falha com motivo nomeado em vez de silenciosamente
|
|
63
|
+
/// nao acontecer.
|
|
64
|
+
/// </para>
|
|
65
|
+
/// <para>
|
|
66
|
+
/// <b>Quem descarta o cliente e o projeto</b>, que o construiu. O kit nao guarda cache
|
|
67
|
+
/// propria de cliente — duas caches da mesma coisa e como um cliente vaza de um teste.
|
|
68
|
+
/// </para>
|
|
69
|
+
/// </remarks>
|
|
70
|
+
public interface IMediaClientFactory
|
|
71
|
+
{
|
|
72
|
+
/// <summary>O <c>ImageClient</c> do alias, ou nulo quando o provider nao e a OpenAI.</summary>
|
|
73
|
+
OpenAI.Images.ImageClient? CreateOpenAiImageClient(MediaAliasBinding binding);
|
|
74
|
+
|
|
75
|
+
/// <summary>O cliente do Google GenAI, ou nulo quando o provider nao e o Google.</summary>
|
|
76
|
+
Google.GenAI.Client? CreateGoogleClient(MediaAliasBinding binding);
|
|
77
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
namespace Morph.AiKit.Media;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// O que um provider produz: bytes, mais o que so o provider sabe sobre a geracao.
|
|
5
|
+
/// </summary>
|
|
6
|
+
/// <remarks>
|
|
7
|
+
/// <b>Este tipo nunca sai pela superficie publica do subsistema.</b> Ele existe entre o
|
|
8
|
+
/// <c>IMediaProvider</c> (internal) e o worker (internal); o que chega ao projeto e um
|
|
9
|
+
/// <see cref="MediaJob"/> com caminho e hash. Se um handler consegue segurar um
|
|
10
|
+
/// <see cref="MediaArtifact"/>, a regra dura de ai-agents/media-pipeline.md foi furada.
|
|
11
|
+
/// </remarks>
|
|
12
|
+
/// <param name="Kind">Imagem ou video.</param>
|
|
13
|
+
/// <param name="MediaType">MIME dos bytes devolvidos pelo provider.</param>
|
|
14
|
+
/// <param name="Bytes">O conteudo. Baixado JA, nunca uma URL guardada para depois.</param>
|
|
15
|
+
/// <param name="RevisedPrompt">O prompt que o provider reescreveu, quando ele devolve um.</param>
|
|
16
|
+
/// <param name="ResponseId">O id do lado do provider, para abrir chamado.</param>
|
|
17
|
+
/// <param name="C2paPreserved">
|
|
18
|
+
/// Se os metadados de proveniencia do provider sobreviveram ao caminho ate aqui. Falso
|
|
19
|
+
/// quando o adaptador re-encodou os bytes — e um re-encode destrutivo apaga a credencial
|
|
20
|
+
/// C2PA, que e a unica prova tecnica de origem que a imagem carregava.
|
|
21
|
+
/// </param>
|
|
22
|
+
internal sealed record MediaArtifact(
|
|
23
|
+
MediaKind Kind,
|
|
24
|
+
string MediaType,
|
|
25
|
+
ReadOnlyMemory<byte> Bytes,
|
|
26
|
+
string? RevisedPrompt,
|
|
27
|
+
string? ResponseId,
|
|
28
|
+
bool C2paPreserved);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
namespace Morph.AiKit.Media;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// A marcação "gerado/editado por IA" que acompanha a publicação.
|
|
5
|
+
/// </summary>
|
|
6
|
+
/// <remarks>
|
|
7
|
+
/// <para>
|
|
8
|
+
/// <b>Este tipo existe porque a afirmação estava só na prosa.</b> Antes dele,
|
|
9
|
+
/// <see cref="MediaJobState.Published"/> dizia "publicado, com a marcacao aplicada" e
|
|
10
|
+
/// <see cref="MediaPurpose.Marketing"/> dizia "marcacao visivel obrigatoria" — e um
|
|
11
|
+
/// <c>grep</c> por label, watermark ou disclosure no kit inteiro voltava <b>vazio</b>. Três
|
|
12
|
+
/// das quatro afirmações que a rubrica <c>media-claims</c> cobra de terceiros estavam presas
|
|
13
|
+
/// por tipo ou por estado; a quarta, que é a obrigação legal, estava presa por nada.
|
|
14
|
+
/// </para>
|
|
15
|
+
/// <para>
|
|
16
|
+
/// <b>O construtor é PRIVADO, e isso não é estilo.</b> A primeira versão deste tipo era um
|
|
17
|
+
/// record POSICIONAL — e um record posicional gera um construtor <b>público</b>. A validação
|
|
18
|
+
/// morava só em <see cref="Create"/>, então <c>new MediaDisclosure("", "", at)</c> compilava,
|
|
19
|
+
/// era aceito, e publicava com rótulo vazio. Medido de um consumidor externo:
|
|
20
|
+
/// <c>IsSuccess=True State=Published</c>, <c>Text='' Surface=''</c>. A prosa dizia "recusado na
|
|
21
|
+
/// construção" em quatro superfícies; a construção não recusava nada. Uma marcação vazia é
|
|
22
|
+
/// exatamente o campo preenchido "para passar" que este tipo existe para impedir, e agora é o
|
|
23
|
+
/// <b>compilador</b> que fecha a porta — não a documentação. Mesma forma de
|
|
24
|
+
/// <see cref="MediaReview"/>, três arquivos ao lado.
|
|
25
|
+
/// </para>
|
|
26
|
+
/// <para>
|
|
27
|
+
/// <b>O que este tipo prova, exatamente:</b> que um job <b>não alcança</b>
|
|
28
|
+
/// <see cref="MediaJobState.Published"/> sem uma marcação registrada e não-vazia, quando a
|
|
29
|
+
/// finalidade a exige. É o mesmo mecanismo de <see cref="MediaJob.Approve"/> — uma transição
|
|
30
|
+
/// que o código não tem, em vez de uma frase que alguém tem de lembrar.
|
|
31
|
+
/// </para>
|
|
32
|
+
/// <para>
|
|
33
|
+
/// <b>O que ele NÃO prova, e é honesto dizer:</b> que o rótulo chegou aos olhos de alguém. A
|
|
34
|
+
/// renderização é do aplicativo consumidor, e nenhum tipo em C# alcança um pixel. O que o kit
|
|
35
|
+
/// garante é que a decisão foi tomada, com texto e superfície nomeados, e ficou gravada com o
|
|
36
|
+
/// artefato — de modo que "esquecemos de marcar" deixa de ser um caminho silencioso e passa a
|
|
37
|
+
/// ser uma recusa na publicação.
|
|
38
|
+
/// </para>
|
|
39
|
+
/// </remarks>
|
|
40
|
+
public sealed record MediaDisclosure
|
|
41
|
+
{
|
|
42
|
+
private MediaDisclosure(string text, string surface, DateTimeOffset appliedAt)
|
|
43
|
+
{
|
|
44
|
+
Text = text;
|
|
45
|
+
Surface = surface;
|
|
46
|
+
AppliedAt = appliedAt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// <summary>
|
|
50
|
+
/// O texto visível, por extenso. Em branco é recusado — e a recusa é presa pelo construtor
|
|
51
|
+
/// privado, não por convenção de chamada.
|
|
52
|
+
/// </summary>
|
|
53
|
+
public string Text { get; }
|
|
54
|
+
|
|
55
|
+
/// <summary>
|
|
56
|
+
/// ONDE o rótulo aparece — legenda, sobreposição, texto alternativo, ficha do produto. Sem
|
|
57
|
+
/// isto, "marcado" não diz se alguém consegue ver.
|
|
58
|
+
/// </summary>
|
|
59
|
+
public string Surface { get; }
|
|
60
|
+
|
|
61
|
+
/// <summary>Quando a marcação foi decidida.</summary>
|
|
62
|
+
public DateTimeOffset AppliedAt { get; }
|
|
63
|
+
|
|
64
|
+
/// <summary>
|
|
65
|
+
/// A ÚNICA forma de obter uma marcação. Recusa texto ou superfície em branco.
|
|
66
|
+
/// </summary>
|
|
67
|
+
/// <exception cref="ArgumentException">Texto ou superfície vazios.</exception>
|
|
68
|
+
public static MediaDisclosure Create(string text, string surface, DateTimeOffset appliedAt)
|
|
69
|
+
{
|
|
70
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(text);
|
|
71
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(surface);
|
|
72
|
+
|
|
73
|
+
return new MediaDisclosure(text, surface, appliedAt);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// <summary>Política de marcação por finalidade. Escrita uma vez, aqui.</summary>
|
|
78
|
+
public static class MediaPurposeExtensions
|
|
79
|
+
{
|
|
80
|
+
/// <summary>
|
|
81
|
+
/// Se a finalidade exige marcação visível para publicar.
|
|
82
|
+
/// </summary>
|
|
83
|
+
/// <remarks>
|
|
84
|
+
/// <para>
|
|
85
|
+
/// <b>Catálogo e marketing exigem; rascunho não publica.</b> A regra do art. 50 do AI Act
|
|
86
|
+
/// mira conteúdo que <i>possa parecer autêntico</i> sobre produto, pessoa ou lugar — e é
|
|
87
|
+
/// exatamente o que catálogo e peça de campanha são. A leitura conservadora é do standard
|
|
88
|
+
/// <c>ai-agents/media-pipeline.md</c>, seção Marcação, com a ressalva de fonte que ele
|
|
89
|
+
/// carrega: este predicado é uma decisão de desenho, não parecer jurídico.
|
|
90
|
+
/// </para>
|
|
91
|
+
/// <para>
|
|
92
|
+
/// Não confunda com <see cref="IsPublishable"/>: um rascunho não chega a publicar, então
|
|
93
|
+
/// a pergunta "precisa de marcação" nem se coloca para ele.
|
|
94
|
+
/// </para>
|
|
95
|
+
/// </remarks>
|
|
96
|
+
public static bool RequiresDisclosure(this MediaPurpose purpose)
|
|
97
|
+
=> purpose is MediaPurpose.CatalogPhoto or MediaPurpose.Marketing;
|
|
98
|
+
|
|
99
|
+
/// <summary>
|
|
100
|
+
/// Se a finalidade pode chegar a <see cref="MediaJobState.Published"/>.
|
|
101
|
+
/// </summary>
|
|
102
|
+
/// <remarks>
|
|
103
|
+
/// <see cref="MediaPurpose.Draft"/> se declara "nunca publicável" desde o primeiro dia — e
|
|
104
|
+
/// até este tipo existir, isso era só um comentário XML. Agora é uma recusa.
|
|
105
|
+
/// </remarks>
|
|
106
|
+
public static bool IsPublishable(this MediaPurpose purpose) => purpose != MediaPurpose.Draft;
|
|
107
|
+
}
|