@polymorphism-tech/morph-spec 8.38.0 → 8.40.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 (227) hide show
  1. package/CLAUDE.md +19 -8
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +3 -2
  8. package/framework/MORPH.md +1 -1
  9. package/framework/agents.json +36 -25
  10. package/framework/docs/CLI.md +2 -2
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/mcp/registry.json +33 -2
  14. package/framework/rules/infrastructure-standards.md +0 -1
  15. package/framework/rules/morph-workflow.md +9 -4
  16. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  17. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  18. package/framework/skills/core/morph-apply/SKILL.md +5 -1
  19. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  20. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  21. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  22. package/framework/skills/core/morph-implement/SKILL.md +6 -1
  23. package/framework/skills/core/morph-media/SKILL.md +1 -1
  24. package/framework/skills/core/morph-plan/SKILL.md +4 -3
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +1 -1
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/skills/core/morph-uiux/SKILL.md +6 -5
  30. package/framework/standards/STANDARDS.json +3 -3
  31. package/framework/standards/ai-agents/context-providers.md +16 -26
  32. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  33. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  34. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  35. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  36. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  37. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  38. package/framework/standards/architecture/code-comments.md +52 -16
  39. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  40. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  41. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  42. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  43. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  44. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  45. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  46. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  47. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  48. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  49. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  50. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  51. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  52. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  53. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  54. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  55. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  56. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  57. package/framework/templates/code/dotnet/test.cs +2 -87
  58. package/framework/templates/code/typescript/contracts.ts +140 -167
  59. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  60. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  61. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  62. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  63. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  64. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  65. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  66. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  110. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  111. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  112. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  113. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  114. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  115. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  116. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  117. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  142. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  143. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  144. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  145. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  146. package/framework/templates/integrations/asaas-client.cs +332 -387
  147. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  148. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  149. package/framework/templates/project/validate.js +97 -120
  150. package/package.json +2 -2
  151. package/src/commands/dashboard/dashboard.js +80 -53
  152. package/src/commands/mcp/mcp-setup.js +37 -9
  153. package/src/commands/project/doctor.js +3 -5
  154. package/src/commands/project/init.js +6 -3
  155. package/src/commands/project/install-plugin-cmd.js +1 -1
  156. package/src/commands/project/update.js +5 -0
  157. package/src/dashboard/app/App.jsx +80 -16
  158. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  159. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  160. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  161. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  162. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  163. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  164. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  165. package/src/dashboard/app/components/Markdown.jsx +47 -0
  166. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  167. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  168. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  169. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  170. package/src/dashboard/app/hooks/usePref.js +38 -0
  171. package/src/dashboard/app/lib/format.js +69 -0
  172. package/src/dashboard/app/lib/phases.js +56 -0
  173. package/src/dashboard/app/lib/project.js +50 -0
  174. package/src/dashboard/app/pages/Activity.jsx +24 -0
  175. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  176. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  177. package/src/dashboard/app/pages/Features.jsx +400 -0
  178. package/src/dashboard/app/pages/Guide.jsx +146 -0
  179. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  180. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  181. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  182. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  183. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  184. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  185. package/src/dashboard/app/pages/Standards.jsx +407 -332
  186. package/src/dashboard/app/styles/index.css +187 -47
  187. package/src/dashboard/index.html +14 -12
  188. package/src/dashboard/package.json +32 -24
  189. package/src/dashboard/scanner/activity-scanner.js +351 -0
  190. package/src/dashboard/scanner/agents-scanner.js +59 -1
  191. package/src/dashboard/scanner/docs-scanner.js +59 -0
  192. package/src/dashboard/scanner/feature-status.js +10 -0
  193. package/src/dashboard/scanner/features-scanner.js +360 -0
  194. package/src/dashboard/scanner/index.js +14 -7
  195. package/src/dashboard/scanner/phases-scanner.js +14 -15
  196. package/src/dashboard/scanner/portfolio.js +153 -0
  197. package/src/dashboard/scanner/project-api.js +195 -0
  198. package/src/dashboard/scanner/rules-scanner.js +42 -37
  199. package/src/dashboard/scanner/skills-scanner.js +105 -43
  200. package/src/dashboard/scanner/standards-scanner.js +2 -0
  201. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  202. package/src/lib/detectors/claude-config-detector.js +2 -7
  203. package/src/lib/detectors/index.js +5 -1
  204. package/src/lib/evaluator-report.js +22 -0
  205. package/src/lib/generators/settings-generator.js +208 -209
  206. package/src/lib/installers/mcp-installer.js +724 -719
  207. package/src/lib/installers/memory-installer.js +1 -2
  208. package/src/lib/projects/registry.js +154 -0
  209. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  210. package/src/scripts/install-plugin.js +1 -1
  211. package/src/scripts/setup-infra.js +2 -4
  212. package/src/utils/agents-installer.js +57 -12
  213. package/src/utils/banner.js +1 -1
  214. package/src/commands/index.js +0 -13
  215. package/src/commands/project/checkpoint.js +0 -209
  216. package/src/core/state/phase-state-machine.js +0 -197
  217. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  218. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  219. package/src/dashboard/scanner/squads-scanner.js +0 -13
  220. package/src/dashboard/scanner/state-scanner.js +0 -85
  221. package/src/lib/generators/index.js +0 -10
  222. package/src/lib/hooks/hook-executor.js +0 -167
  223. package/src/lib/validators/checkpoint-persistence.js +0 -39
  224. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  225. package/src/lib/validators/shared/index.js +0 -12
  226. package/src/lib/validators/shared/issue-counter.js +0 -14
  227. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -5,56 +5,14 @@ using Microsoft.Extensions.AI.Evaluation.Reporting.Storage;
