@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
@@ -15,6 +15,41 @@
15
15
  * checagem por `existsSync` puro do `approve` aceitava um relatório SEM o token
16
16
  * que o `gate-check` então descartava — o Gate 3 passava carimbado `auto` sobre
17
17
  * uma média de auto-avaliação, exatamente o que MORPH.md §5 proíbe.
18
+ *
19
+ * ── O TOKEN VIGENTE É O ÚLTIMO ──────────────────────────────────────────────
20
+ *
21
+ * O `evaluator-report.md` é APPEND-ONLY por contrato: uma reavaliação acrescenta
22
+ * ao fim, preservando a análise anterior. Este módulo lia o COMEÇO (`String.match`
23
+ * com regex sem flag `g`), então num relatório reavaliado o gate era decidido
24
+ * pelo score SUPERADO. Medido em campo três vezes na mesma epic — #90 (8.7 →
25
+ * 9.4), #92 (8.2 → 9.0) e #93 (8.9 → 8.3 → 9.1) — e nas três o avaliador teve de
26
+ * desarmar o token antigo à mão para o gate ler a nota vigente.
27
+ *
28
+ * O caso perigoso é o INVERSO e é silencioso: numa reavaliação que BAIXA a nota
29
+ * (o #93 caiu de 8.9 para 8.3 quando uma correção abriu um bloqueante), o gate
30
+ * leria a nota antiga, mais alta, e AUTO-PASSARIA o Gate 3 sobre um produto com
31
+ * bloqueante aberto — a mesma classe de defeito que a v8.26.0 existiu para
32
+ * fechar, voltando por outra porta: lendo um veredito real, porém vencido.
33
+ *
34
+ * DECISÃO EXPLÍCITA — "o último vence", e não "múltiplos tokens viram
35
+ * `report-ambiguous` e recusam":
36
+ *
37
+ * 1. É a semântica do próprio artefato. Append-only significa que o arquivo é
38
+ * um LOG, e o valor corrente de um log é a sua última entrada. Ler o
39
+ * primeiro não é uma política conservadora — é ler a entrada errada.
40
+ * 2. Recusar transformaria um veredito REAL e CORRENTE numa pausa que só sai
41
+ * editando à mão um arquivo declarado append-only. Isto é exatamente o
42
+ * contorno manual que este conserto existe para eliminar, promovido a
43
+ * requisito.
44
+ * 3. Recusar não é mais seguro em nenhum dos dois casos observados: com a nota
45
+ * SUBINDO, o humano é obrigado a mexer no relatório para liberar um gate
46
+ * que já foi aprovado; com a nota CAINDO, "o último vence" já pausa sozinho
47
+ * pela regra de score, que é a razão certa a reportar.
48
+ *
49
+ * Ambiguidade não fica silenciosa, ainda assim: `evaluatorReportState` devolve
50
+ * `scoreTokens` (quantos tokens vivos o arquivo tem) e `superseded` (os valores
51
+ * anteriores, na ordem do arquivo). Quem consome pode dizer "9.1, terceira
52
+ * rodada, superando 8.9 e 8.3" em vez de fingir que só havia um veredito.
18
53
  */
19
54
 
20
55
  import { existsSync, readFileSync } from 'fs';
@@ -27,29 +62,133 @@ import { getAbsoluteOutputPath } from '../core/paths/output-schema.js';
27
62
  */
28
63
  export const COMPOSITE_SCORE_TOKEN = '**Composite-Score:**';
29
64
 
30
- const COMPOSITE_SCORE_RE = /\*\*Composite-Score:\*\*\s*([0-9]+(?:\.[0-9]+)?)/;
65
+ /**
66
+ * O token das rubricas de PROVA (`.morph/framework/evals/proof-rubrics/`):
67
+ * `**Proof-Scores:** mutation=9 verde=8 fiacao=9 guard=7 eval=8`. Mesma convenção
68
+ * literal do Composite-Score de propósito — mesmo arquivo, mesmo módulo leitor,
69
+ * mesmo estilo de token. Ele pontua a EVIDÊNCIA que o avaliador produziu, nunca
70
+ * o produto: não entra no composto, impõe teto em `test-coverage`.
71
+ */
72
+ export const PROOF_SCORES_TOKEN = '**Proof-Scores:**';
73
+
74
+ /** `g` é obrigatório: o arquivo é append-only e o token vigente é o ÚLTIMO. */
75
+ const COMPOSITE_SCORE_RE = /\*\*Composite-Score:\*\*\s*([0-9]+(?:\.[0-9]+)?)/g;
76
+
77
+ /** Idem — uma reavaliação reescreve os Proof-Scores acrescentando ao fim. */
78
+ const PROOF_SCORES_RE = /\*\*Proof-Scores:\*\*[ \t]*([^\r\n]*)/g;
79
+
80
+ /**
81
+ * A seção de prova de mutação. Exige `##`: um `# Mutações` é outra estrutura
82
+ * (H1 é título de documento, não seção), e o template que a `morph-review`
83
+ * entrega ao avaliador gera exatamente `## Mutações`. Aceita a grafia sem
84
+ * acento porque um relatório escrito em terminal ASCII continua sendo o mesmo
85
+ * fato — o que não se aceita é o nível errado de cabeçalho.
86
+ */
87
+ const MUTATION_SECTION_RE = /^##\s+Muta[çc][õo]es\s*$/m;
88
+
89
+ /**
90
+ * A seção de prova de MÍDIA. Mesma disciplina do `## Mutações`: exige `##`, e
91
+ * aceita a grafia sem acento porque um relatório escrito em terminal ASCII
92
+ * continua sendo o mesmo fato. `Mídia gerada` / `Midia gerada`.
93
+ */
94
+ const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
95
+
96
+ /**
97
+ * As chaves reconhecidas de `**Proof-Scores:**`, uma por rubrica de prova.
98
+ * Chave fora desta lista é IGNORADA (não vira score, não vira erro): o relatório
99
+ * é texto de LLM e uma chave inventada não deve nem contaminar nenhum mínimo nem
100
+ * derrubar a leitura das outras.
101
+ *
102
+ * `media` PRECISA estar aqui. Sem isso a rubrica `media-claims.md` seria
103
+ * decorativa: o avaliador escreveria `media=7` e o número seria descartado em
104
+ * silêncio, que é o caso "campo publicado sem nenhum leitor".
105
+ */
106
+ export const PROOF_SCORE_KEYS = ['mutation', 'verde', 'fiacao', 'guard', 'eval', 'media', 'obs'];
107
+
108
+ /**
109
+ * As chaves que NÃO entram no `min` de `test-coverage` porque impõem teto em
110
+ * OUTRA dimensão: `media` (teto em `architecture`) e `obs` (teto em
111
+ * `code-quality`).
112
+ *
113
+ * **`obs` chegou exatamente pelo caminho que o parágrafo abaixo antecipou**, e
114
+ * vale registrar porque o aviso não bastou. A feature irmã (#94) criou a rubrica
115
+ * `observabilidade-genai.md`, escreveu `obs=` onze vezes no seu próprio
116
+ * `evaluator-report.md` — e a chave não estava em `PROOF_SCORE_KEYS`, nem lá nem
117
+ * na base dela. O parser a descartava em silêncio (ver o `continue` de
118
+ * `parseProofScores`), então a rubrica nasceu DECORATIVA e atravessou **onze
119
+ * rodadas de Gate 3** sem que ninguém percebesse: o número era escrito, lido por
120
+ * um humano no relatório, e nunca chegava ao gate. Só o rebase, que põe as duas
121
+ * features na mesma árvore, tornou o buraco visível.
122
+ *
123
+ * **Isto é uma exclusão, e a forma importa mais que a lista.** A primeira versão
124
+ * era o inverso — uma lista fechada das quatro chaves que entram. Ela responde
125
+ * certo hoje e erra sozinha amanhã: a feature irmã acrescenta uma quinta chave
126
+ * (`eval`) com rubrica própria, e depois do rebase esse `eval=N` cairia do teto
127
+ * **em silêncio** — o "campo publicado sem nenhum leitor" que este mesmo arquivo
128
+ * existe para impedir, produzido pela correção.
129
+ *
130
+ * Por exclusão, uma chave nova nasce DENTRO do mínimo por padrão, e tirá-la de lá
131
+ * passa a ser um ato deliberado e visível: uma linha aqui, com a dimensão que ela
132
+ * limita escrita ao lado. Errar para o lado de "conta no mínimo" custa um teto a
133
+ * mais; errar para o outro lado devolve o gate cego.
134
+ *
135
+ * O motivo de `media` estar fora continua o mesmo: as quatro do mínimo perguntam
136
+ * "a evidência é real?"; `media` pergunta "a chamada mora no lugar certo? que
137
+ * estados existem? o que é publicado?". Um `media=4` derrubando o teto de TESTE
138
+ * seriam dois fatos diferentes lidos como um só.
139
+ */
140
+ export const NON_MUTATION_PROOF_KEYS = ['media', 'obs'];
141
+
142
+ /**
143
+ * As chaves que entram no `min` que impõe teto em `test-coverage`: DERIVADA de
144
+ * {@link PROOF_SCORE_KEYS} menos {@link NON_MUTATION_PROOF_KEYS} — nunca digitada.
145
+ * Ver o racional da exclusão acima.
146
+ */
147
+ export const MUTATION_PROOF_KEYS =
148
+ PROOF_SCORE_KEYS.filter((k) => !NON_MUTATION_PROOF_KEYS.includes(k));
31
149
 
32
150
  /** Path canônico do evaluator-report da feature. Fonte única para os dois comandos. */
33
151
  export function evaluatorReportPath(cwd, featureName) {
34
152
  return getAbsoluteOutputPath(cwd, featureName, 'evaluatorReport');
35
153
  }
36
154
 
155
+ /** Conteúdo do relatório, ou null quando ausente/ilegível. Nunca lança. */
156
+ function readReport(cwd, featureName) {
157
+ try {
158
+ const p = evaluatorReportPath(cwd, featureName);
159
+ if (!existsSync(p)) return null;
160
+ return readFileSync(p, 'utf8');
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Todos os valores de `**Composite-Score:**` do arquivo, NA ORDEM DO ARQUIVO.
168
+ * O último é o vigente; os anteriores são rodadas superadas.
169
+ *
170
+ * @param {string} text
171
+ * @returns {number[]}
172
+ */
173
+ function compositeScoresIn(text) {
174
+ return [...text.matchAll(COMPOSITE_SCORE_RE)].map((m) => Number(m[1]));
175
+ }
176
+
37
177
  /**
38
178
  * Score composto REAL da feature, do token gravado pelo avaliador independente.
39
179
  *
180
+ * Num relatório reavaliado (append-only), é o token do FIM — o veredito
181
+ * vigente. Ver o bloco "O TOKEN VIGENTE É O ÚLTIMO" no topo do arquivo.
182
+ *
40
183
  * @returns {number|undefined} undefined quando o relatório não existe, não é
41
184
  * legível, ou existe sem o token (relatório legado ou oco). `undefined` é
42
185
  * deliberado e não 0: quem chama distingue "sem veredito" de "veredito ruim".
43
186
  */
44
187
  export function evaluatorCompositeScore(cwd, featureName) {
45
- try {
46
- const p = evaluatorReportPath(cwd, featureName);
47
- if (!existsSync(p)) return undefined;
48
- const m = readFileSync(p, 'utf8').match(COMPOSITE_SCORE_RE);
49
- return m ? Number(m[1]) : undefined;
50
- } catch {
51
- return undefined;
52
- }
188
+ const text = readReport(cwd, featureName);
189
+ if (text === null) return undefined;
190
+ const scores = compositeScoresIn(text);
191
+ return scores.length === 0 ? undefined : scores[scores.length - 1];
53
192
  }
54
193
 
55
194
  /**
@@ -60,13 +199,128 @@ export function evaluatorCompositeScore(cwd, featureName) {
60
199
  * "o avaliador rodou mas o relatório está oco — ele não seguiu o contrato";
61
200
  * e só o terceiro caso é aprovável.
62
201
  *
63
- * @returns {{ status: 'ok'|'missing'|'untokenized', score?: number, path: string }}
202
+ * `scoreTokens`/`superseded` só aparecem no caso `ok` e existem para que uma
203
+ * reavaliação nunca passe silenciosa: o score é o último, mas quem consome sabe
204
+ * quantas rodadas o arquivo carrega e quais notas foram superadas.
205
+ *
206
+ * @returns {{ status: 'ok'|'missing'|'untokenized', score?: number,
207
+ * scoreTokens?: number, superseded?: number[], path: string }}
64
208
  */
65
209
  export function evaluatorReportState(cwd, featureName) {
66
210
  const path = evaluatorReportPath(cwd, featureName);
67
- if (!existsSync(path)) return { status: 'missing', path };
68
- const score = evaluatorCompositeScore(cwd, featureName);
69
- return score === undefined
70
- ? { status: 'untokenized', path }
71
- : { status: 'ok', score, path };
211
+ const text = readReport(cwd, featureName);
212
+ if (text === null) return { status: 'missing', path };
213
+ const scores = compositeScoresIn(text);
214
+ if (scores.length === 0) return { status: 'untokenized', path };
215
+ return {
216
+ status: 'ok',
217
+ score: scores[scores.length - 1],
218
+ scoreTokens: scores.length,
219
+ superseded: scores.slice(0, -1),
220
+ path,
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Parseia o corpo de UMA linha `**Proof-Scores:**` — pares `chave=N` separados
226
+ * por espaço.
227
+ *
228
+ * @param {string} body
229
+ * @returns {Record<string, number>|undefined} undefined quando nenhum par
230
+ * VÁLIDO foi lido (linha vazia, só chaves desconhecidas, ou só valores
231
+ * não-numéricos). `undefined` e não `{}` pela mesma disciplina do
232
+ * `evaluatorCompositeScore`: "sem veredito" tem de ser distinguível de
233
+ * "veredito vazio".
234
+ */
235
+ function parseProofScores(body) {
236
+ const out = {};
237
+ for (const m of body.matchAll(/([A-Za-zÀ-ÿ][\w-]*)\s*=\s*([^\s]+)/g)) {
238
+ const key = m[1].toLowerCase();
239
+ if (!PROOF_SCORE_KEYS.includes(key)) continue;
240
+ if (!/^[0-9]+(?:\.[0-9]+)?$/.test(m[2])) continue;
241
+ out[key] = Number(m[2]);
242
+ }
243
+ return Object.keys(out).length === 0 ? undefined : out;
244
+ }
245
+
246
+ /**
247
+ * Estado de PROVA do relatório: a seção `## Mutações` está lá, e quais
248
+ * Proof-Scores o avaliador registrou.
249
+ *
250
+ * Deriva do disco e não julga: quem decide se a ausência pausa o Gate 3 é
251
+ * `risk-detector.js` (a divisão "gate-check coleta, risk-detector decide"), e
252
+ * quem decide o teto é a rubrica `test-coverage.md`. O `min` NÃO é gravado
253
+ * aqui — é derivado por quem consome, para que exista um só lugar onde
254
+ * "mínimo de quantas chaves" é definido.
255
+ *
256
+ * Arquivo ausente ou ilegível: `{ mutationSection: false, mediaSection: false,
257
+ * proofScores: undefined }`. Nunca lança, e nunca devolve `0` no lugar de
258
+ * `undefined`.
259
+ *
260
+ * `mediaSection` é lido do MESMO arquivo pelo MESMO módulo, de propósito: uma
261
+ * segunda leitura do relatório noutro lugar é como duas superfícies passam a
262
+ * discordar sobre o que ele diz.
263
+ *
264
+ * @param {string} cwd
265
+ * @param {string} featureName
266
+ * @returns {{ mutationSection: boolean, mediaSection: boolean, proofScores: Record<string, number>|undefined, path: string }}
267
+ */
268
+ export function evaluatorProofState(cwd, featureName) {
269
+ const path = evaluatorReportPath(cwd, featureName);
270
+ const text = readReport(cwd, featureName);
271
+ if (text === null) {
272
+ return { mutationSection: false, mediaSection: false, proofScores: undefined, path };
273
+ }
274
+
275
+ // Append-only outra vez: numa reavaliação a linha nova vai para o fim, e é
276
+ // ela que vale. A primeira linha descreveria a prova de uma rodada superada.
277
+ const lines = [...text.matchAll(PROOF_SCORES_RE)];
278
+ const proofScores = lines.length === 0
279
+ ? undefined
280
+ : parseProofScores(lines[lines.length - 1][1]);
281
+
282
+ return {
283
+ mutationSection: MUTATION_SECTION_RE.test(text),
284
+ mediaSection: MEDIA_SECTION_RE.test(text),
285
+ proofScores,
286
+ path,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * O menor Proof-Score das provas de EVIDÊNCIA — o que impõe o teto em
292
+ * `test-coverage`.
293
+ *
294
+ * Calculado sobre {@link MUTATION_PROOF_KEYS} — que é
295
+ * {@link PROOF_SCORE_KEYS} MENOS {@link NON_MUTATION_PROOF_KEYS}, e não uma lista
296
+ * digitada. `media` responde a outra pergunta e limita outra dimensão
297
+ * (`architecture`); ler as duas famílias como um mínimo só faria um `media=4`
298
+ * derrubar o teto de teste sem que nada de teste tivesse acontecido. Uma chave
299
+ * NOVA de prova entra aqui sozinha: excluí-la exige uma linha deliberada em
300
+ * `NON_MUTATION_PROOF_KEYS`, e nunca acontece por esquecimento.
301
+ *
302
+ * @param {Record<string, number>|undefined} proofScores
303
+ * @returns {number|null} null quando não há nenhum score do mínimo: SEM TETO A
304
+ * APLICAR, jamais "teto 0". A mesma razão pela qual `count: null` do verify
305
+ * não é 0.
306
+ */
307
+ export function minProofScore(proofScores) {
308
+ if (!proofScores) return null;
309
+ const values = MUTATION_PROOF_KEYS
310
+ .map((key) => proofScores[key])
311
+ .filter((n) => typeof n === 'number' && Number.isFinite(n));
312
+ return values.length === 0 ? null : Math.min(...values);
313
+ }
314
+
315
+ /**
316
+ * O Proof-Score de MÍDIA, lido à parte. Impõe teto em `architecture` (tabela em
317
+ * `framework/evals/rubrics/architecture.md`, "Tetos de prova de mídia").
318
+ *
319
+ * @param {Record<string, number>|undefined} proofScores
320
+ * @returns {number|null} null quando a chave não foi registrada — o caso normal
321
+ * de uma feature que não gera mídia, e nunca "teto 0".
322
+ */
323
+ export function mediaProofScore(proofScores) {
324
+ const value = proofScores?.media;
325
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
72
326
  }