@polymorphism-tech/morph-spec 8.33.0 → 8.35.0

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