5
5
 
6
6
  namespace Morph.AiKit.Evals;
7
7
 
8
- /// <summary>O que uma execucao de cenario produziu.</summary>
9
- /// <param name="Scenario">A chave do cenario na cache (<c>{agente}/{tenant}/{nome}</c>).</param>
10
- /// <param name="Passed">Se TODAS as assercoes deterministicas se sustentaram.</param>
11
- /// <param name="Outcomes">Um veredito por assercao, na ordem do dataset.</param>
12
- /// <param name="Result">O resultado completo da biblioteca, com as metricas de juizo junto.</param>
13
8
  public sealed record EvalScenarioResult(
14
9
  string Scenario,
15
10
  bool Passed,
16
11
  IReadOnlyList<EvalAssertionOutcome> Outcomes,
17
12
  EvaluationResult Result);
18
13
 
19
- /// <summary>
20
- /// Monta a configuracao de eval com cache em disco de TTL explicito, roda os cenarios do
21
- /// dataset versionado e escreve o sumario que o CLI le.
22
- /// </summary>
23
- /// <remarks>
24
- /// <para>
25
- /// <b>A montagem inteira existe para uma frase: com a cache quente, o eval roda sem
26
- /// credencial e sem rede.</b> O caminho e este — a biblioteca embrulha o cliente interno num
27
- /// cliente de cache; no HIT o interno nunca e invocado; em modo offline o interno e o
28
- /// <see cref="OfflineChatClient"/>, que recusa. Logo, ou tudo resolve pela cache, ou a suite
29
- /// fica vermelha nomeando o cenario que faltou. Nunca <c>skip</c>: gate verde sobre
30
- /// verificacao que nao aconteceu e o modo de falha que este harness mais paga para evitar.
31
- /// </para>
32
- /// <para>
33
- /// <b>Cache e resultados vivem em raizes DIFERENTES</b>, e isso e uma decisao, nao um
34
- /// detalhe: os resultados carregam latencia, uso de tokens e o flag de cache hit, todos
35
- /// nao-deterministicos. Numa raiz so, commitar a cache arrastaria os resultados junto e
36
- /// produziria diff em toda rodada, sem sinal nenhum. Separar por CONSTRUCAO (duas raizes) e
37
- /// mais forte que separar por <c>.gitignore</c>: uma regra de ignore dentro de um diretorio
38
- /// commitado e uma regra que a proxima pessoa remove sem entender.
39
- /// </para>
40
- /// <para>
41
- /// <b>Uma <c>ReportingConfiguration</c> por cenario</b>, e nao uma para a suite, porque as
42
- /// assercoes deterministicas SAO os avaliadores e elas mudam a cada cenario. A alternativa
43
- /// seria um avaliador com estado mutavel entre cenarios — barato de escrever e caro de
44
- /// depurar no dia em que dois cenarios rodarem fora de ordem.
45
- /// </para>
46
- /// </remarks>
47
14
  public sealed class EvalFixture : IAsyncDisposable
