@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.
Files changed (328) hide show
  1. package/CLAUDE.md +22 -5
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +18 -4
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +5 -4
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/MORPH.md +16 -17
  9. package/framework/agents.json +197 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +236 -11
  13. package/framework/evals/proof-rubrics/README.md +76 -0
  14. package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
  15. package/framework/evals/proof-rubrics/fiacao.md +54 -0
  16. package/framework/evals/proof-rubrics/guard-cego.md +52 -0
  17. package/framework/evals/proof-rubrics/media-claims.md +109 -0
  18. package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
  19. package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
  20. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
  21. package/framework/evals/rubrics/architecture.md +30 -0
  22. package/framework/evals/rubrics/code-quality.md +26 -0
  23. package/framework/evals/rubrics/test-coverage.md +20 -0
  24. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  25. package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
  26. package/framework/hooks/claude-code/statusline.sh +11 -11
  27. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  28. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  29. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  30. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  31. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  32. package/framework/hooks/shared/e2e-check.js +135 -22
  33. package/framework/hooks/shared/payload-utils.js +15 -2
  34. package/framework/hooks/shared/stdin-reader.js +26 -26
  35. package/framework/rules/ai-agents-standards.md +14 -2
  36. package/framework/schemas/ai-pin.schema.json +97 -0
  37. package/framework/schemas/config.schema.json +107 -0
  38. package/framework/schemas/feature.schema.json +23 -4
  39. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  40. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  41. package/framework/skills/core/morph-apply/SKILL.md +26 -5
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  45. package/framework/skills/core/morph-implement/SKILL.md +18 -2
  46. package/framework/skills/core/morph-media/SKILL.md +215 -0
  47. package/framework/skills/core/morph-plan/SKILL.md +33 -2
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +166 -9
  50. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  51. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  52. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  53. package/framework/standards/PROVENANCE.json +81 -3
  54. package/framework/standards/STANDARDS.json +1483 -839
  55. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  56. package/framework/standards/ai-agents/agent-session.md +164 -68
  57. package/framework/standards/ai-agents/agent-spec.md +196 -0
  58. package/framework/standards/ai-agents/batching.md +61 -37
  59. package/framework/standards/ai-agents/context-providers.md +97 -24
  60. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  61. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  62. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  63. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  64. package/framework/standards/ai-agents/guardrails.md +222 -0
  65. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  66. package/framework/standards/ai-agents/mcp-server.md +119 -18
  67. package/framework/standards/ai-agents/mcp-tools.md +85 -32
  68. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  69. package/framework/standards/ai-agents/media-video.md +267 -0
  70. package/framework/standards/ai-agents/middleware-patterns.md +39 -5
  71. package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
  72. package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
  73. package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
  74. package/framework/standards/ai-agents/modalities-vision.md +6 -2
  75. package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
  76. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  77. package/framework/standards/ai-agents/production.md +38 -9
  78. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  79. package/framework/standards/ai-agents/providers/model-registry.md +174 -39
  80. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  81. package/framework/standards/ai-agents/service-tier-flex.md +27 -19
  82. package/framework/standards/ai-agents/setup.md +166 -50
  83. package/framework/standards/ai-agents/structured-output.md +34 -3
  84. package/framework/standards/ai-agents/sweet-spot.md +23 -3
  85. package/framework/standards/ai-agents/testing-ai.md +236 -0
  86. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  87. package/framework/standards/ai-agents/workflows.md +27 -7
  88. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  89. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  90. package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
  91. package/framework/templates/.idea/morph-templates.xml +92 -0
  92. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  93. package/framework/templates/IDE-SNIPPETS.md +262 -262
  94. package/framework/templates/REGISTRY.json +13 -0
  95. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  96. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  97. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  98. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  99. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
  100. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  101. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  102. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  103. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  104. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  105. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  106. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  107. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  108. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  109. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
  110. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  111. package/framework/templates/code/typescript/contracts.ts +167 -167
  112. package/framework/templates/docs/clarifications.md +253 -253
  113. package/framework/templates/docs/schema-analysis.md +119 -119
  114. package/framework/templates/docs/ui-components.md +52 -52
  115. package/framework/templates/docs/ui-design-system.md +76 -76
  116. package/framework/templates/docs/ui-flows.md +167 -167
  117. package/framework/templates/docs/ui-mockups.md +98 -98
  118. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  119. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  120. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  121. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  122. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  123. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  157. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  158. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  159. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  160. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  161. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  162. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  163. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  164. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  165. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  166. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  167. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  168. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  169. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  170. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  171. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  172. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  173. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  174. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  175. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  176. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  203. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  204. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  205. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  206. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  207. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  208. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  209. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  210. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  211. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  212. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  213. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  214. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  215. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  216. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  217. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  218. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  219. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  220. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  221. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  222. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  223. package/framework/templates/feature/decisions.md +122 -122
  224. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  225. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  226. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  227. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  228. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  229. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  230. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  231. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  232. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  233. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  234. package/framework/templates/infrastructure/github/README.md +56 -56
  235. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  236. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  237. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  238. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  239. package/framework/templates/integrations/asaas-client.cs +387 -387
  240. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  241. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  242. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  243. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  244. package/framework/templates/project/validate.js +120 -120
  245. package/framework/templates/state.template.json +17 -17
  246. package/framework/templates/ui/design-system.css +226 -226
  247. package/package.json +3 -3
  248. package/src/commands/dashboard/dashboard.js +53 -53
  249. package/src/commands/e2e/e2e.js +15 -2
  250. package/src/commands/index.js +13 -13
  251. package/src/commands/project/checkpoint.js +209 -209
  252. package/src/commands/project/doctor.js +148 -6
  253. package/src/commands/project/update.js +469 -153
  254. package/src/commands/project/worktree.js +76 -4
  255. package/src/commands/state/advance.js +24 -2
  256. package/src/commands/state/approve.js +33 -1
  257. package/src/commands/state/create.js +72 -11
  258. package/src/commands/state/finish.js +379 -24
  259. package/src/commands/state/gate-check.js +190 -6
  260. package/src/commands/state/score.js +36 -1
  261. package/src/commands/validation/verify.js +134 -25
  262. package/src/core/state/phase-state-machine.js +197 -197
  263. package/src/core/state/state-manager.js +9 -1
  264. package/src/core/templates/template-renderer.js +477 -477
  265. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  266. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  267. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  268. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  269. package/src/dashboard/app/main.jsx +13 -13
  270. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  271. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  272. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  273. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  274. package/src/dashboard/app/pages/Standards.jsx +332 -332
  275. package/src/dashboard/app/styles/index.css +47 -47
  276. package/src/dashboard/index.html +12 -12
  277. package/src/dashboard/package.json +24 -24
  278. package/src/dashboard/scanner/phases-scanner.js +15 -15
  279. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  280. package/src/dashboard/scanner/rules-scanner.js +37 -37
  281. package/src/dashboard/scanner/skills-scanner.js +43 -43
  282. package/src/dashboard/scanner/squads-scanner.js +13 -13
  283. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  284. package/src/dashboard/vite.config.js +24 -24
  285. package/src/lib/ai/ai-checks.js +182 -0
  286. package/src/lib/ai/ai-project-scan.js +347 -0
  287. package/src/lib/ai/code-audit.js +181 -0
  288. package/src/lib/ai/comment-stripper.js +184 -0
  289. package/src/lib/ai/pin-drift.js +138 -0
  290. package/src/lib/ai/registry-audit.js +285 -0
  291. package/src/lib/detectors/design-system-detector.js +188 -188
  292. package/src/lib/detectors/index.js +217 -217
  293. package/src/lib/e2e/compose-env.js +50 -4
  294. package/src/lib/evaluator-report.js +269 -15
  295. package/src/lib/generators/design-system-generator.js +508 -508
  296. package/src/lib/generators/settings-generator.js +209 -209
  297. package/src/lib/git/default-branch.js +92 -4
  298. package/src/lib/git/pr-files.js +56 -0
  299. package/src/lib/hooks/hook-executor.js +167 -167
  300. package/src/lib/installers/effects.js +193 -0
  301. package/src/lib/installers/mcp-installer.js +719 -719
  302. package/src/lib/installers/templates-reconcile.js +3 -3
  303. package/src/lib/risk-detector.js +284 -0
  304. package/src/lib/standards/verified-against.js +276 -0
  305. package/src/lib/tasks/eval-node.js +485 -0
  306. package/src/lib/tasks/linux-runner.js +297 -0
  307. package/src/lib/tasks/task-classifier.js +30 -30
  308. package/src/lib/tasks/task-parser.js +209 -209
  309. package/src/lib/tasks/test-nodes.js +486 -0
  310. package/src/lib/tasks/test-runner.js +410 -33
  311. package/src/lib/validators/checkpoint-persistence.js +39 -39
  312. package/src/lib/validators/css/css-validator.js +340 -340
  313. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  314. package/src/lib/validators/shared/issue-counter.js +14 -14
  315. package/src/lib/validators/verify-runner.js +583 -28
  316. package/src/lib/worktree/docker-audit.js +287 -0
  317. package/src/lib/worktree/env-allocator.js +24 -0
  318. package/src/lib/worktree/worktree-audit.js +30 -1
  319. package/src/scripts/setup-infra.js +24 -11
  320. package/src/utils/agent-id-mapper.js +11 -11
  321. package/src/utils/agents-installer.js +18 -8
  322. package/src/utils/claude-md-injector.js +10 -5
  323. package/src/utils/claude-settings-manager.js +300 -300
  324. package/src/utils/file-copier.js +11 -4
  325. package/src/utils/hooks-installer.js +29 -26
  326. package/src/utils/skills-installer.js +36 -29
  327. package/src/utils/version-checker.js +68 -3
  328. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -0,0 +1,194 @@
