@polymorphism-tech/morph-spec 8.34.2 → 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 (325) hide show
  1. package/CLAUDE.md +20 -3
  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 +234 -9
  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/statusline.sh +11 -11
  26. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  27. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  28. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  29. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  30. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  31. package/framework/hooks/shared/e2e-check.js +135 -22
  32. package/framework/hooks/shared/stdin-reader.js +26 -26
  33. package/framework/rules/ai-agents-standards.md +14 -2
  34. package/framework/schemas/ai-pin.schema.json +97 -0
  35. package/framework/schemas/config.schema.json +107 -0
  36. package/framework/schemas/feature.schema.json +23 -4
  37. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  38. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  39. package/framework/skills/core/morph-apply/SKILL.md +26 -5
  40. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  41. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  42. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  43. package/framework/skills/core/morph-implement/SKILL.md +18 -2
  44. package/framework/skills/core/morph-media/SKILL.md +215 -0
  45. package/framework/skills/core/morph-plan/SKILL.md +33 -2
  46. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  47. package/framework/skills/core/morph-review/SKILL.md +166 -9
  48. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  49. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  50. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  51. package/framework/standards/PROVENANCE.json +81 -3
  52. package/framework/standards/STANDARDS.json +1483 -839
  53. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  54. package/framework/standards/ai-agents/agent-session.md +164 -68
  55. package/framework/standards/ai-agents/agent-spec.md +196 -0
  56. package/framework/standards/ai-agents/batching.md +61 -37
  57. package/framework/standards/ai-agents/context-providers.md +97 -24
  58. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  59. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  60. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  61. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  62. package/framework/standards/ai-agents/guardrails.md +222 -0
  63. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  64. package/framework/standards/ai-agents/mcp-server.md +119 -18
  65. package/framework/standards/ai-agents/mcp-tools.md +85 -32
  66. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  67. package/framework/standards/ai-agents/media-video.md +267 -0
  68. package/framework/standards/ai-agents/middleware-patterns.md +39 -5
  69. package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
  70. package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
  71. package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
  72. package/framework/standards/ai-agents/modalities-vision.md +6 -2
  73. package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
  74. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  75. package/framework/standards/ai-agents/production.md +38 -9
  76. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  77. package/framework/standards/ai-agents/providers/model-registry.md +174 -39
  78. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  79. package/framework/standards/ai-agents/service-tier-flex.md +27 -19
  80. package/framework/standards/ai-agents/setup.md +166 -50
  81. package/framework/standards/ai-agents/structured-output.md +34 -3
  82. package/framework/standards/ai-agents/sweet-spot.md +23 -3
  83. package/framework/standards/ai-agents/testing-ai.md +236 -0
  84. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  85. package/framework/standards/ai-agents/workflows.md +27 -7
  86. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  87. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  88. package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
  89. package/framework/templates/.idea/morph-templates.xml +92 -0
  90. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  91. package/framework/templates/IDE-SNIPPETS.md +262 -262
  92. package/framework/templates/REGISTRY.json +13 -0
  93. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  94. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  95. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  96. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  97. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
  98. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  99. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  100. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  101. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  102. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  103. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  104. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  105. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  106. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  107. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
  108. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  109. package/framework/templates/code/typescript/contracts.ts +167 -167
  110. package/framework/templates/docs/clarifications.md +253 -253
  111. package/framework/templates/docs/schema-analysis.md +119 -119
  112. package/framework/templates/docs/ui-components.md +52 -52
  113. package/framework/templates/docs/ui-design-system.md +76 -76
  114. package/framework/templates/docs/ui-flows.md +167 -167
  115. package/framework/templates/docs/ui-mockups.md +98 -98
  116. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  117. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  118. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  119. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  120. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  121. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  157. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  158. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  159. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  160. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  161. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  162. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  163. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  164. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  165. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  166. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  167. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  168. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  169. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  170. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  171. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  172. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  173. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  174. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  203. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  204. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  205. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  206. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  207. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  208. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  209. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  210. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  211. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  212. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  213. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  214. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  215. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  216. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  217. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  218. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  219. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  220. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  221. package/framework/templates/feature/decisions.md +122 -122
  222. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  223. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  224. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  225. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  226. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  227. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  228. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  229. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  230. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  231. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  232. package/framework/templates/infrastructure/github/README.md +56 -56
  233. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  234. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  235. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  236. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  237. package/framework/templates/integrations/asaas-client.cs +387 -387
  238. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  239. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  240. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  241. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  242. package/framework/templates/project/validate.js +120 -120
  243. package/framework/templates/state.template.json +17 -17
  244. package/framework/templates/ui/design-system.css +226 -226
  245. package/package.json +3 -3
  246. package/src/commands/dashboard/dashboard.js +53 -53
  247. package/src/commands/e2e/e2e.js +15 -2
  248. package/src/commands/index.js +13 -13
  249. package/src/commands/project/checkpoint.js +209 -209
  250. package/src/commands/project/doctor.js +140 -5
  251. package/src/commands/project/update.js +469 -153
  252. package/src/commands/project/worktree.js +8 -1
  253. package/src/commands/state/advance.js +24 -2
  254. package/src/commands/state/approve.js +33 -1
  255. package/src/commands/state/create.js +72 -11
  256. package/src/commands/state/finish.js +333 -21
  257. package/src/commands/state/gate-check.js +190 -6
  258. package/src/commands/state/score.js +36 -1
  259. package/src/commands/validation/verify.js +134 -25
  260. package/src/core/state/phase-state-machine.js +197 -197
  261. package/src/core/state/state-manager.js +9 -1
  262. package/src/core/templates/template-renderer.js +477 -477
  263. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  264. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  265. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  266. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  267. package/src/dashboard/app/main.jsx +13 -13
  268. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  269. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  270. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  271. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  272. package/src/dashboard/app/pages/Standards.jsx +332 -332
  273. package/src/dashboard/app/styles/index.css +47 -47
  274. package/src/dashboard/index.html +12 -12
  275. package/src/dashboard/package.json +24 -24
  276. package/src/dashboard/scanner/phases-scanner.js +15 -15
  277. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  278. package/src/dashboard/scanner/rules-scanner.js +37 -37
  279. package/src/dashboard/scanner/skills-scanner.js +43 -43
  280. package/src/dashboard/scanner/squads-scanner.js +13 -13
  281. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  282. package/src/dashboard/vite.config.js +24 -24
  283. package/src/lib/ai/ai-checks.js +182 -0
  284. package/src/lib/ai/ai-project-scan.js +347 -0
  285. package/src/lib/ai/code-audit.js +181 -0
  286. package/src/lib/ai/comment-stripper.js +184 -0
  287. package/src/lib/ai/pin-drift.js +138 -0
  288. package/src/lib/ai/registry-audit.js +285 -0
  289. package/src/lib/detectors/design-system-detector.js +188 -188
  290. package/src/lib/detectors/index.js +217 -217
  291. package/src/lib/e2e/compose-env.js +50 -4
  292. package/src/lib/evaluator-report.js +269 -15
  293. package/src/lib/generators/design-system-generator.js +508 -508
  294. package/src/lib/generators/settings-generator.js +209 -209
  295. package/src/lib/git/default-branch.js +92 -4
  296. package/src/lib/hooks/hook-executor.js +167 -167
  297. package/src/lib/installers/effects.js +193 -0
  298. package/src/lib/installers/mcp-installer.js +719 -719
  299. package/src/lib/installers/templates-reconcile.js +3 -3
  300. package/src/lib/risk-detector.js +284 -0
  301. package/src/lib/standards/verified-against.js +276 -0
  302. package/src/lib/tasks/eval-node.js +485 -0
  303. package/src/lib/tasks/linux-runner.js +297 -0
  304. package/src/lib/tasks/task-classifier.js +30 -30
  305. package/src/lib/tasks/task-parser.js +209 -209
  306. package/src/lib/tasks/test-nodes.js +486 -0
  307. package/src/lib/tasks/test-runner.js +410 -33
  308. package/src/lib/validators/checkpoint-persistence.js +39 -39
  309. package/src/lib/validators/css/css-validator.js +340 -340
  310. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  311. package/src/lib/validators/shared/issue-counter.js +14 -14
  312. package/src/lib/validators/verify-runner.js +583 -28
  313. package/src/lib/worktree/docker-audit.js +287 -0
  314. package/src/lib/worktree/env-allocator.js +24 -0
  315. package/src/lib/worktree/worktree-audit.js +30 -1
  316. package/src/scripts/setup-infra.js +24 -11
  317. package/src/utils/agent-id-mapper.js +11 -11
  318. package/src/utils/agents-installer.js +18 -8
  319. package/src/utils/claude-md-injector.js +10 -5
  320. package/src/utils/claude-settings-manager.js +300 -300
  321. package/src/utils/file-copier.js +11 -4
  322. package/src/utils/hooks-installer.js +29 -26
  323. package/src/utils/skills-installer.js +36 -29
  324. package/src/utils/version-checker.js +68 -3
  325. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -0,0 +1,485 @@