48
15
  {
49
- /// <summary>
50
- /// O nome do cenario em execucao, por indirecao.
51
- /// </summary>
52
- /// <remarks>
53
- /// O <see cref="OfflineChatClient"/> e construido ANTES do fixture (ele e o cliente
54
- /// interno dele) e precisa nomear, na mensagem de miss, o cenario que esta rodando
55
- /// AGORA. Um nome capturado na construcao nomearia sempre o primeiro cenario, e a
56
- /// mensagem mandaria o leitor investigar o cenario errado.
57
- /// </remarks>
58
16
  private sealed class ScenarioCursor
59
17
  {
60
18
  public string Name { get; set; } = "(nenhum)";
@@ -87,33 +45,18 @@ public sealed class EvalFixture : IAsyncDisposable
87
45
  _resultStore = new DiskBasedResultStore(options.ResultsRoot);
88
46
  }
89
47
 
90
- /// <summary>Nome da execucao no store de resultados. Estavel, porque os resultados nao se commitam.</summary>
91
48
  public const string ExecutionName = "morph-eval";
92
49
 
93
- /// <summary>A configuracao efetiva desta execucao.</summary>
94
50
  public EvalCacheOptions Options { get; }
95
51
 
96
- /// <summary>
97
- /// O veredito do carimbo, apurado UMA vez na construcao — antes de avaliar qualquer
98
- /// cenario. Nao faz sentido pagar o relogio de uma suite para descobrir no fim que a
99
- /// cache nao servia.
100
- /// </summary>
101
52
  public EvalCacheStampCheck StampCheck { get; }
102
53
 
103
- /// <summary>
104
- /// O cliente offline, quando o modo e <see cref="EvalMode.Offline"/>. E por ele que se
105
- /// mede "zero invocacoes do cliente interno" — a prova de que nenhuma chamada paga foi
106
- /// feita.
107
- /// </summary>
108
54
  public OfflineChatClient? Offline => _offline;
109
55
 
110
- /// <summary>Quantas vezes a cache falhou e a chamada desceu ao cliente interno.</summary>
111
56
  public int Misses => _offline?.Invocations ?? _counting?.Calls ?? 0;
112
57
 
113
- /// <summary>Os resultados acumulados, na ordem em que os cenarios rodaram.</summary>
114
58
  public IReadOnlyList<EvalScenarioResult> Results => _results;
115
59
 
116
- /// <summary>O sumario do que rodou ate agora.</summary>
117
60
  public EvalRunSummary Summary => new()
118
61
  {
119
62
  Scenarios = _results.Count,
@@ -128,15 +71,6 @@ public sealed class EvalFixture : IAsyncDisposable
128
71
  Failures = CollectFailures(),
129
72
  };
130
73
 
131
- /// <summary>
132
- /// Uma entrada por assercao REPROVADA, com o cenario que a carregava.
133
- /// </summary>
134
- /// <remarks>
135
- /// Percorre as assercoes reprovadas, e nao os cenarios reprovados: o cenario diz ONDE, a
136
- /// assercao diz O QUE. Um sumario que so contasse cenarios obrigaria quem le a reabrir o
137
- /// dataset para descobrir qual regua quebrou — que e exatamente a investigacao que a
138
- /// mensagem existe para dispensar.
139
- /// </remarks>
140
74
  private IReadOnlyList<EvalFailureDetail> CollectFailures()
141
75
  {
142
76
  var failures = new List<EvalFailureDetail>();
@@ -145,10 +79,6 @@ public sealed class EvalFixture : IAsyncDisposable
145
79
  {
146
80
  var reproved = result.Outcomes.Where(o => !o.Passed).ToList();
147
81
 
148
- // O cenario SEM assercao nenhuma reprova por vacuidade (ele nao cobra nada), e
149
- // nesse caso nao existe outcome reprovado para nomear. Sem esta linha ele seria
150
- // contado em `Failed` e invisivel em `Failures` — um numero sem frase, que e a
151
- // metade exata do defeito que este tipo existe para fechar.
152
82
  if (reproved.Count == 0)
153
83
  {
154
84
  failures.Add(new EvalFailureDetail(
@@ -169,20 +99,6 @@ public sealed class EvalFixture : IAsyncDisposable
169
99
  return failures;
170
100
  }
171
101
 
172
- /// <summary>
173
- /// Constroi o fixture a partir das variaveis que o CLI DITA.
174
- /// </summary>
175
- /// <remarks>
176
- /// Fora do CLI um projeto pode montar <see cref="EvalCacheOptions"/> a mao e chamar
177
- /// <see cref="Create"/>; as variaveis existem para que a MESMA suite seja executavel pelo
178
- /// <c>verify</c> sem um segundo caminho de configuracao.
179
- /// </remarks>
180
- /// <param name="liveClient">
181
- /// O cliente real. Obrigatorio em <see cref="EvalMode.Refresh"/> e
182
- /// <see cref="EvalMode.Online"/>; ignorado em <see cref="EvalMode.Offline"/>.
183
- /// </param>
184
- /// <param name="qualityEvaluators">Avaliadores de juizo. Reportados, nunca decisivos.</param>
185
- /// <param name="timeProvider">Relogio, para a checagem de pin no futuro.</param>
186
102
  public static EvalFixture FromEnvironment(
187
103
  IChatClient? liveClient = null,
188
104
  IEnumerable<IEvaluator>? qualityEvaluators = null,
@@ -198,8 +114,6 @@ public sealed class EvalFixture : IAsyncDisposable
198
114
  DatasetRoot = Environment.GetEnvironmentVariable("MORPH_EVAL_DATASET") ?? Path.Combine(beside, "eval-datasets"),
199
115
  ResultsRoot = Environment.GetEnvironmentVariable("MORPH_EVAL_RESULTS") ?? Path.Combine(beside, "eval-results"),
200
116
  Mode = ParseMode(Environment.GetEnvironmentVariable("MORPH_EVAL_MODE")),
201
- // Explicito e longo: o default da biblioteca (14 dias) apagaria a arvore
202
- // commitada durante uma leitura. Ver EvalCacheOptions.
203
117
  TimeToLive = TimeSpan.FromDays(3650),
204
118
  ProviderName = Environment.GetEnvironmentVariable("MORPH_EVAL_PROVIDER") ?? "morph-eval",
205
119
  DefaultModelId = Environment.GetEnvironmentVariable("MORPH_EVAL_MODEL"),
@@ -211,9 +125,6 @@ public sealed class EvalFixture : IAsyncDisposable
211
125
  return Create(options, liveClient, qualityEvaluators, timeProvider);
212
126
  }
213
127
 
214
- /// <summary>Constroi o fixture a partir de opcoes explicitas.</summary>
215
- /// <exception cref="ArgumentOutOfRangeException">TTL abaixo do piso — ver <see cref="EvalCacheOptions.MinimumTimeToLive"/>.</exception>
216
- /// <exception cref="InvalidOperationException">Carimbo ausente ou divergente em modo de leitura; ou modo de escrita sem cliente real.</exception>
217
128
  public static EvalFixture Create(
218
129
  EvalCacheOptions options,
219
130
  IChatClient? liveClient = null,
@@ -237,8 +148,7 @@ public sealed class EvalFixture : IAsyncDisposable
237
148
  var clock = timeProvider ?? TimeProvider.System;
238
149
  IReadOnlyList<IEvaluator> quality = qualityEvaluators is null ? [] : [.. qualityEvaluators];
239
150
 
240
- // O carimbo so e COBRADO nos modos que LEEM a cache. Em Refresh a arvore esta sendo
241
- // (re)escrita — cobrar carimbo ali tornaria impossivel criar a primeira cache.
151
+ // Em Refresh a arvore esta sendo reescrita: cobrar carimbo impediria gravar a primeira cache.
242
152
  var stampCheck = options.Mode == EvalMode.Refresh || options.Pin is null
243
153
  ? new EvalCacheStampCheck(
244
154
  EvalStampVerdict.Ok,
@@ -278,10 +188,6 @@ public sealed class EvalFixture : IAsyncDisposable
278
188
  return new EvalFixture(options, stampCheck, cursor, inner, offline, counting, quality);
279
189
  }
280
190
 
281
- /// <summary>
282
- /// Roda TODOS os cenarios do dataset e escreve o sumario, quando ha caminho para ele.
283
- /// </summary>
284
- /// <exception cref="InvalidOperationException">Dataset sem nenhum cenario — uma suite de eval que avalia zero cenarios e sai verde e um guardrail inerte.</exception>
285
191
  public async Task<EvalRunSummary> RunAllAsync(CancellationToken cancellationToken = default)
286
192
  {
287
193
  var scenarios = EvalScenario.LoadAll(Options.DatasetRoot);
@@ -293,9 +199,7 @@ public sealed class EvalFixture : IAsyncDisposable
293
199
  + "que este kit existe para nao produzir.");
294
200
  }
295
201
 
296
- // Em Refresh a arvore inteira e zerada antes de regravar: uma regravacao parcial
297
- // deixaria entradas de uma versao antiga convivendo com entradas novas, e a proxima
298
- // leitura offline nao teria como saber quais sao quais.
202
+ // Zera antes de regravar: entradas de versoes diferentes nao podem conviver na arvore.
299
203
  if (Options.Mode == EvalMode.Refresh)
300
204
  {
301
205
  try
@@ -305,12 +209,7 @@ public sealed class EvalFixture : IAsyncDisposable
305
209
  }
306
210
  catch (DirectoryNotFoundException)
307
211
  {
308
- // MEDIDO na v10.9.0: ResetAsync chama Directory.Delete no subdiretorio de
309
- // cache e LANCA quando ele ainda nao existe — o caso da PRIMEIRA gravacao.
310
- // "Nao ha o que zerar" e sucesso, nao falha. Capturado aqui, e nao evitado
311
- // com um Directory.Exists sobre o layout interno da biblioteca: repetir o
312
- // nome do subdiretorio dela aqui seria uma segunda verdade sobre onde a
313
- // cache mora.
212
+ // Raiz ausente na primeira gravacao: nada a zerar.
314
213
  }
315
214
  }
316
215
 
@@ -339,7 +238,6 @@ public sealed class EvalFixture : IAsyncDisposable
339
238
  return summary;
340
239
  }
341
240
 
342
- /// <summary>Roda UM cenario: resolve a resposta (cache ou cliente interno) e aplica os avaliadores.</summary>
343
241
  public async Task<EvalScenarioResult> RunAsync(EvalScenario scenario, CancellationToken cancellationToken = default)
344
242
  {
345
243
  ArgumentNullException.ThrowIfNull(scenario);
@@ -378,11 +276,9 @@ public sealed class EvalFixture : IAsyncDisposable
378
276
  outcomes.Add(assertion.Evaluate(response));
379
277
  }
380
278
 
381
- // Um cenario SEM assercao deterministica nao passa: ele nao cobra nada. Deixa-lo
382
- // verde por vacuidade e o mesmo defeito de um teste vazio — e aqui ele seria pior,
383
- // porque o cenario apareceria na contagem como se tivesse verificado algo.
384
279
  var scenarioResult = new EvalScenarioResult(
385
280
  scenario.CacheKey,
281
+ // Sem assercao o cenario reprova: verde por vacuidade contaria como verificado.
386
282
  outcomes.Count > 0 && outcomes.All(o => o.Passed),
387
283
  outcomes,
388
284
  result);
@@ -391,7 +287,6 @@ public sealed class EvalFixture : IAsyncDisposable
391
287
  return scenarioResult;
392
288
  }
393
289
 
394
- /// <inheritdoc />
395
290
  public ValueTask DisposeAsync()
396
291
  {
397
292
  _inner.Dispose();
@@ -1,32 +1,10 @@
1
1
  namespace Morph.AiKit.Evals;
2
2
 
3
- /// <summary>
4
- /// De onde a resposta do modelo pode vir nesta execucao de eval.
5
- /// </summary>
6
- /// <remarks>
7
- /// A distincao existe porque as tres situacoes tem custos diferentes em DINHEIRO e em
8
- /// confianca, e um enum de dois valores ("usa cache" / "nao usa") esconderia a do meio.
9
- /// </remarks>
10
3
  public enum EvalMode
11
4
  {
12
- /// <summary>
13
- /// So a cache. Um miss e falha nomeada, nunca uma chamada de rede — e nunca um
14
- /// <c>skip</c>: gate verde sobre verificacao que nao aconteceu e o modo de falha que
15
- /// este harness mais paga para evitar. E o modo do CI.
16
- /// </summary>
17
5
  Offline = 0,
18
6
 
19
- /// <summary>
20
- /// Reexecuta TODOS os cenarios contra o provider e regrava a arvore de cache. Exige
21
- /// credencial e nunca acontece implicitamente: uma regravacao automatica e uma chamada
22
- /// paga que ninguem pediu.
23
- /// </summary>
24
7
  Refresh = 1,
25
8
 
26
- /// <summary>
27
- /// Cache primeiro, provider no miss. E o modo de quem esta escrevendo cenario novo na
28
- /// propria maquina: paga so pelo que falta, e o resultado sai marcado como
29
- /// <c>cold</c> com aviso de que a arvore mudou e precisa ser commitada.
30
- /// </summary>
31
9
  Online = 2,
32
10
  }
@@ -3,44 +3,9 @@ using Microsoft.Extensions.AI.Evaluation.Quality;
3
3
 
4
4
  namespace Morph.AiKit.Evals;
5
5
 
6
- /// <summary>
7
- /// Os avaliadores de JUIZO que o kit considera utilizaveis, e a fronteira que os separa dos
8
- /// experimentais.
9
- /// </summary>
10
- /// <remarks>
11
- /// <para>
12
- /// <b>Eles nunca decidem.</b> O veredito de um cenario sai das <see cref="EvalAssertion"/>
13
- /// deterministicas; o que sai daqui e METRICA REPORTADA. Um juizo de LLM que reprovasse
14
- /// sozinho reintroduziria a variancia que faz suites de eval serem desligadas do CI — e um
15
- /// no de eval desligado nao verifica nada.
16
- /// </para>
17
- /// <para>
18
- /// <b>Por que a lista e curta.</b> Dois dos quatro avaliadores que o plano desta feature
19
- /// listava como estaveis carregam <c>[Experimental("AIEVAL001")]</c> na versao pinada:
20
- /// <c>TaskAdherenceEvaluator</c> e <c>ToolCallAccuracyEvaluator</c>. Sob
21
- /// <c>TreatWarningsAsErrors</c>, usa-los exige suprimir um diagnostico — uma decisao
22
- /// consciente de quem adota o kit, jamais um efeito colateral de chamar um metodo chamado
23
- /// "Stable". Medido por reflexao sobre <c>Microsoft.Extensions.AI.Evaluation.Quality</c>
24
- /// 10.9.0, e pinado pelo teste <c>WhichQualityEvaluatorsAreExperimentalIsMEASURED</c>.
25
- /// </para>
26
- /// <para>
27
- /// <b>E por que este tipo existe.</b> Sem uma unica referencia em <c>src/</c>, o pacote
28
- /// <c>.Quality</c> teria apenas contrato de VERSAO (o restore, NU1102) e um rename de API
29
- /// nele nao reprovaria PR nenhuma. Com o <c>using</c> real acima, ele passa a ter cobertura
30
- /// de compilacao — que e o nivel de garantia que o kit promete para a familia de Evaluation.
31
- /// </para>
32
- /// </remarks>
33
6
  public static class EvalQualityEvaluators
34
7
  {
35
- /// <summary>
36
- /// Os avaliadores de qualidade NAO experimentais da versao pinada, prontos para
37
- /// <see cref="EvalFixture.Create"/>.
38
- /// </summary>
39
- /// <remarks>
40
- /// Uma lista nova a cada chamada: os avaliadores sao passados adiante e guardados por uma
41
- /// <c>ReportingConfiguration</c>, e um array compartilhado seria estado global disfarcado
42
- /// de conveniencia.
43
- /// </remarks>
8
+ // TaskAdherence e ToolCallAccuracy ficam fora: sao [Experimental("AIEVAL001")] no Quality 10.9.0.
44
9
  public static IReadOnlyList<IEvaluator> Stable() =>
45
10
  [
46
11
  new RelevanceEvaluator(),
@@ -4,73 +4,21 @@ using Microsoft.Extensions.AI;
4
4
 
5
5
  namespace Morph.AiKit.Evals;
6
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
7
  public sealed record EvalScenario
25
8
  {
26
- /// <summary>Primeiro segmento do caminho no dataset.</summary>
27
9
  public required string Agent { get; init; }
28
10
 
29
- /// <summary>Segundo segmento do caminho no dataset.</summary>
30
11
  public required string Tenant { get; init; }
31
12
 
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
13
  public required string Name { get; init; }
37
14
 
38
- /// <summary>As mensagens enviadas ao modelo, na ordem.</summary>
39
15
  public required IReadOnlyList<ChatMessage> Messages { get; init; }
40
16
 
41
- /// <summary>As assercoes deterministicas que decidem pass/fail deste cenario.</summary>
42
17
  public IReadOnlyList<EvalAssertion> Assertions { get; init; } = [];
43
18
 
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>
19
+ // Ponto, nao barra: a biblioteca exige que o nome do cenario seja UM segmento de caminho (10.9.0).
62
20
  public string CacheKey => $"{Agent}.{Tenant}.{Name}";
63
21
 
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
22
  public static IReadOnlyList<EvalScenario> LoadAll(string datasetRoot)
75
23
  {
76
24
  ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
@@ -85,6 +33,7 @@ public sealed record EvalScenario
85
33
 
86
34
  var files = Directory
87
35
  .EnumerateFiles(datasetRoot, "*.json", SearchOption.AllDirectories)
36
+ // Ordem ordinal: duas maquinas precisam gravar a mesma arvore de cache.
88
37
  .OrderBy(p => p.Replace('\\', '/'), StringComparer.Ordinal)
89
38
  .ToList();
90
39
 
@@ -97,7 +46,6 @@ public sealed record EvalScenario
97
46
  return scenarios;
98
47
  }
99
48
 
100
- /// <summary>Le um cenario, derivando agente e tenant do caminho relativo a raiz.</summary>
101
49
  public static EvalScenario Load(string datasetRoot, string file)
102
50
  {
103
51
  ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
@@ -126,9 +74,6 @@ public sealed record EvalScenario
126
74
  for (var i = 0; i < (dto.Assertions?.Count ?? 0); i++)
127
75
  {
128
76
  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
77
  var name = string.IsNullOrWhiteSpace(raw.Name) ? $"{raw.Kind}-{i + 1}" : raw.Name!;
133
78
  if (!used.Add(name))
134
79
  {
@@ -3,128 +3,55 @@ using System.Text.Json.Serialization;
3
3
 
4
4
  namespace Morph.AiKit.Evals;
5
5
 
6
- /// <summary>Estado da arvore de cache nesta execucao, como o sumario o reporta.</summary>
7
6
  public static class EvalCacheState
8
7
  {
9
- /// <summary>Todos os cenarios resolveram pela cache. Nenhuma chamada ao provider.</summary>
10
8
  public const string Warm = "warm";
11
9
 
12
- /// <summary>Ao menos um cenario foi ao provider — a arvore mudou e precisa ser commitada.</summary>
13
10
  public const string Cold = "cold";
14
11
  }
15
12
 
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
13
  public sealed record EvalFailureDetail(
36
14
  [property: JsonPropertyName("scenario")] string Scenario,
37
15
  [property: JsonPropertyName("assertion")] string Assertion,
38
16
  [property: JsonPropertyName("kind")] string Kind,
39
17
  [property: JsonPropertyName("reason")] string Reason)
40
18
  {
41
- /// <summary>Uma linha legivel, no formato que a rubrica cita.</summary>
42
19
  public override string ToString()
43
20
  => $"eval-fail: {Scenario} — {Assertion} ({Kind}): {Reason}";
44
21
  }
45
22
 
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
23
  public sealed record EvalRunSummary
65
24
  {
66
- /// <summary>Quantos cenarios foram avaliados.</summary>
67
25
  [JsonPropertyName("scenarios")]
68
26
  public required int Scenarios { get; init; }
69
27
 
70
- /// <summary>Quantos passaram em TODAS as suas assercoes deterministicas.</summary>
71
28
  [JsonPropertyName("passed")]
72
29
  public required int Passed { get; init; }
73
30
 
74
- /// <summary>Quantos falharam em ao menos uma assercao deterministica.</summary>
75
31
  [JsonPropertyName("failed")]
76
32
  public required int Failed { get; init; }
77
33
 
78
- /// <summary><see cref="EvalCacheState.Warm"/> ou <see cref="EvalCacheState.Cold"/>.</summary>
79
34
  [JsonPropertyName("cache")]
80
35
  public required string Cache { get; init; }
81
36
 
82
- /// <summary>Veredito do carimbo, em minusculas (<c>ok</c>, <c>missing</c>, ...).</summary>
83
37
  [JsonPropertyName("stamp")]
84
38
  public required string Stamp { get; init; }
85
39
 
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
40
  [JsonPropertyName("misses")]
91
41
  public required int Misses { get; init; }
92
42
 
93
- /// <summary>O <c>ProviderName</c> usado, o mesmo que entrou na chave.</summary>
94
43
  [JsonPropertyName("provider")]
95
44
  public required string Provider { get; init; }
96
45
 
97
- /// <summary>O <c>DefaultModelId</c> usado, o mesmo que entrou na chave.</summary>
98
46
  [JsonPropertyName("model")]
99
47
  public string? Model { get; init; }
100
48
 
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
49
  [JsonPropertyName("quality")]
107
50
  public IReadOnlyDictionary<string, double> Quality { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
108
51
 
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
52
  [JsonPropertyName("failures")]
119
53
  public IReadOnlyList<EvalFailureDetail> Failures { get; init; } = [];
120
54
 
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
55
  public string DescribeFailures()
129
56
  => Failures.Count == 0
130
57
  ? "nenhuma falha de eval registrada"
@@ -132,7 +59,6 @@ public sealed record EvalRunSummary
132
59
 
133
60
  private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
134
61
 
135
- /// <summary>Grava o sumario. Cria o diretorio se preciso; sobrescreve se ja existe.</summary>
136
62
  public void WriteTo(string path)
137
63
  {
138
64
  ArgumentException.ThrowIfNullOrWhiteSpace(path);
@@ -146,7 +72,6 @@ public sealed record EvalRunSummary
146
72
  File.WriteAllText(path, JsonSerializer.Serialize(this, Json));
147
73
  }
148
74
 
149
- /// <summary>Le um sumario gravado. Devolve nulo para arquivo ausente ou ilegivel — DESCONHECIDO, jamais zero.</summary>
150
75
  public static EvalRunSummary? ReadFrom(string path)
151
76
  {
152
77
  ArgumentException.ThrowIfNullOrWhiteSpace(path);