1
+ using System.Security.Cryptography;
2
+ using Microsoft.Extensions.AI.Evaluation.Quality;
3
+ using Morph.AiKit.Evals;
4
+ using Morph.AiKit.Tests.Fakes;
5
+ using Xunit;
6
+
7
+ namespace Morph.AiKit.Tests.Evals;
8
+
9
+ /// <summary>
10
+ /// Regravacao, e o invariante barato que protege a premissa de commitabilidade:
11
+ /// <b>uma leitura nao escreve um byte na arvore de cache</b>.
12
+ /// </summary>
13
+ public sealed class EvalRefreshTests
14
+ {
15
+ /// <summary>
16
+ /// Impressao digital da arvore: caminho relativo mais SHA-256 de cada arquivo.
17
+ /// </summary>
18
+ /// <remarks>
19
+ /// Comparar CONTEUDO, e nao data de modificacao: o que importa e se o proximo
20
+ /// <c>git status</c> mostra diff, e o git tambem olha conteudo. Um teste sobre mtime
21
+ /// falharia por gravacao identica e passaria por gravacao diferente no mesmo tick.
22
+ /// </remarks>
23
+ private static IReadOnlyList<string> Fingerprint(string root)
24
+ {
25
+ if (!Directory.Exists(root))
26
+ {
27
+ return [];
28
+ }
29
+
30
+ return [.. Directory
31
+ .EnumerateFiles(root, "*", SearchOption.AllDirectories)
32
+ .OrderBy(p => p.Replace('\\', '/'), StringComparer.Ordinal)
33
+ .Select(p => $"{Path.GetRelativePath(root, p).Replace('\\', '/')} {Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(p)))}")];
34
+ }
35
+
36
+ private static void WriteThreeScenarios(EvalWorkspace ws, FakeChatClient fake)
37
+ {
38
+ foreach (var name in new[] { "a-primeiro", "b-segundo", "c-terceiro" })
39
+ {
40
+ ws.WriteScenario("triagem", "acme", name, $"pergunta {name}", """[ { "kind": "contains", "value": "resposta" } ]""");
41
+ fake.EnqueueText($"resposta {name}");
42
+ }
43
+ }
44
+
45
+ [Fact]
46
+ public async Task AReadWithAWarmCacheLeavesTheTreeBYTEForBYTEIdentical()
47
+ {
48
+ using var ws = new EvalWorkspace();
49
+ using var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" };
50
+ WriteThreeScenarios(ws, fake);
51
+
52
+ await using (var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake))
53
+ {
54
+ await refresh.RunAllAsync(TestContext.Current.CancellationToken);
55
+ }
56
+
57
+ var before = Fingerprint(ws.CacheRoot);
58
+ Assert.NotEmpty(before);
59
+
60
+ await using (var offline = EvalFixture.Create(ws.Options(EvalMode.Offline)))
61
+ {
62
+ var summary = await offline.RunAllAsync(TestContext.Current.CancellationToken);
63
+ Assert.Equal(3, summary.Passed);
64
+ }
65
+
66
+ // Se algum dia a biblioteca passar a tocar disco no caminho de HIT, este canario morde
67
+ // no dia em que isso acontecer — e nao meses depois, quando alguem estranhar um diff
68
+ // que aparece sozinho em toda rodada.
69
+ Assert.Equal(before, Fingerprint(ws.CacheRoot));
70
+ }
71
+
72
+ [Fact]
73
+ public async Task ExecutionResultsLandOUTSIDETheCommittedCacheTree()
74
+ {
75
+ using var ws = new EvalWorkspace();
76
+ using var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" };
77
+ WriteThreeScenarios(ws, fake);
78
+
79
+ await using var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
80
+ await refresh.RunAllAsync(TestContext.Current.CancellationToken);
81
+
82
+ // Os resultados carregam latencia de cronometro, uso de tokens e o flag de cache hit:
83
+ // commita-los seria diff garantido em toda rodada, sem sinal nenhum. Por CONSTRUCAO
84
+ // (outra raiz), nunca por um .gitignore dentro de um diretorio commitado.
85
+ Assert.True(Directory.Exists(ws.ResultsRoot), "o store de resultados tem de existir depois de um run");
86
+ Assert.NotEmpty(Directory.EnumerateFiles(ws.ResultsRoot, "*", SearchOption.AllDirectories));
87
+
88
+ var full = Path.GetFullPath(ws.CacheRoot).Replace('\\', '/');
89
+ foreach (var file in Directory.EnumerateFiles(ws.ResultsRoot, "*", SearchOption.AllDirectories))
90
+ {
91
+ Assert.DoesNotContain(full, Path.GetFullPath(file).Replace('\\', '/'), StringComparison.OrdinalIgnoreCase);
92
+ }
93
+ }
94
+
95
+ [Fact]
96
+ public async Task RefreshRewritesTheTreeAndCountsEveryScenarioAsAProviderCall()
97
+ {
98
+ using var ws = new EvalWorkspace();
99
+ using var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" };
100
+ WriteThreeScenarios(ws, fake);
101
+
102
+ await using var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
103
+ var summary = await refresh.RunAllAsync(TestContext.Current.CancellationToken);
104
+
105
+ // O contador do cliente real e o que torna "a cache esta fria" uma MEDICAO: a arvore
106
+ // mudou e precisa ser commitada, e isso e afirmacao com consequencia.
107
+ Assert.Equal(3, summary.Misses);
108
+ Assert.Equal(EvalCacheState.Cold, summary.Cache);
109
+ Assert.Equal(3, fake.CallCount);
110
+ }
111
+
112
+ [Fact]
113
+ public async Task ASecondRefreshZeroesTheTreeInsteadOfLayeringOverIt()
114
+ {
115
+ using var ws = new EvalWorkspace();
116
+
117
+ using (var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" })
118
+ {
119
+ WriteThreeScenarios(ws, fake);
120
+ await using var first = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
121
+ await first.RunAllAsync(TestContext.Current.CancellationToken);
122
+ }
123
+
124
+ // O cenario do meio some do dataset. Uma regravacao PARCIAL deixaria a entrada antiga
125
+ // dele viva na arvore, e a proxima leitura offline nao teria como saber que ela nao
126
+ // corresponde mais a nenhum cenario declarado.
127
+ File.Delete(Path.Combine(ws.DatasetRoot, "triagem", "acme", "b-segundo.json"));
128
+
129
+ using (var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" })
130
+ {
131
+ fake.EnqueueText("resposta a-primeiro").EnqueueText("resposta c-terceiro");
132
+ await using var second = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
133
+ await second.RunAllAsync(TestContext.Current.CancellationToken);
134
+ }
135
+
136
+ var survivors = Fingerprint(ws.CacheRoot);
137
+ Assert.DoesNotContain(survivors, entry => entry.Contains("b-segundo", StringComparison.Ordinal));
138
+ Assert.Contains(survivors, entry => entry.Contains("a-primeiro", StringComparison.Ordinal));
139
+ }
140
+
141
+ [Fact]
142
+ public async Task QualityEvaluatorsAreWiredAsREPORTEDMetrics_NeverAsTheGate()
143
+ {
144
+ using var ws = new EvalWorkspace();
145
+ using var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" };
146
+
147
+ // A lista vem do KIT, e nao do teste: e ela que da ao pacote .Quality cobertura de
148
+ // COMPILACAO em src/ (sem um using real la, o pin daquele pacote teria so contrato de
149
+ // versao, e um rename de API nao reprovaria PR nenhuma).
150
+ var quality = EvalQualityEvaluators.Stable();
151
+
152
+ await using var fixture = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake, quality);
153
+
154
+ // A superficie existe e COMPILA contra as versoes do pin — que e o que um kit de
155
+ // template tem de garantir. O que ela NAO faz e decidir: o veredito de um cenario sai
156
+ // das assercoes deterministicas, e uma metrica de juizo entra no sumario como numero.
157
+ // Sem isso, a variancia de LLM reprovaria PRs e o no de eval nasceria destinado a ser
158
+ // desligado do CI.
159
+ Assert.Equal(2, quality.Count);
160
+ Assert.All(quality, evaluator => Assert.NotEmpty(evaluator.EvaluationMetricNames));
161
+ Assert.Empty(fixture.Summary.Quality);
162
+
163
+ // Uma lista NOVA a cada chamada: um array compartilhado seria estado global
164
+ // disfarcado de conveniencia, e a ReportingConfiguration guarda o que recebe.
165
+ Assert.NotSame(quality, EvalQualityEvaluators.Stable());
166
+ }
167
+
168
+ [Theory]
169
+ [InlineData("Microsoft.Extensions.AI.Evaluation.Quality.RelevanceEvaluator", false)]
170
+ [InlineData("Microsoft.Extensions.AI.Evaluation.Quality.CompletenessEvaluator", false)]
171
+ [InlineData("Microsoft.Extensions.AI.Evaluation.Quality.TaskAdherenceEvaluator", true)]
172
+ [InlineData("Microsoft.Extensions.AI.Evaluation.Quality.ToolCallAccuracyEvaluator", true)]
173
+ public void WhichQualityEvaluatorsAreExperimentalIsMEASURED_NotRemembered(string typeName, bool experimental)
174
+ {
175
+ // O plano descrevia os QUATRO como estaveis. Dois nao sao: eles carregam
176
+ // [Experimental("AIEVAL001")] na 10.9.0, e usa-los sob TreatWarningsAsErrors exigiria
177
+ // suprimir um diagnostico — uma decisao consciente, nunca um efeito colateral. Se um
178
+ // dia a Microsoft estabilizar (ou marcar) algum deles, este teste morre e a escolha e
179
+ // rediscutida com o fato na mao, em vez de envelhecer em silencio num comentario.
180
+ var type = typeof(RelevanceEvaluator).Assembly.GetType(typeName);
181
+ Assert.NotNull(type);
182
+
183
+ var attribute = type!
184
+ .GetCustomAttributes(typeof(System.Diagnostics.CodeAnalysis.ExperimentalAttribute), inherit: false)
185
+ .Cast<System.Diagnostics.CodeAnalysis.ExperimentalAttribute>()
186
+ .SingleOrDefault();
187
+
188
+ Assert.Equal(experimental, attribute is not null);
189
+ if (experimental)
190
+ {
191
+ Assert.Equal("AIEVAL001", attribute!.DiagnosticId);
192
+ }
193
+ }
194
+ }
@@ -0,0 +1,86 @@
1
+ using Morph.AiKit.Evals;
2
+
3
+ namespace Morph.AiKit.Tests.Evals;
4
+
5
+ /// <summary>
6
+ /// Um diretorio temporario com dataset, cache e resultados — a montagem que todo teste de
7
+ /// eval precisa, escrita uma vez.
8
+ /// </summary>
9
+ /// <remarks>
10
+ /// Ele existe para que os testes falem de COMPORTAMENTO e nao de caminhos. E cada instancia
11
+ /// tem raiz propria (GUID v7): dois testes rodando lado a lado nao podem disputar a mesma
12
+ /// arvore de cache, senao a falha aparece como flake e nao como defeito.
13
+ /// </remarks>
14
+ internal sealed class EvalWorkspace : IDisposable
15
+ {
16
+ public EvalWorkspace()
17
+ {
18
+ Root = Path.Combine(Path.GetTempPath(), $"morph-eval-{Guid.CreateVersion7():N}");
19
+ Directory.CreateDirectory(CacheRoot);
20
+ Directory.CreateDirectory(DatasetRoot);
21
+ }
22
+
23
+ public string Root { get; }
24
+
25
+ public string CacheRoot => Path.Combine(Root, "eval-cache");
26
+
27
+ public string DatasetRoot => Path.Combine(Root, "eval-datasets");
28
+
29
+ public string ResultsRoot => Path.Combine(Root, "eval-results");
30
+
31
+ public string SummaryPath => Path.Combine(Root, "summary.json");
32
+
33
+ /// <summary>O pin exemplar: a data e as versoes sob as quais a arvore diz ter sido gravada.</summary>
34
+ public static EvalPin Pin { get; } = new(
35
+ "2026-09-08",
36
+ new Dictionary<string, string>(StringComparer.Ordinal)
37
+ {
38
+ ["Microsoft.Extensions.AI"] = "10.9.0",
39
+ ["Microsoft.Extensions.AI.Evaluation"] = "10.9.0",
40
+ });
41
+
42
+ /// <summary>Escreve um cenario no dataset e devolve o caminho do arquivo.</summary>
43
+ public string WriteScenario(string agent, string tenant, string name, string userText, string assertions)
44
+ {
45
+ var dir = Path.Combine(DatasetRoot, agent, tenant);
46
+ Directory.CreateDirectory(dir);
47
+ var file = Path.Combine(dir, $"{name}.json");
48
+ File.WriteAllText(file, $$"""
49
+ {
50
+ "messages": [ { "role": "user", "text": {{System.Text.Json.JsonSerializer.Serialize(userText)}} } ],
51
+ "assertions": {{assertions}}
52
+ }
53
+ """);
54
+ return file;
55
+ }
56
+
57
+ /// <summary>Opcoes prontas para um modo, com TTL longo e o pin exemplar.</summary>
58
+ public EvalCacheOptions Options(EvalMode mode, EvalPin? pin = null) => new()
59
+ {
60
+ CacheRoot = CacheRoot,
61
+ DatasetRoot = DatasetRoot,
62
+ ResultsRoot = ResultsRoot,
63
+ TimeToLive = TimeSpan.FromDays(3650),
64
+ ProviderName = "morph-fake",
65
+ DefaultModelId = "modelo-exemplar",
66
+ Mode = mode,
67
+ SummaryPath = SummaryPath,
68
+ Pin = pin ?? Pin,
69
+ };
70
+
71
+ public void Dispose()
72
+ {
73
+ try
74
+ {
75
+ if (Directory.Exists(Root))
76
+ {
77
+ Directory.Delete(Root, recursive: true);
78
+ }
79
+ }
80
+ catch (IOException)
81
+ {
82
+ // Um temporario que resiste a remocao nao pode derrubar um teste que ja provou o
83
+ // que veio provar.
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,213 @@
1
+ using Morph.AiKit.Evals;
2
+ using Morph.AiKit.Tests.Fakes;
3
+ using Xunit;
4
+
5
+ namespace Morph.AiKit.Tests.Evals;
6
+
7
+ /// <summary>
8
+ /// Os evals DO PROPRIO KIT, contra a arvore de cache COMMITADA — sem credencial, sem rede.
9
+ /// </summary>
10
+ /// <remarks>
11
+ /// <para>
12
+ /// Esta classe e o exemplar que um projeto copia. Ela roda com a categoria <c>Eval</c>, le a
13
+ /// cache commitada em <c>eval-cache/</c> e o dataset versionado em <c>eval-datasets/</c>, e
14
+ /// termina verde com TODAS as variaveis de credencial ausentes. Se algum cenario nao estiver
15
+ /// na cache, ela fica VERMELHA nomeando o cenario — nunca <c>skip</c>.
16
+ /// </para>
17
+ /// <para>
18
+ /// <b>A cache foi semeada por FAKE, jamais por chamada paga</b> (ver <see cref="SeedTheCommittedCacheTree"/>).
19
+ /// O caminho de ESCRITA exercitado e o da biblioteca e a chave e computada pela mesma funcao
20
+ /// que a leitura usa — entao a leitura offline le exatamente o que aquela escrita produziu.
21
+ /// A lacuna residual, declarada em voz alta: isto NAO prova que uma cache gravada contra um
22
+ /// provider real e legivel offline. O que a mitiga e um fato medido — a chave depende de
23
+ /// <c>ProviderName</c> e <c>DefaultModelId</c>, e nao do endpoint, e os dois estao sob nosso
24
+ /// controle no cliente offline.
25
+ /// </para>
26
+ /// </remarks>
27
+ public sealed class KitEvals
28
+ {
29
+ /// <summary>Identidade sob a qual a arvore commitada foi gravada. Ela ENTRA NA CHAVE.</summary>
30
+ private const string Provider = "morph-fake";
31
+
32
+ /// <summary>Idem. Trocar qualquer um dos dois invalida a arvore inteira.</summary>
33
+ private const string Model = "morph-kit-exemplar";
34
+
35
+ /// <summary>
36
+ /// O diretorio do PROJETO de teste, achado subindo a partir do assembly.
37
+ /// </summary>
38
+ /// <remarks>
39
+ /// De proposito: a alternativa seria copiar a cache para <c>bin/</c> e ler a copia — e ai
40
+ /// o canario de "um run offline nao suja a arvore commitada" seria verdadeiro por
41
+ /// vacuidade, porque a arvore commitada nem teria sido tocada.
42
+ /// </remarks>
43
+ private static string ProjectDirectory
44
+ {
45
+ get
46
+ {
47
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
48
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "Morph.AiKit.Tests.csproj")))
49
+ {
50
+ dir = dir.Parent;
51
+ }
52
+
53
+ return dir?.FullName ?? throw new DirectoryNotFoundException(
54
+ "nao foi possivel localizar Morph.AiKit.Tests.csproj subindo a partir de " + AppContext.BaseDirectory);
55
+ }
56
+ }
57
+
58
+ private static string CacheRoot => Path.Combine(ProjectDirectory, "eval-cache");
59
+
60
+ private static string DatasetRoot => Path.Combine(ProjectDirectory, "eval-datasets");
61
+
62
+ /// <summary>O pin sob o qual a arvore commitada foi gravada — o mesmo que o carimbo carrega.</summary>
63
+ private static EvalPin Pin => new(
64
+ "2026-09-08",
65
+ new Dictionary<string, string>(StringComparer.Ordinal)
66
+ {
67
+ ["Microsoft.Extensions.AI"] = "10.9.0",
68
+ ["Microsoft.Extensions.AI.Evaluation"] = "10.9.0",
69
+ ["Microsoft.Extensions.AI.Evaluation.Quality"] = "10.9.0",
70
+ ["Microsoft.Extensions.AI.Evaluation.Reporting"] = "10.9.0",
71
+ });
72
+
73
+ private static EvalCacheOptions Options(EvalMode mode, string? summaryPath) => new()
74
+ {
75
+ CacheRoot = CacheRoot,
76
+ DatasetRoot = DatasetRoot,
77
+ // FORA da arvore commitada: os resultados carregam latencia, uso de tokens e o flag
78
+ // de cache hit, e commita-los seria diff garantido em toda rodada.
79
+ ResultsRoot = Path.Combine(Path.GetTempPath(), "morph-kit-eval-results"),
80
+ TimeToLive = TimeSpan.FromDays(3650),
81
+ ProviderName = Provider,
82
+ DefaultModelId = Model,
83
+ Mode = mode,
84
+ SummaryPath = summaryPath,
85
+ Pin = Pin,
86
+ };
87
+
88
+ [Fact]
89
+ [Trait("Category", "Eval")]
90
+ public async Task TheKitEvalsRunGreenFromTheCommittedCacheWithNoCredentialAtAll()
91
+ {
92
+ // Nenhuma variavel de credencial e lida por nada nesta montagem — o cliente interno
93
+ // e o OfflineChatClient, que nao abre socket e nao le ambiente. Se a cache nao
94
+ // respondesse, este teste morreria com EvalCacheMissException nomeando o cenario.
95
+ var summaryPath = Path.Combine(Path.GetTempPath(), $"morph-kit-eval-{Guid.CreateVersion7():N}.json");
96
+
97
+ try
98
+ {
99
+ await using var fixture = EvalFixture.Create(Options(EvalMode.Offline, summaryPath));
100
+ var summary = await fixture.RunAllAsync(TestContext.Current.CancellationToken);
101
+
102
+ Assert.Equal(EvalCacheState.Warm, summary.Cache);
103
+ Assert.Equal("ok", summary.Stamp);
104
+
105
+ // NOMEANDO. `Assert.Equal(0, summary.Failed)` produzia "Expected 0, Actual 1" —
106
+ // verdadeiro e inutil: nao dizia qual cenario nem qual regua. A rubrica
107
+ // eval-de-modelo.md exige, na banda 9-10, derrubar o cenario NOMEANDO-O.
108
+ Assert.True(summary.Failed == 0, summary.DescribeFailures());
109
+ Assert.Empty(summary.Failures);
110
+ Assert.Equal(summary.Scenarios, summary.Passed);
111
+
112
+ // A prova central, e ela e um CONTADOR: o cliente interno e o unico caminho para
113
+ // a rede, e ele nao foi invocado nenhuma vez.
114
+ Assert.Equal(0, summary.Misses);
115
+ Assert.Equal(0, fixture.Offline!.Invocations);
116
+
117
+ // O dataset nao pode encolher em silencio: uma suite de eval que passa a avaliar
118
+ // menos cenarios e a mesma classe de defeito que uma baseline de teste caindo.
119
+ Assert.Equal(4, summary.Scenarios);
120
+ }
121
+ finally
122
+ {
123
+ if (File.Exists(summaryPath))
124
+ {
125
+ File.Delete(summaryPath);
126
+ }
127
+ }
128
+ }
129
+
130
+ [Fact]
131
+ [Trait("Category", "Eval")]
132
+ public void TheCommittedCacheCarriesTheStampOfThePinItWasWrittenUnder()
133
+ {
134
+ var stamp = EvalCacheStamp.Read(CacheRoot);
135
+
136
+ Assert.NotNull(stamp);
137
+ Assert.Equal("2026-09-08", stamp!.PinnedAt);
138
+ Assert.Equal(Provider, stamp.Provider);
139
+ Assert.Equal(Model, stamp.Model);
140
+ Assert.Equal("10.9.0", stamp.Packages["Microsoft.Extensions.AI.Evaluation"]);
141
+ }
142
+
143
+ [Fact]
144
+ [Trait("Category", "Eval")]
145
+ public void TheCommittedCacheCarriesNoABSOLUTEPathOfTheMachineThatWroteIt()
146
+ {
147
+ // A premissa que torna a arvore commitavel: nada dentro dela aponta para o disco de
148
+ // quem gravou. Medido, e nao suposto — se um dia a biblioteca passar a gravar o
149
+ // storageRootPath dentro das entradas, este canario morde.
150
+ var root = ProjectDirectory.Replace('\\', '/');
151
+ var drive = Path.GetPathRoot(ProjectDirectory)?.Replace('\\', '/');
152
+
153
+ // A raiz so' identifica uma MAQUINA quando e' uma unidade nomeada ("R:/"). Em POSIX
154
+ // `GetPathRoot` devolve "/", que nao identifica nada — e exigir que a cache nao
155
+ // contenha "/" reprova qualquer arquivo com um path ou uma URL dentro. O canario
156
+ // passava no Windows e era ABSURDO no Linux: media "nenhuma barra em lugar nenhum".
157
+ // Medido no CI (job ai-kit, ubuntu): 1 falha, `Assert.DoesNotContain() Sub-string found`.
158
+ var namedDrive = drive is { Length: > 1 } && drive.Contains(':');
159
+
160
+ foreach (var file in Directory.EnumerateFiles(CacheRoot, "*", SearchOption.AllDirectories))
161
+ {
162
+ var text = File.ReadAllText(file).Replace("\\\\", "/").Replace('\\', '/');
163
+ Assert.DoesNotContain(root, text, StringComparison.OrdinalIgnoreCase);
164
+ if (namedDrive)
165
+ {
166
+ Assert.DoesNotContain(drive!, text, StringComparison.OrdinalIgnoreCase);
167
+ }
168
+ }
169
+ }
170
+
171
+ /// <summary>
172
+ /// Regrava a arvore commitada a partir do fake. NAO roda em CI.
173
+ /// </summary>
174
+ /// <remarks>
175
+ /// <para>
176
+ /// Ele existe committed, e nao como um script que alguem rodou uma vez e jogou fora,
177
+ /// porque a proxima pessoa que subir o pin precisa saber COMO regravar — e uma instrucao
178
+ /// em prosa envelhece; um teste executavel nao.
179
+ /// </para>
180
+ /// <para>
181
+ /// A porta e uma variavel de ambiente, e nao um <c>[Fact(Skip=...)]</c> fixo, porque um
182
+ /// skip fixo nunca poderia ser exercitado: seria um seeder que ninguem consegue rodar.
183
+ /// Rode com <c>MORPH_EVAL_SEED=1 dotnet test</c> e commite a arvore que sair.
184
+ /// </para>
185
+ /// </remarks>
186
+ [Fact]
187
+ public async Task SeedTheCommittedCacheTree()
188
+ {
189
+ Assert.SkipUnless(
190
+ Environment.GetEnvironmentVariable("MORPH_EVAL_SEED") == "1",
191
+ "semeadura da cache exemplar: rode com MORPH_EVAL_SEED=1 e commite a arvore resultante");
192
+
193
+ using var fake = new FakeChatClient
194
+ {
195
+ MetadataProviderName = Provider,
196
+ MetadataDefaultModelId = Model,
197
+ };
198
+
199
+ // Uma resposta por cenario, na ORDEM do dataset (ordinal por caminho relativo):
200
+ // orcamento/acme/a-resposta-estruturada, triagem/acme/a-saudacao,
201
+ // triagem/acme/b-escalonamento, triagem/globex/a-saudacao.
202
+ fake.EnqueueText("""{"valor":"R$ 1.200,00","prazo":"5 dias uteis"}""")
203
+ .EnqueueText("Bom dia! Como posso ajudar voce hoje?")
204
+ .EnqueueText("Encaminhando voce para um atendente humano agora.")
205
+ .EnqueueText("Bom dia! Aqui e o atendimento da Globex.");
206
+
207
+ await using var fixture = EvalFixture.Create(Options(EvalMode.Refresh, summaryPath: null), fake);
208
+ var summary = await fixture.RunAllAsync(TestContext.Current.CancellationToken);
209
+
210
+ Assert.Equal(summary.Scenarios, summary.Passed);
211
+ Assert.Equal(EvalCacheState.Cold, summary.Cache);
212
+ }
213
+ }
@@ -0,0 +1,95 @@
1
+ using Microsoft.Extensions.AI;
2
+ using Morph.AiKit.Evals;
3
+ using Xunit;
4
+
5
+ namespace Morph.AiKit.Tests.Evals;
6
+
7
+ /// <summary>
8
+ /// O cliente que RECUSA: a peca de que depende "o eval roda no CI sem credencial".
9
+ /// </summary>
10
+ public sealed class OfflineChatClientTests
11
+ {
12
+ private static OfflineChatClient Build(Func<string>? scenario = null)
13
+ => new("morph-fake", "modelo-exemplar", "/tmp/eval-cache", ".env.evals", scenario ?? (() => "triagem.acme.saudacao"));
14
+
15
+ [Fact]
16
+ public void ItReplicatesTheProviderAndModelOfTheRunThatWroteTheCache()
17
+ {
18
+ using var client = Build();
19
+
20
+ var metadata = Assert.IsType<ChatClientMetadata>(client.GetService(typeof(ChatClientMetadata)));
21
+
22
+ // Estes DOIS campos entram na chave de cache. Se divergissem do run que gravou, todo
23
+ // cenario daria miss e a cache quente seria indistinguivel de uma cache vazia.
24
+ Assert.Equal("morph-fake", metadata.ProviderName);
25
+ Assert.Equal("modelo-exemplar", metadata.DefaultModelId);
26
+
27
+ // E o endpoint e NULO de proposito: ele nao entra na chave (medido na v10.9.0), e um
28
+ // cliente que nunca liga para lugar nenhum nao deve anunciar um destino.
29
+ Assert.Null(metadata.ProviderUri);
30
+ }
31
+
32
+ [Fact]
33
+ public async Task ARequestRefusesInsteadOfReachingTheNetwork()
34
+ {
35
+ using var client = Build();
36
+
37
+ var error = await Assert.ThrowsAsync<EvalCacheMissException>(
38
+ () => client.GetResponseAsync([new ChatMessage(ChatRole.User, "oi")], cancellationToken: TestContext.Current.CancellationToken));
39
+
40
+ Assert.Equal("triagem.acme.saudacao", error.ScenarioName);
41
+ Assert.Equal("/tmp/eval-cache", error.CachePath);
42
+ Assert.Equal(".env.evals", error.EnvFile);
43
+ }
44
+
45
+ [Fact]
46
+ public void TheCounterRisesOnEveryAttempt_ItIsTheProofOfZeroPaidCalls()
47
+ {
48
+ using var client = Build();
49
+ Assert.Equal(0, client.Invocations);
50
+
51
+ for (var i = 1; i <= 3; i++)
52
+ {
53
+ Assert.Throws<EvalCacheMissException>(
54
+ () => client.GetStreamingResponseAsync([new ChatMessage(ChatRole.User, "oi")], cancellationToken: TestContext.Current.CancellationToken));
55
+ Assert.Equal(i, client.Invocations);
56
+ }
57
+ }
58
+
59
+ [Fact]
60
+ public void TheScenarioNameIsReadAtCALLTIME_NotCaptured()
61
+ {
62
+ // Um fixture roda N cenarios com o MESMO cliente. Se o nome fosse capturado na
63
+ // construcao, toda mensagem de miss nomearia o primeiro cenario e mandaria o leitor
64
+ // investigar o arquivo errado.
65
+ var current = "primeiro";
66
+ using var client = Build(() => current);
67
+
68
+ var first = Assert.Throws<EvalCacheMissException>(
69
+ () => client.GetStreamingResponseAsync([new ChatMessage(ChatRole.User, "oi")], cancellationToken: TestContext.Current.CancellationToken));
70
+ Assert.Equal("primeiro", first.ScenarioName);
71
+
72
+ current = "segundo";
73
+ var second = Assert.Throws<EvalCacheMissException>(
74
+ () => client.GetStreamingResponseAsync([new ChatMessage(ChatRole.User, "oi")], cancellationToken: TestContext.Current.CancellationToken));
75
+ Assert.Equal("segundo", second.ScenarioName);
76
+ }
77
+
78
+ [Fact]
79
+ public void ItRefusesToBeBuiltWithoutTheIdentityThatEntersTheCacheKey()
80
+ {
81
+ Assert.Throws<ArgumentException>(() => new OfflineChatClient(" ", "m", "/tmp", ".env", () => "s"));
82
+ Assert.Throws<ArgumentException>(() => new OfflineChatClient("p", "m", " ", ".env", () => "s"));
83
+ Assert.Throws<ArgumentNullException>(() => new OfflineChatClient("p", "m", "/tmp", ".env", null!));
84
+ }
85
+
86
+ [Fact]
87
+ public void GetServiceAnswersLikeARealClientAndNeverInventsAKeyedService()
88
+ {
89
+ using var client = Build();
90
+
91
+ Assert.Same(client, client.GetService(typeof(IChatClient)));
92
+ Assert.Null(client.GetService(typeof(ChatClientMetadata), serviceKey: "qualquer"));
93
+ Assert.Null(client.GetService(typeof(string)));
94
+ }
95
+ }