1
+ /**
2
+ * Eval Node — the `runner: "evals"` kind of `project.tests[]`.
3
+ *
4
+ * WHY THIS IS A RUNNER KIND AND NOT A SIXTH VERIFY NODE
5
+ *
6
+ * An eval IS `dotnet test` with a category filter: one command, one cwd, one
7
+ * clock, one exit code. Modelling it as something other than a test suite would
8
+ * mean a second copy of the executor, a second decision for `--filter`, a second
9
+ * decision for `--linux`, a second place for `expectedCount` — and a new line in
10
+ * a residency-capped charter that has zero slack. As a runner kind it inherits
11
+ * all of that for free, and the ONE thing the rollup cannot express (eval red vs
12
+ * unit-test red) is recovered by the verify stamp, which records `nodes.evals`
13
+ * separately.
14
+ *
15
+ * WHAT THIS MODULE OWNS
16
+ *
17
+ * - the STATES of a cache tree, judged BEFORE `dotnet test` is invoked. Paying
18
+ * for a whole suite to discover at the end that the cache did not serve is
19
+ * the worst possible ordering;
20
+ * - the `MORPH_EVAL_*` variables the CLI DICTATES, so the same xunit suite is
21
+ * runnable by `verify` without a second configuration path;
22
+ * - reading the summary the fixture writes — where **null is UNKNOWN, never
23
+ * zero**.
24
+ *
25
+ * WHAT IT DELIBERATELY DOES NOT OWN
26
+ *
27
+ * The pin. `readAiPin()` (src/lib/standards/verified-against.js) is the single
28
+ * reader of `framework/ai-pin.json`; a second parser here is how two truths
29
+ * about "which version is pinned" begin.
30
+ *
31
+ * @module eval-node
32
+ */
33
+
34
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
35
+ import { join, resolve, sep } from 'path';
36
+ import { tmpdir } from 'os';
37
+
38
+ import { isInside } from './test-runner.js';
39
+ import { readAiPin } from '../standards/verified-against.js';
40
+
41
+ /** The runner kind. One spelling, imported by every reader. */
42
+ export const EVAL_RUNNER = 'evals';
43
+
44
+ /** Stamp file at the root of a cache tree — the SAME name the .NET fixture writes. */
45
+ export const EVAL_STAMP_FILE = '.morph-eval-cache.json';
46
+
47
+ /** Default dataset root, relative to the cache root's parent. */
48
+ export const DEFAULT_DATASET_DIR = 'eval-datasets';
49
+
50
+ /** Default credential file. Gitignored, like `e2e.auth.envFile`; never in the committed config. */
51
+ export const DEFAULT_ENV_FILE = '.env.evals';
52
+
53
+ /**
54
+ * The pin entries that decide the FORMAT of a response cache.
55
+ *
56
+ * Only the `Microsoft.Extensions.AI` family: the cache key is computed by that
57
+ * library, and its own docs say the hash is not guaranteed stable across
58
+ * releases. Bumping an unrelated provider package (OpenAI, Google.GenAI) cannot
59
+ * invalidate a cache tree, and a stamp that reacted to it would fail for
60
+ * nothing — a guardrail that cries wolf gets switched off.
61
+ *
62
+ * @param {Record<string, {version: string}>} packages
63
+ * @returns {Record<string, string>} name → exact version
64
+ */
65
+ export function cacheRelevantPackages(packages) {
66
+ const out = {};
67
+ for (const [name, entry] of Object.entries(packages ?? {})) {
68
+ if (name === 'Microsoft.Extensions.AI' || name.startsWith('Microsoft.Extensions.AI.Evaluation')) {
69
+ out[name] = entry?.version;
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+
75
+ /**
76
+ * Read the pin from a project, wherever the framework lives.
77
+ *
78
+ * Two roots, ONE reader: this repository carries `framework/ai-pin.json` at the
79
+ * top; an installed project carries it under `.morph/framework/`. `readAiPin`
80
+ * appends `framework/ai-pin.json` to whatever root it is given, so both cases
81
+ * are the same function with a different argument — never a second parser.
82
+ *
83
+ * Returns null when neither exists: a project with no pin simply has no stamp
84
+ * ruler, and refusing to run there would make the eval node unusable outside
85
+ * this framework's own stack.
86
+ *
87
+ * @param {string} projectPath
88
+ * @returns {{pinnedAt: string, packages: object}|null}
89
+ */
90
+ export function readProjectPin(projectPath) {
91
+ for (const root of [projectPath, join(projectPath, '.morph')]) {
92
+ try {
93
+ return readAiPin(root);
94
+ } catch {
95
+ // Next candidate. The throw is readAiPin's contract for "no pin here",
96
+ // and it is the reason this loop exists instead of an existsSync probe
97
+ // that would duplicate the path knowledge.
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ /**
104
+ * Resolve every path an eval node names, against the project root.
105
+ *
106
+ * @param {import('./test-nodes.js').TestNode & {evals?: object}} node
107
+ * @param {string} projectPath
108
+ * @returns {{cacheRoot: string, datasetRoot: string, resultsRoot: string, envFile: string}}
109
+ */
110
+ export function resolveEvalPaths(node, projectPath) {
111
+ const root = resolve(projectPath);
112
+ const evals = node?.evals ?? {};
113
+ const cacheRoot = resolve(root, evals.cachePath ?? '');
114
+ const beside = resolve(cacheRoot, '..');
115
+
116
+ return {
117
+ cacheRoot,
118
+ datasetRoot: evals.datasetPath ? resolve(root, evals.datasetPath) : join(beside, DEFAULT_DATASET_DIR),
119
+ // Results live OUTSIDE the committed cache tree by construction — they carry
120
+ // stopwatch latency, token usage and the cache-hit flag, so committing them
121
+ // is a guaranteed diff on every run with no signal in it.
122
+ resultsRoot: join(beside, 'eval-results'),
123
+ envFile: resolve(root, evals.envFile ?? DEFAULT_ENV_FILE),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * First problem with a node's `evals` block, or null when it is well formed.
129
+ *
130
+ * Fail-CLOSED, like the rest of `validateTestNodes`: a typo here would make the
131
+ * eval silently disappear, which is the exact defect this feature exists to
132
+ * prevent.
133
+ *
134
+ * @param {*} raw - the raw `tests[]` entry
135
+ * @param {string|null} projectPath
136
+ * @returns {string|null}
137
+ */
138
+ export function evalBlockProblem(raw, projectPath) {
139
+ const evals = raw?.evals;
140
+
141
+ if (evals === undefined) {
142
+ return 'runner "evals" requires an evals.cachePath — a default would create a cache tree somewhere nobody declared';
143
+ }
144
+ if (typeof evals !== 'object' || evals === null || Array.isArray(evals)) {
145
+ return 'evals must be an object';
146
+ }
147
+ if (typeof evals.cachePath !== 'string' || !evals.cachePath.trim()) {
148
+ return 'evals.cachePath must be a non-empty string';
149
+ }
150
+ for (const key of ['datasetPath', 'envFile']) {
151
+ if (evals[key] !== undefined && (typeof evals[key] !== 'string' || !evals[key].trim())) {
152
+ return `evals.${key} must be a non-empty string`;
153
+ }
154
+ }
155
+
156
+ const root = resolve(projectPath ?? '.');
157
+ for (const key of ['cachePath', 'datasetPath']) {
158
+ const value = evals[key];
159
+ if (typeof value !== 'string' || !value.trim()) continue;
160
+ // Same rule as `cwd`: a path that leaves the root addresses ANOTHER tree.
161
+ if (!isInside(resolve(root, value.trim()), root)) {
162
+ return `evals.${key} "${value}" resolves outside the project root`;
163
+ }
164
+ }
165
+
166
+ return null;
167
+ }
168
+
169
+ /** Does this cache tree hold at least one entry, beyond the stamp? */
170
+ function cacheHasEntries(cacheRoot) {
171
+ const walk = (dir) => {
172
+ let entries;
173
+ try {
174
+ entries = readdirSync(dir, { withFileTypes: true });
175
+ } catch {
176
+ return false;
177
+ }
178
+ for (const entry of entries) {
179
+ const full = join(dir, entry.name);
180
+ if (entry.isDirectory()) {
181
+ if (walk(full)) return true;
182
+ } else if (entry.name !== EVAL_STAMP_FILE) {
183
+ return true;
184
+ }
185
+ }
186
+ return false;
187
+ };
188
+ return walk(cacheRoot);
189
+ }
190
+
191
+ /** Read the stamp the .NET fixture wrote. null for absent OR unreadable — Check tells them apart. */
192
+ export function readEvalStamp(cacheRoot) {
193
+ const path = join(cacheRoot, EVAL_STAMP_FILE);
194
+ if (!existsSync(path)) return null;
195
+ try {
196
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
197
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Decide, BEFORE spending a single second of suite clock, whether this eval node
205
+ * can run.
206
+ *
207
+ * The order is the order of the spec, and it is not arbitrary: the three cache
208
+ * states are judged before the command is invoked, because discovering at the
209
+ * END of a suite that the cache did not serve costs the whole clock and tells
210
+ * the reader nothing they could not have been told first.
211
+ *
212
+ * **There is no `skip` for a bad cache.** A skip here would recreate the hole
213
+ * this harness closed twice: a green gate over a verification that never
214
+ * happened. The only skip is scope — an eval suite does not belong in the
215
+ * per-task loop.
216
+ *
217
+ * @param {string} projectPath
218
+ * @param {object} node - a resolved test node with `runner === 'evals'`
219
+ * @param {{taskId?: string|null, refresh?: boolean}} [options]
220
+ * @returns {{status: 'skip'|'fail'|null, reason: string|null}} status null means RUN
221
+ */
222
+ export function evalPreflight(projectPath, node, options = {}) {
223
+ const { taskId = null, refresh = false } = options;
224
+
225
+ if (node?.runner !== EVAL_RUNNER) return { status: null, reason: null };
226
+
227
+ // Scope first: an eval suite does not pay for itself in a per-task loop, and
228
+ // the result Gate 3 reads is the feature-scoped one. Same treatment `e2e: true`
229
+ // already gets.
230
+ if (taskId) {
231
+ return { status: 'skip', reason: 'eval node runs at feature scope only' };
232
+ }
233
+
234
+ const { cacheRoot, envFile } = resolveEvalPaths(node, projectPath);
235
+
236
+ // In refresh the tree is being (re)written; demanding a populated, stamped
237
+ // cache would make the FIRST cache impossible to create.
238
+ if (refresh) {
239
+ if (!existsSync(envFile)) {
240
+ return {
241
+ status: 'fail',
242
+ reason: `eval-refresh-no-credential: --refresh-cache exige o arquivo de credencial "${envFile}", que não existe`,
243
+ };
244
+ }
245
+ return { status: null, reason: null };
246
+ }
247
+
248
+ if (!existsSync(cacheRoot)) {
249
+ return { status: 'fail', reason: `eval-cache-missing: ${cacheRoot}` };
250
+ }
251
+
252
+ if (!cacheHasEntries(cacheRoot)) {
253
+ // Distinct from `missing` on purpose: an empty COMMITTED directory is far
254
+ // more likely a `.gitignore` mistake than a deliberate absence, and the two
255
+ // ask for different fixes.
256
+ return { status: 'fail', reason: `eval-cache-empty: ${cacheRoot} não contém nenhuma entrada de cache` };
257
+ }
258
+
259
+ const pin = readProjectPin(projectPath);
260
+ if (!pin) {
261
+ // No pin, no ruler — and a project without one is a legitimate case, not a
262
+ // failure: the stamp check simply does not apply. Saying so is the point;
263
+ // silently treating it as "stamp ok" would be the same thing said badly.
264
+ return { status: null, reason: null };
265
+ }
266
+
267
+ const stamp = readEvalStamp(cacheRoot);
268
+ if (!stamp) {
269
+ return {
270
+ status: 'fail',
271
+ reason: `eval-cache-unstamped: ${join(cacheRoot, EVAL_STAMP_FILE)} ausente ou ilegível`
272
+ + ' — a árvore não diz sob quais versões foi gravada; rode verify --refresh-cache',
273
+ };
274
+ }
275
+
276
+ const expected = cacheRelevantPackages(pin.packages);
277
+ const recorded = stamp.packages ?? {};
278
+ const divergent = Object.entries(expected)
279
+ .filter(([name, version]) => recorded[name] !== version)
280
+ .map(([name, version]) => `${name}: gravada ${recorded[name] ?? '(ausente)'} vs pin ${version}`);
281
+
282
+ if (divergent.length > 0) {
283
+ return {
284
+ status: 'fail',
285
+ reason: `eval-cache-stale-pin: ${divergent.join('; ')} — rode verify --refresh-cache`,
286
+ };
287
+ }
288
+
289
+ return { status: null, reason: null };
290
+ }
291
+
292
+ /**
293
+ * The `MORPH_EVAL_*` variables the CLI dictates for one eval node.
294
+ *
295
+ * The summary path is chosen HERE, by the CLI, and not by the fixture: two eval
296
+ * nodes in the same `verify` must not collide, and the process that reads the
297
+ * file is the one that should decide where it lives.
298
+ *
299
+ * @param {object} node
300
+ * @param {string} projectPath
301
+ * @param {{refresh?: boolean}} [options]
302
+ * @returns {{env: Record<string,string>, summaryPath: string}}
303
+ */
304
+ export function evalNodeEnv(node, projectPath, options = {}) {
305
+ const { refresh = false } = options;
306
+ const { cacheRoot, datasetRoot, resultsRoot, envFile } = resolveEvalPaths(node, projectPath);
307
+ const summaryPath = join(tmpdir(), `morph-eval-${node.id}-${process.pid}.json`);
308
+
309
+ const env = {
310
+ MORPH_EVAL_CACHE: cacheRoot,
311
+ MORPH_EVAL_DATASET: datasetRoot,
312
+ MORPH_EVAL_RESULTS: resultsRoot,
313
+ MORPH_EVAL_MODE: refresh ? 'refresh' : 'offline',
314
+ MORPH_EVAL_SUMMARY: summaryPath,
315
+ MORPH_EVAL_ENVFILE: envFile,
316
+ };
317
+
318
+ const pin = readProjectPin(projectPath);
319
+ if (pin) {
320
+ // Compact, and only the packages that decide the cache format. The fixture
321
+ // stamps from THIS, so it never has to read (or parse) the pin itself.
322
+ env.MORPH_EVAL_PIN = JSON.stringify({
323
+ pinnedAt: pin.pinnedAt,
324
+ packages: cacheRelevantPackages(pin.packages),
325
+ });
326
+ }
327
+
328
+ // The credential file goes on top, same pattern as `e2e.auth.envFile`. Absent
329
+ // is NOT an error offline: that is the whole point of a warm cache.
330
+ Object.assign(env, readEnvFile(envFile));
331
+
332
+ return { env, summaryPath };
333
+ }
334
+
335
+ /**
336
+ * Parse a `KEY=value` file. Blank lines and `#` comments ignored; surrounding
337
+ * quotes stripped; the first `=` splits.
338
+ *
339
+ * Fail-open to `{}`: an unreadable credential file must not take down a run that
340
+ * did not need a credential in the first place.
341
+ *
342
+ * @param {string} path
343
+ * @returns {Record<string,string>}
344
+ */
345
+ export function readEnvFile(path) {
346
+ if (!path || !existsSync(path)) return {};
347
+ let text;
348
+ try {
349
+ text = readFileSync(path, 'utf8');
350
+ } catch {
351
+ return {};
352
+ }
353
+
354
+ const out = {};
355
+ for (const line of text.split(/\r?\n/)) {
356
+ const trimmed = line.trim();
357
+ if (!trimmed || trimmed.startsWith('#')) continue;
358
+ const eq = trimmed.indexOf('=');
359
+ if (eq <= 0) continue;
360
+ const key = trimmed.slice(0, eq).trim();
361
+ let value = trimmed.slice(eq + 1).trim();
362
+ if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
363
+ value = value.slice(1, -1);
364
+ }
365
+ if (key) out[key] = value;
366
+ }
367
+ return out;
368
+ }
369
+
370
+ /**
371
+ * Read the summary the fixture wrote.
372
+ *
373
+ * @param {string} path
374
+ * @returns {object|null} **null is UNKNOWN, never zero.** A summary nobody could
375
+ * read is not a suite that evaluated zero scenarios: collapsing the two would
376
+ * manufacture a count drop against `expectedCount` out of a missing file.
377
+ */
378
+ export function readEvalSummary(path) {
379
+ if (!path || !existsSync(path)) return null;
380
+ try {
381
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
382
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
383
+ return parsed;
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+
389
+ /**
390
+ * How many scenarios an eval run evaluated.
391
+ *
392
+ * The summary wins when it is readable; otherwise the caller falls back to the
393
+ * `dotnet` extractor, because an eval IS `dotnet test`. **No new extractor is
394
+ * invented**, and an unreadable summary yields null, never 0.
395
+ *
396
+ * @param {object|null} summary
397
+ * @returns {number|null}
398
+ */
399
+ export function evalScenarioCount(summary) {
400
+ const n = summary?.scenarios;
401
+ return Number.isInteger(n) ? n : null;
402
+ }
403
+
404
+ /**
405
+ * The NAMED failures of an eval run, one readable line each.
406
+ *
407
+ * The summary publishes `failures[]` (scenario, assertion, kind, reason) and
408
+ * without this reader the field would be written and never surfaced — a
409
+ * published field with no consumer, the same shape #91 closed on `superseded`.
410
+ * Worse here than usual: the whole point of `failures[]` is that a red eval
411
+ * names what broke, and a name nobody prints is not a name.
412
+ *
413
+ * Defensive by construction, because this parses a file produced by ANOTHER
414
+ * process: a summary that is absent, unreadable, or carries a malformed entry
415
+ * yields fewer lines, never a throw. A crash here would turn "the eval failed"
416
+ * into "the verify crashed", which reports the wrong defect.
417
+ *
418
+ * @param {object|null} summary
419
+ * @returns {string[]} readable lines; empty when there is nothing to name
420
+ */
421
+ export function evalFailureLines(summary) {
422
+ const failures = summary?.failures;
423
+ if (!Array.isArray(failures)) return [];
424
+
425
+ return failures
426
+ .filter((f) => f && typeof f === 'object')
427
+ .map((f) => {
428
+ const scenario = typeof f.scenario === 'string' && f.scenario ? f.scenario : '(cenário sem nome)';
429
+ const assertion = typeof f.assertion === 'string' && f.assertion ? f.assertion : '(asserção sem nome)';
430
+ const kind = typeof f.kind === 'string' && f.kind ? f.kind : '(régua desconhecida)';
431
+ const reason = typeof f.reason === 'string' && f.reason ? f.reason : '(sem motivo registrado)';
432
+ return `eval-fail: ${scenario} — ${assertion} (${kind}): ${reason}`;
433
+ });
434
+ }
435
+
436
+ /**
437
+ * Why an otherwise-green eval run must still be red, or null when it is fine.
438
+ *
439
+ * One case, and it is the guardrail-inert shape this repository has already paid
440
+ * to learn: a suite that evaluated ZERO scenarios and exited 0. The exit code
441
+ * alone cannot see it, because nothing failed — nothing ran.
442
+ *
443
+ * @param {object|null} summary
444
+ * @returns {string|null}
445
+ */
446
+ export function evalEmptyRunProblem(summary) {
447
+ if (summary && summary.scenarios === 0) {
448
+ return 'eval-empty-run: a suíte de eval avaliou 0 cenários e saiu verde';
449
+ }
450
+ return null;
451
+ }
452
+
453
+ /**
454
+ * Warning for a run whose cache went cold — the tree changed and must be committed.
455
+ *
456
+ * A warning and not a failure, deliberately: spending money nobody asked for is
457
+ * bad, but lying about the result is worse. Without the warning, a freshly
458
+ * written cache stays on the disk of whoever ran it and CI misses tomorrow.
459
+ *
460
+ * @param {object|null} summary
461
+ * @returns {string|null}
462
+ */
463
+ export function evalColdCacheWarning(summary) {
464
+ if (summary?.cache === 'cold') {
465
+ const misses = Number.isInteger(summary.misses) ? summary.misses : '?';
466
+ return `a árvore de cache mudou (${misses} miss(es)) — commite ${EVAL_STAMP_FILE} e as entradas novas`;
467
+ }
468
+ return null;
469
+ }
470
+
471
+ /** Relative form of a path, for messages that should not shout an absolute path. */
472
+ export function relativeToProject(projectPath, target) {
473
+ const root = resolve(projectPath) + sep;
474
+ const full = resolve(target);
475
+ return full.startsWith(root) ? full.slice(root.length).replace(/\\/g, '/') : full;
476
+ }
477
+
478
+ /** Is `path` a directory? Fail-open to false. */
479
+ export function isDirectory(path) {
480
+ try {
481
+ return statSync(path).isDirectory();
482
+ } catch {
483
+ return false;
484
+ }
485
+ }