@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,98 @@
1
+ using System.Text;
2
+ using Morph.AiKit.Media;
3
+ using Morph.AiKit.Tests.Fakes;
4
+ using Xunit;
5
+
6
+ namespace Morph.AiKit.Tests;
7
+
8
+ /// <summary>
9
+ /// Canarios do <see cref="MediaStore"/>: endereco por conteudo, e gravacao unica.
10
+ /// </summary>
11
+ /// <remarks>
12
+ /// A armadilha deste arquivo, e a razao de <see cref="InMemoryMediaBlobs.Writes"/> existir:
13
+ /// um teste que so conferisse "o hash e o mesmo" <b>nao distingue dedupe de regravacao</b>,
14
+ /// porque os dois produzem o mesmo hash. A afirmacao "grava uma vez" so e verificavel
15
+ /// contando gravacoes no blob.
16
+ /// </remarks>
17
+ public sealed class MediaStoreTests
18
+ {
19
+ private static CancellationToken Ct => TestContext.Current.CancellationToken;
20
+
21
+ private static ReadOnlyMemory<byte> Bytes(string s) => Encoding.UTF8.GetBytes(s);
22
+
23
+ /// <summary>Mesmo conteudo, mesmo hash, mesmo caminho — e UMA gravacao.</summary>
24
+ [Fact]
25
+ public async Task TheSameContentIsStoredOnce()
26
+ {
27
+ var blobs = new InMemoryMediaBlobs();
28
+ var store = new MediaStore(blobs);
29
+
30
+ var first = await store.PutAsync(Bytes("os bytes"), "image/png", Ct);
31
+ var second = await store.PutAsync(Bytes("os bytes"), "image/png", Ct);
32
+
33
+ Assert.Equal(first.Hash, second.Hash);
34
+ Assert.Equal(first.RelativePath, second.RelativePath);
35
+ Assert.False(first.AlreadyExisted);
36
+ Assert.True(second.AlreadyExisted);
37
+ Assert.Equal(1, blobs.Writes);
38
+ }
39
+
40
+ /// <summary>Conteudo diferente, endereco diferente, duas gravacoes.</summary>
41
+ [Fact]
42
+ public async Task DifferentContentGetsADifferentAddress()
43
+ {
44
+ var blobs = new InMemoryMediaBlobs();
45
+ var store = new MediaStore(blobs);
46
+
47
+ var a = await store.PutAsync(Bytes("um"), "image/png", Ct);
48
+ var b = await store.PutAsync(Bytes("outro"), "image/png", Ct);
49
+
50
+ Assert.NotEqual(a.Hash, b.Hash);
51
+ Assert.NotEqual(a.RelativePath, b.RelativePath);
52
+ Assert.Equal(2, blobs.Writes);
53
+ }
54
+
55
+ /// <summary>
56
+ /// O hash e o do CONTEUDO, e nao do conteudo mais o MIME: o mesmo PNG servido como
57
+ /// <c>image/png</c> e como <c>application/octet-stream</c> e o mesmo arquivo.
58
+ /// </summary>
59
+ [Fact]
60
+ public void TheHashIsOfTheContentAlone()
61
+ => Assert.Equal(MediaHash.Of(Bytes("x").Span), MediaHash.Of(Bytes("x").Span));
62
+
63
+ /// <summary>
64
+ /// O caminho tem dois niveis de prefixo. Um diretorio unico com centenas de milhares de
65
+ /// arquivos degrada listagem em praticamente todo sistema de arquivos.
66
+ /// </summary>
67
+ [Fact]
68
+ public void ThePathIsShardedByTheFirstFourHexDigits()
69
+ {
70
+ var hash = MediaHash.Of(Bytes("qualquer").Span);
71
+ var path = MediaStore.RelativePathFor(hash, "image/png");
72
+
73
+ Assert.Equal($"{hash.Value[..2]}/{hash.Value[2..4]}/{hash.Value}.png", path);
74
+ }
75
+
76
+ /// <summary>
77
+ /// MIME desconhecido vira <c>.bin</c> e NAO recusa: perder a extensao e cosmetico,
78
+ /// perder os bytes pelos quais se pagou nao e.
79
+ /// </summary>
80
+ [Theory]
81
+ [InlineData("image/png", ".png")]
82
+ [InlineData("image/jpeg", ".jpg")]
83
+ [InlineData("image/webp", ".webp")]
84
+ [InlineData("video/mp4", ".mp4")]
85
+ [InlineData("application/x-inventado", ".bin")]
86
+ public void UnknownMediaTypesStillGetAPath(string mediaType, string expected)
87
+ => Assert.Equal(expected, MediaStore.ExtensionFor(mediaType));
88
+
89
+ /// <summary>MIME em branco e defeito de chamada, e grita no lugar em que aconteceu.</summary>
90
+ [Fact]
91
+ public async Task ABlankMediaTypeIsRefused()
92
+ {
93
+ var store = new MediaStore(new InMemoryMediaBlobs());
94
+
95
+ await Assert.ThrowsAsync<ArgumentException>(
96
+ async () => await store.PutAsync(Bytes("x"), " ", Ct));
97
+ }
98
+ }
@@ -0,0 +1,451 @@
1
+ using Microsoft.Extensions.AI;
2
+ using Morph.AiKit.Agents;
3
+ using Morph.AiKit.Providers;
4
+ using Morph.AiKit.Tests.Fakes;
5
+ using Xunit;
6
+
7
+ namespace Morph.AiKit.Tests;
8
+
9
+ /// <summary>
10
+ /// Canarios do <see cref="ModelRegistry"/> (T8). Tres coisas sao cobradas aqui, e as tres
11
+ /// sao as que o registry v1 nao tinha: <b>protocolo por alias</b>, <b>tarifa por alias</b>
12
+ /// e <b>falha explicita</b> onde antes havia nulo silencioso.
13
+ /// </summary>
14
+ public sealed class ModelRegistryTests
15
+ {
16
+ /// <summary>O exemplar de verdade, embutido do proprio arquivo que o kit publica.</summary>
17
+ private static string ExampleDocument()
18
+ {
19
+ using var stream = typeof(ModelRegistryTests).Assembly
20
+ .GetManifestResourceStream("model-registry.v2.json")
21
+ ?? throw new InvalidOperationException(
22
+ "model-registry.v2.json nao foi embutido no assembly de teste.");
23
+
24
+ using var reader = new StreamReader(stream);
25
+ return reader.ReadToEnd();
26
+ }
27
+
28
+ private static string Document(string aliasesBody, string extraRoot = "")
29
+ => $$"""
30
+ {
31
+ "version": "2.0"{{extraRoot}},
32
+ "aliases": { {{aliasesBody}} }
33
+ }
34
+ """;
35
+
36
+ // ---------------------------------------------------------------------------------
37
+ // O exemplar publicado carrega — e expoe api e pricing por alias
38
+ // ---------------------------------------------------------------------------------
39
+
40
+ [Fact]
41
+ public void ThePublishedExampleLoadsAndExposesApiAndPricingPerAlias()
42
+ {
43
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
44
+
45
+ Assert.Equal(
46
+ ["local-dev", "text-default", "text-heavy", "text-heavy-chat", "vision"],
47
+ registry.Aliases.Order(StringComparer.Ordinal));
48
+
49
+ Assert.Equal("text-default", registry.DefaultAlias);
50
+
51
+ // 'api' omitido cai em Responses — a decisao da casa, verificada e nao suposta.
52
+ Assert.Equal(AgentApi.Responses, registry.Resolve("text-default").Api);
53
+ Assert.Equal(AgentApi.Responses, registry.Resolve("text-heavy").Api);
54
+
55
+ // ... e o MESMO modelo, sob outro alias, pode falar Chat Completions.
56
+ var chat = registry.Resolve("text-heavy-chat");
57
+ Assert.Equal(AgentApi.Chat, chat.Api);
58
+ Assert.Equal(registry.Resolve("text-heavy").Model, chat.Model);
59
+
60
+ var pricing = registry.Resolve("text-heavy").RequirePricing();
61
+ Assert.Equal(1.25m, pricing.InputPerMTok);
62
+ Assert.Equal(10.0m, pricing.OutputPerMTok);
63
+ Assert.Equal(0.125m, pricing.CachedInputPerMTok);
64
+
65
+ // cachedInputPerMTok explicitamente nulo = sem desconto declarado (nao "de graca").
66
+ Assert.Null(registry.Resolve("vision").RequirePricing().CachedInputPerMTok);
67
+
68
+ Assert.Equal("http://localhost:11434/v1", registry.Resolve("local-dev").Endpoint);
69
+ }
70
+
71
+ [Fact]
72
+ public void TheExampleCarriesTheChatCompletionsLimitWithItsProvenance()
73
+ {
74
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
75
+
76
+ var constraints = registry.Resolve("text-heavy-chat").Constraints;
77
+
78
+ Assert.True(constraints.ToolCallingRequiresReasoningEffortNone);
79
+ Assert.False(constraints.TemperatureUnsupported);
80
+ Assert.Contains("reasoning_effort", constraints.Provenance!, StringComparison.Ordinal);
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------------
84
+ // Tarifa ausente e ALTO, nunca nulo silencioso
85
+ // ---------------------------------------------------------------------------------
86
+
87
+ [Fact]
88
+ public void AnAliasWithNeitherPricingNorWaiverIsRefusedAtLoadNamingTheAlias()
89
+ {
90
+ var document = Document("""
91
+ "orfao": { "provider": "openai", "model": "gpt-5.4" }
92
+ """);
93
+
94
+ var error = Assert.Throws<ModelRegistryException>(
95
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
96
+
97
+ Assert.Equal(ModelRegistryError.InvalidDocument, error.Reason);
98
+ Assert.Contains("alias 'orfao'", error.Message, StringComparison.Ordinal);
99
+ Assert.Contains("pricingWaiver", error.Message, StringComparison.Ordinal);
100
+ }
101
+
102
+ [Fact]
103
+ public void AnAliasWithBothPricingAndWaiverIsRefusedBecauseNeitherWins()
104
+ {
105
+ var document = Document("""
106
+ "ambiguo": {
107
+ "provider": "openai", "model": "gpt-5.4",
108
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 },
109
+ "pricingWaiver": "nao tem preco"
110
+ }
111
+ """);
112
+
113
+ var error = Assert.Throws<ModelRegistryException>(
114
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
115
+
116
+ Assert.Contains("alias 'ambiguo'", error.Message, StringComparison.Ordinal);
117
+ }
118
+
119
+ [Fact]
120
+ public void AWaivedAliasLoadsAndSaysWhyItHasNoTariff()
121
+ {
122
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
123
+
124
+ var local = registry.Resolve("local-dev");
125
+
126
+ Assert.False(local.HasPricing);
127
+ Assert.Contains("modelo local", local.PricingWaiver!, StringComparison.Ordinal);
128
+
129
+ var error = Assert.Throws<ModelRegistryException>(() => { _ = local.RequirePricing(); });
130
+ Assert.Equal(ModelRegistryError.MissingPricing, error.Reason);
131
+ Assert.Contains("local-dev", error.Message, StringComparison.Ordinal);
132
+ Assert.Contains("modelo local", error.Message, StringComparison.Ordinal);
133
+ }
134
+
135
+ [Fact]
136
+ public void ANegativeTariffIsRefused()
137
+ {
138
+ var document = Document("""
139
+ "barato-demais": {
140
+ "provider": "openai", "model": "gpt-5.4",
141
+ "pricing": { "inputPerMTok": -1, "outputPerMTok": 2 }
142
+ }
143
+ """);
144
+
145
+ var error = Assert.Throws<ModelRegistryException>(
146
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
147
+
148
+ Assert.Contains("inputPerMTok", error.Message, StringComparison.Ordinal);
149
+ }
150
+
151
+ [Fact]
152
+ public void ATypoInAFieldNameIsRefusedInsteadOfSilentlyIgnored()
153
+ {
154
+ // "priceing" seria lido como "sem pricing" — e a ausencia silenciosa e o defeito.
155
+ var document = Document("""
156
+ "digitado-errado": {
157
+ "provider": "openai", "model": "gpt-5.4",
158
+ "priceing": { "inputPerMTok": 1, "outputPerMTok": 2 }
159
+ }
160
+ """);
161
+
162
+ var error = Assert.Throws<ModelRegistryException>(
163
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
164
+
165
+ Assert.Contains("priceing", error.Message, StringComparison.Ordinal);
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------------
169
+ // api: por alias
170
+ // ---------------------------------------------------------------------------------
171
+
172
+ [Fact]
173
+ public void AnUnknownApiValueIsRefusedListingTheValidOnes()
174
+ {
175
+ var document = Document("""
176
+ "inventado": {
177
+ "provider": "openai", "model": "gpt-5.4", "api": "assistants",
178
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 }
179
+ }
180
+ """);
181
+
182
+ var error = Assert.Throws<ModelRegistryException>(
183
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
184
+
185
+ Assert.Contains("'assistants'", error.Message, StringComparison.Ordinal);
186
+ Assert.Contains("responses", error.Message, StringComparison.Ordinal);
187
+ Assert.Contains("chat", error.Message, StringComparison.Ordinal);
188
+ }
189
+
190
+ [Fact]
191
+ public void ApiIsCaseInsensitiveOnTheWayIn()
192
+ {
193
+ var document = Document("""
194
+ "gritado": {
195
+ "provider": "openai", "model": "gpt-5.4", "api": "CHAT",
196
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 }
197
+ }
198
+ """);
199
+
200
+ using var registry = ModelRegistry.FromJson(document, new FakeProviderClientFactory());
201
+
202
+ Assert.Equal(AgentApi.Chat, registry.Resolve("gritado").Api);
203
+ }
204
+
205
+ [Fact]
206
+ public void AV1DocumentIsRefusedWithAVersionMessageInsteadOfAConfusingFieldError()
207
+ {
208
+ var document = """
209
+ {
210
+ "version": "1.0",
211
+ "defaultAlias": "text-default",
212
+ "aliases": { "text-default": { "provider": "openai", "model": "gpt-4o-mini" } }
213
+ }
214
+ """;
215
+
216
+ var error = Assert.Throws<ModelRegistryException>(
217
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
218
+
219
+ Assert.Contains("'1.0'", error.Message, StringComparison.Ordinal);
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------------
223
+ // Resolucao e memoizacao
224
+ // ---------------------------------------------------------------------------------
225
+
226
+ [Fact]
227
+ public void ResolvingAnUnknownAliasFailsExplicitlyAndListsWhatExists()
228
+ {
229
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
230
+
231
+ var error = Assert.Throws<ModelRegistryException>(() => registry.Resolve("nao-existe"));
232
+
233
+ Assert.Equal(ModelRegistryError.UnknownAlias, error.Reason);
234
+ Assert.Contains("nao-existe", error.Message, StringComparison.Ordinal);
235
+ Assert.Contains("text-default", error.Message, StringComparison.Ordinal);
236
+
237
+ Assert.False(registry.TryResolve("nao-existe", out var silent));
238
+ Assert.Null(silent);
239
+ }
240
+
241
+ [Fact]
242
+ public void TheChatClientIsMemoizedPerAliasAndTheFactoryRunsOnce()
243
+ {
244
+ var factory = new FakeProviderClientFactory();
245
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), factory);
246
+
247
+ var first = registry.GetChatClient("text-default");
248
+ var second = registry.GetChatClient("text-default");
249
+
250
+ Assert.Same(first, second);
251
+ Assert.Equal(1, factory.CreateCount);
252
+
253
+ var other = registry.GetChatClient("text-heavy");
254
+
255
+ Assert.NotSame(first, other);
256
+ Assert.Equal(2, factory.CreateCount);
257
+ }
258
+
259
+ [Fact]
260
+ public void AFactoryThatReturnsNullIsRefusedInsteadOfCachingNothing()
261
+ {
262
+ var factory = new FakeProviderClientFactory(_ => null);
263
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), factory);
264
+
265
+ var error = Assert.Throws<ModelRegistryException>(() => registry.GetChatClient("text-default"));
266
+
267
+ Assert.Contains("devolveu nulo", error.Message, StringComparison.Ordinal);
268
+ }
269
+
270
+ [Fact]
271
+ public void TheRegistryOwnsAndDisposesTheClientsItMemoized()
272
+ {
273
+ var factory = new FakeProviderClientFactory();
274
+ var registry = ModelRegistry.FromJson(ExampleDocument(), factory);
275
+
276
+ var client = (FakeChatClient)registry.GetChatClient("text-default");
277
+ Assert.False(client.IsDisposed);
278
+
279
+ registry.Dispose();
280
+
281
+ Assert.True(client.IsDisposed);
282
+ Assert.Throws<ObjectDisposedException>(() => registry.GetChatClient("text-default"));
283
+
284
+ registry.Dispose(); // idempotente
285
+ }
286
+
287
+ [Fact]
288
+ public void ADefaultAliasPointingNowhereIsRefused()
289
+ {
290
+ var document = Document(
291
+ """
292
+ "existe": {
293
+ "provider": "openai", "model": "gpt-5.4",
294
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 }
295
+ }
296
+ """,
297
+ extraRoot: ""","defaultAlias": "fantasma" """);
298
+
299
+ var error = Assert.Throws<ModelRegistryException>(
300
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
301
+
302
+ Assert.Contains("fantasma", error.Message, StringComparison.Ordinal);
303
+ }
304
+
305
+ [Fact]
306
+ public void BrokenJsonFailsNamingTheDocumentInsteadOfLeakingAJsonException()
307
+ {
308
+ var error = Assert.Throws<ModelRegistryException>(
309
+ () => ModelRegistry.FromJson("{ isto nao e json", new FakeProviderClientFactory()));
310
+
311
+ Assert.Contains("JSON invalido", error.Message, StringComparison.Ordinal);
312
+ Assert.IsAssignableFrom<System.Text.Json.JsonException>(error.InnerException);
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------------
316
+ // Tarifa: a conta
317
+ // ---------------------------------------------------------------------------------
318
+
319
+ [Fact]
320
+ public void CostIsTheTariffTimesTheTokensDividedByAMillion()
321
+ {
322
+ var pricing = new ModelPricing(InputPerMTok: 1.25m, OutputPerMTok: 10m, CachedInputPerMTok: null);
323
+
324
+ // 1.000.000 de entrada = 1.25 USD; 100.000 de saida = 1.00 USD
325
+ Assert.Equal(2.25m, pricing.ComputeCostUsd(inputTokens: 1_000_000, outputTokens: 100_000));
326
+ Assert.Equal(0m, pricing.ComputeCostUsd(0, 0));
327
+ }
328
+
329
+ [Fact]
330
+ public void CachedInputIsChargedApartOnlyWhenItsTariffExists()
331
+ {
332
+ var comCache = new ModelPricing(1.25m, 10m, CachedInputPerMTok: 0.125m);
333
+ var semCache = comCache with { CachedInputPerMTok = null };
334
+
335
+ // 1M de entrada, dos quais 800k vieram do cache:
336
+ // com tarifa de cache: 200k * 1.25 + 800k * 0.125 = 0.25 + 0.10 = 0.35
337
+ Assert.Equal(
338
+ 0.35m,
339
+ comCache.ComputeCostUsd(inputTokens: 1_000_000, outputTokens: 0, cachedInputTokens: 800_000));
340
+
341
+ // sem tarifa de cache, a entrada inteira e cobrada como entrada — nunca de graca.
342
+ Assert.Equal(
343
+ 1.25m,
344
+ semCache.ComputeCostUsd(inputTokens: 1_000_000, outputTokens: 0, cachedInputTokens: 800_000));
345
+ }
346
+
347
+ [Fact]
348
+ public void CachedTokensCannotExceedInputTokens()
349
+ {
350
+ var pricing = new ModelPricing(1m, 1m, 0.1m);
351
+
352
+ Assert.Throws<ArgumentException>(
353
+ () => pricing.ComputeCostUsd(inputTokens: 10, outputTokens: 0, cachedInputTokens: 11));
354
+ Assert.Throws<ArgumentOutOfRangeException>(
355
+ () => pricing.ComputeCostUsd(inputTokens: -1, outputTokens: 0));
356
+ }
357
+
358
+ // ---------------------------------------------------------------------------------
359
+ // Limites por alias — o limite REAL, nao o folclore
360
+ // ---------------------------------------------------------------------------------
361
+
362
+ [Fact]
363
+ public void TheChatToolLimitFiresOnlyWithAllFourFactsTogether()
364
+ {
365
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
366
+ var alias = registry.Resolve("text-heavy-chat");
367
+
368
+ // os quatro fatos juntos: Chat + tools + effort enviado + effort != none
369
+ var violado = alias.CheckRequest(
370
+ AgentApi.Chat, hasTools: true, reasoningEffort: ReasoningEffort.High, temperatureIsSet: false);
371
+
372
+ Assert.False(violado.IsSatisfied);
373
+ Assert.Contains("reasoning effort", Assert.Single(violado.Violations), StringComparison.Ordinal);
374
+
375
+ // mesmo pedido, na Responses API: sem violacao
376
+ Assert.True(alias.CheckRequest(
377
+ AgentApi.Responses, hasTools: true, ReasoningEffort.High, temperatureIsSet: false).IsSatisfied);
378
+
379
+ // sem tools: sem violacao
380
+ Assert.True(alias.CheckRequest(
381
+ AgentApi.Chat, hasTools: false, ReasoningEffort.High, temperatureIsSet: false).IsSatisfied);
382
+
383
+ // effort 'none': e exatamente o que a documentacao permite
384
+ Assert.True(alias.CheckRequest(
385
+ AgentApi.Chat, hasTools: true, ReasoningEffort.None, temperatureIsSet: false).IsSatisfied);
386
+
387
+ // effort nao enviado: o default do provider vale, e o kit NAO afirma saber qual e
388
+ Assert.True(alias.CheckRequest(
389
+ AgentApi.Chat, hasTools: true, reasoningEffort: null, temperatureIsSet: false).IsSatisfied);
390
+ }
391
+
392
+ [Fact]
393
+ public void AnAliasWithoutConstraintsNeverViolatesAnything()
394
+ {
395
+ using var registry = ModelRegistry.FromJson(ExampleDocument(), new FakeProviderClientFactory());
396
+
397
+ var check = registry.Resolve("text-default").CheckRequest(
398
+ AgentApi.Chat, hasTools: true, ReasoningEffort.High, temperatureIsSet: true);
399
+
400
+ Assert.True(check.IsSatisfied);
401
+ Assert.Empty(check.Violations);
402
+ }
403
+
404
+ [Fact]
405
+ public void TheTemperatureLimitIsPerAliasAndOnlyBitesWhenTemperatureIsSent()
406
+ {
407
+ // A afirmacao "temperatura e obsoleta para modelo de raciocinio" NAO foi confirmada
408
+ // em fonte primaria (issue #93). Por isso ela e uma flag por alias com procedencia
409
+ // escrita — nunca uma regra universal no codigo. Aqui a procedencia diz, por extenso,
410
+ // que o exemplo e sintetico.
411
+ var document = Document("""
412
+ "sem-temperatura": {
413
+ "provider": "openai", "model": "modelo-hipotetico",
414
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 },
415
+ "constraints": {
416
+ "temperatureUnsupported": true,
417
+ "provenance": "EXEMPLO SINTETICO deste teste. Nenhuma fonte primaria foi encontrada para a afirmacao generica sobre temperatura em modelos de raciocinio."
418
+ }
419
+ }
420
+ """);
421
+
422
+ using var registry = ModelRegistry.FromJson(document, new FakeProviderClientFactory());
423
+ var alias = registry.Resolve("sem-temperatura");
424
+
425
+ var violado = alias.CheckRequest(
426
+ AgentApi.Responses, hasTools: false, reasoningEffort: null, temperatureIsSet: true);
427
+
428
+ Assert.False(violado.IsSatisfied);
429
+ Assert.Contains("EXEMPLO SINTETICO", Assert.Single(violado.Violations), StringComparison.Ordinal);
430
+
431
+ Assert.True(alias.CheckRequest(
432
+ AgentApi.Responses, hasTools: false, reasoningEffort: null, temperatureIsSet: false).IsSatisfied);
433
+ }
434
+
435
+ [Fact]
436
+ public void AConstraintWithoutProvenanceIsRefusedAtLoad()
437
+ {
438
+ var document = Document("""
439
+ "folclore": {
440
+ "provider": "openai", "model": "gpt-5.4",
441
+ "pricing": { "inputPerMTok": 1, "outputPerMTok": 2 },
442
+ "constraints": { "temperatureUnsupported": true }
443
+ }
444
+ """);
445
+
446
+ var error = Assert.Throws<ModelRegistryException>(
447
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
448
+
449
+ Assert.Contains("provenance", error.Message, StringComparison.Ordinal);
450
+ }
451
+ }
@@ -0,0 +1,76 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <RootNamespace>Morph.AiKit.Tests</RootNamespace>
5
+ <AssemblyName>Morph.AiKit.Tests</AssemblyName>
6
+ <IsTestProject>true</IsTestProject>
7
+ <TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
8
+
9
+ <!--
10
+ PISO DE DESCOBERTA. Este kit existe porque "markdown envelhece em silencio;
11
+ codigo compilado grita" — e um runner que descobre ZERO teste nao grita: ele
12
+ imprime uma linha curta e some. Foi exatamente o que aconteceu na primeira
13
+ invocacao real (a flag `nologo`, que so existe no modo VSTest, matou a
14
+ sessao: "Zero testes executados" com 115 testes passando por baixo).
15
+
16
+ A opcao `minimum-expected-tests N` (dois hifens a frente, como toda opcao da
17
+ plataforma) faz o runner sair com o codigo 9
18
+ (MinimumExpectedTestsPolicyViolation) sempre que MENOS de N casos rodarem,
19
+ inclusive quando rodaram zero — esse piso explicito SUPERA o veredito 8
20
+ (ZeroTests). Medido em 2026‑09‑08 contra o SDK .NET 10:
21
+ piso 221 → 221 aprovados, exit 0
22
+ piso 9999 → 115 aprovados, exit 9
23
+
24
+ Mora AQUI, e nao no passo do workflow, de proposito: assim o piso viaja com
25
+ o kit e vale para TODA invocacao de `dotnet test` — CI, maquina do dev e o
26
+ no `tests` do `morph-spec verify` — em vez de proteger so os dois arquivos
27
+ de workflow. Uma unica verdade: nenhum chamador repassa a flag na linha de
28
+ comando, porque repassa-la duplicaria a opcao.
29
+
30
+ O numero e o total de CASOS EXECUTADOS (275), nao de atributos nem de casos
31
+ DESCOBERTOS (276): os [Theory] expandem, e um caso IGNORADO nao conta para o piso — a
32
+ suite descobre 276 e executa 275, porque o semeador da cache exemplar de eval
33
+ (KitEvals.SeedTheCommittedCacheTree) so roda sob MORPH_EVAL_SEED=1. Medido: com piso
34
+ 276 a sessao sai 9 mesmo com zero falhas. Adicionou teste? Suba o piso — o canario de
35
+ test/framework/ai-kit-ci-packaging.test.js cobra isso e falha se o piso
36
+ ficar para tras da contagem de `[Fact]`/`[Theory]` do diretorio.
37
+
38
+ E A SAIDA TAMBEM MUDA, nao so a invocacao. Sob MTP o sumario final e um
39
+ BLOCO DE 6 LINHAS, o veredito NAO comeca a linha ("Resumo da execucao de
40
+ teste: Aprovado!" / "Test run summary: Passed!") e a contagem vem em linha
41
+ propria, indentada, como `total:` MINUSCULO — nada disso e o sumario do
42
+ VSTest. Medido nos dois locales em 2026-09-08. Um parser do framework
43
+ escrito para VSTest (que procure uma linha COMECANDO em
44
+ "Aprovado!"/"Passed!" e contendo "Total de testes:"/"Total:") nao consegue
45
+ contar os testes deste projeto. O conserto do parser e da feature
46
+ `verify-por-stack`; aqui isto e registro da fronteira, nao conserto — o
47
+ piso acima ja garante que uma descoberta parcial saia com codigo 9, que e
48
+ o eixo que ESTE arquivo controla.
49
+ -->
50
+ <TestingPlatformCommandLineArguments>--minimum-expected-tests 275</TestingPlatformCommandLineArguments>
51
+ </PropertyGroup>
52
+
53
+ <!-- Sem atributo Version: as versoes vem de Directory.Packages.props (gerado do pin). -->
54
+ <ItemGroup>
55
+ <PackageReference Include="Microsoft.NET.Test.Sdk" />
56
+ <PackageReference Include="xunit.v3" />
57
+ <PackageReference Include="xunit.runner.visualstudio" />
58
+ </ItemGroup>
59
+
60
+ <ItemGroup>
61
+ <ProjectReference Include="../../src/Morph.AiKit/Morph.AiKit.csproj" />
62
+ </ItemGroup>
63
+
64
+ <!--
65
+ O exemplar model-registry.v2.json entra no assembly de TESTE por link, e nao no da
66
+ biblioteca: ele e artefato de template (o projeto que copia o kit o poe em
67
+ config/model-registry.json), nao payload de runtime. Embutido aqui, o teste carrega o
68
+ MESMO arquivo que o usuario vai copiar — um exemplar que nao carrega deixa de passar
69
+ despercebido.
70
+ -->
71
+ <ItemGroup>
72
+ <EmbeddedResource Include="../../src/Morph.AiKit/Providers/model-registry.v2.json"
73
+ LogicalName="model-registry.v2.json" />
74
+ </ItemGroup>
75
+
76
+ </Project>
@@ -0,0 +1,33 @@
1
+ using System.Reflection;
2
+ using System.Runtime.Versioning;
3
+ using Xunit;
4
+
5
+ namespace Morph.AiKit.Tests;
6
+
7
+ /// <summary>
8
+ /// Canario do esqueleto: prova que o harness de teste EXECUTA de verdade e que o
9
+ /// assembly do kit foi compilado no TFM do pin. Nao testa comportamento — os testes
10
+ /// de AgentFactory, ModelRegistry, middlewares, ConversationStore e DeltaIngest sao
11
+ /// das tasks T7-T14.
12
+ ///
13
+ /// Por que ele existe em vez de um projeto de teste literalmente vazio: um projeto
14
+ /// sem nenhum teste NAO sai 0. Microsoft.Testing.Platform devolve exit code 8
15
+ /// ("zero tests ran") e o VSTest antigo devolvia 1 com "no test is available".
16
+ /// Um esqueleto que so "compila" e um CI verde que nunca executou nada — e um verde
17
+ /// desses e indistinguivel de um verde real, que e exatamente o silencio que esta
18
+ /// feature existe para quebrar.
19
+ /// </summary>
20
+ public sealed class SkeletonSmokeTests
21
+ {
22
+ [Fact]
23
+ public void KitAssemblyIsLoadableAndTargetsThePinnedFramework()
24
+ {
25
+ var kit = typeof(AiKitAssemblyMarker).Assembly;
26
+
27
+ Assert.Equal("Morph.AiKit", kit.GetName().Name);
28
+
29
+ var tfm = kit.GetCustomAttribute<TargetFrameworkAttribute>();
30
+ Assert.NotNull(tfm);
31
+ Assert.Equal(".NETCoreApp,Version=v10.0", tfm.FrameworkName);
32
+ }
33
+ }