@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
@@ -16,8 +16,8 @@
16
16
  */
17
17
 
18
18
  import { existsSync } from 'fs';
19
- import fs from 'fs-extra';
20
19
  import { compareVersions } from '../../utils/version-checker.js';
20
+ import { realEffects } from './effects.js';
21
21
 
22
22
  /**
23
23
  * Last CLI version whose `update`/`setup-infra` still wrote
@@ -62,7 +62,7 @@ export const LAST_VERSION_THAT_COPIED_TEMPLATES = '8.17.0';
62
62
  * version BEFORE this run (e.g. `checkProjectOutdated().current`)
63
63
  * @returns {'absent'|'removed-legacy'|'preserved-override'}
64
64
  */
65
- export function reconcileLegacyTemplatesCopy(templatesDest, priorProjectVersion) {
65
+ export function reconcileLegacyTemplatesCopy(templatesDest, priorProjectVersion, fx = realEffects()) {
66
66
  if (!existsSync(templatesDest)) return 'absent';
67
67
 
68
68
  const isLegacy = !priorProjectVersion
@@ -71,7 +71,7 @@ export function reconcileLegacyTemplatesCopy(templatesDest, priorProjectVersion)
71
71
  if (!isLegacy) return 'preserved-override';
72
72
 
73
73
  try {
74
- fs.removeSync(templatesDest);
74
+ fx.remove(templatesDest);
75
75
  return 'removed-legacy';
76
76
  } catch {
77
77
  // Couldn't remove — don't risk a half-broken state; leave it and let
@@ -9,10 +9,18 @@
9
9
  * - designSystemDivergence: boolean — conflicting design sources detected
10
10
  * - standardsViolations: string[] — violations of mandate.md proibições
11
11
  * - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
12
+ * - e2eStatus + e2eReason + gate — nó e2e do último verify. Um `skip`
13
+ * cuja razão NÃO seja dispensa legítima pausa SÓ no ship (gate 'review')
14
+ * - verificationScope: string — escopo do stamp que carimbou esse
15
+ * `skip`. Dispensa legítima só conta vinda de 'feature' (ver abaixo)
12
16
  * - verificationStale + gate: bool/str — stamp com sha != HEAD pausa SÓ no
13
17
  * ship (gate 'review') — código mudou depois do último verify
14
18
  * - evalScoreSource + gate: str — no ship, score que NÃO veio do avaliador
15
19
  * independente pausa (auto-avaliação não decide o Gate 3)
20
+ * - mutationProof + gate: object — { section, min, productionCode } lido do
21
+ * evaluator-report.md. No ship, relatório SEM a seção `## Mutações` numa
22
+ * feature com código de produção pausa: score é opinião, mutação é prova
23
+ * - mediaClaims + gate: object — { section, score, featureGeneratesMedia }
16
24
  * - evalScore: number — morph-eval score (pause if < 9)
17
25
  * - taskCount: number — total tasks (pause if > 20)
18
26
  */
@@ -34,6 +42,95 @@ const TRUSTED_EVAL_SOURCE = 'evaluator-report';
34
42
  // the work-type config object.
35
43
  const FORCED_PAUSE_WORK_TYPES = ['hotfix'];
36
44
 
45
+ // As razões literais que `framework/hooks/shared/e2e-check.js` emite quando o
46
+ // nó e2e pula. Casadas por PADRÃO e não por igualdade porque duas delas
47
+ // carregam dado variável (o nome do grupo da task). Ordem irrelevante: os
48
+ // padrões são disjuntos.
49
+ const E2E_SKIP_PATTERNS = [
50
+ // "docker is not available in this environment — e2e could not run"
51
+ // (e2e-check.js). A app não foi exercida porque a MÁQUINA não podia.
52
+ [/docker is not available/i, 'environment'],
53
+ // `--skip-e2e` — o operador pediu para pular.
54
+ [/^requested$/i, 'requested'],
55
+ // "project does not containerize (no compose file)" — não há app para subir.
56
+ [/does not containerize/i, 'project'],
57
+ // 'task group "docs" does not touch runtime' — escopo da task, não da app.
58
+ [/does not touch runtime/i, 'scope'],
59
+ ];
60
+
61
+ // Classes de skip que NÃO são dispensa: o e2e não rodou e nada no repositório
62
+ // diz que ele não precisava rodar. `unknown` está aqui de propósito — inclui a
63
+ // razão vazia/nula, e uma dispensa sem motivo é o mesmo no-op silencioso que
64
+ // esta regra existe para eliminar.
65
+ const E2E_SKIP_CLASSES_THAT_PAUSE = ['environment', 'requested', 'unknown'];
66
+
67
+ // O ÚNICO escopo de stamp que responde pela feature inteira. `morph-spec verify
68
+ // {f} {task}` carimba `scope: 'task:{id}'`, e o nó e2e de um verify por task
69
+ // pula com a razão do GRUPO daquela task ('task group "docs" does not touch
70
+ // runtime') — uma afirmação sobre UMA task, nunca sobre a app. Sem este teste,
71
+ // a última task de uma feature (tipicamente `group: docs`) compra o Gate 3 da
72
+ // feature que ela mesma acabou de escrever, numa máquina onde a app nunca
73
+ // subiu. Ausente/desconhecido cai do mesmo lado seguro que `unknown`: recusar
74
+ // é barato, aprovar sem prova não é.
75
+ const FEATURE_SCOPE = 'feature';
76
+
77
+ /**
78
+ * Classifica a razão literal de um `skip` do nó e2e — de QUEM é a ausência de
79
+ * prova. Existe porque `verificationStatus` é o rollup do `verify` e um nó que
80
+ * pula NÃO o reprova: sem classificar a razão, "o e2e rodou e passou" e "o e2e
81
+ * nunca rodou" chegam ao gate como o mesmo `pass`.
82
+ *
83
+ * A assimetria é o ponto: `project`/`scope` descrevem o REPOSITÓRIO dizendo que
84
+ * não há runtime a exercer (dispensa legítima, registrada com motivo);
85
+ * `environment`/`requested` descrevem a MÁQUINA ou o operador, e nenhum dos
86
+ * dois é evidência sobre o código. `unknown` cai no lado seguro pelo mesmo
87
+ * motivo: recusar é barato, aprovar sem prova não é.
88
+ *
89
+ * @param {string|null|undefined} reason - `verification.e2eReason` (a razão
90
+ * literal que o `morph-spec verify` carimbou). Vazia/ausente ⇒ 'unknown' —
91
+ * DESCONHECIDO, jamais lido como dispensa.
92
+ * @returns {'environment'|'requested'|'project'|'scope'|'unknown'}
93
+ */
94
+ export function classifyE2eSkip(reason) {
95
+ const text = typeof reason === 'string' ? reason.trim() : '';
96
+ if (text === '') return 'unknown';
97
+ for (const [pattern, klass] of E2E_SKIP_PATTERNS) {
98
+ if (pattern.test(text)) return klass;
99
+ }
100
+ return 'unknown';
101
+ }
102
+
103
+ /** Mensagem de pausa por classe — nomeia SEMPRE a razão literal do verify. */
104
+ function e2ePauseReason(klass, reason) {
105
+ const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : null;
106
+ if (klass === 'environment') {
107
+ return `e2e não verificado (ambiente): ${literal} — a app não foi exercida;`
108
+ + ' rode o e2e onde ele possa rodar, ou registre a dispensa com `morph-spec gate-decision`';
109
+ }
110
+ if (klass === 'requested') {
111
+ return 'e2e pulado a pedido (--skip-e2e) — pular a pedido não é dispensa;'
112
+ + ' rode `morph-spec verify {feature}` sem a flag antes do ship';
113
+ }
114
+ if (literal === null) {
115
+ return 'e2e pulado SEM motivo registrado — uma dispensa sem motivo não é dispensa';
116
+ }
117
+ return `e2e pulado por razão não classificada: "${literal}" — sem classificação, não há dispensa`;
118
+ }
119
+
120
+ /**
121
+ * Mensagem de recusa por ESCOPO do stamp: a razão do skip É uma dispensa
122
+ * legítima (`project`/`scope`), mas quem a carimbou foi um verify que não
123
+ * responde pela feature. Nomeia o escopo real para que o leitor saiba qual
124
+ * comando rodar, em vez de rodar o mesmo de novo.
125
+ */
126
+ function e2eScopeRefusal(klass, scope, reason) {
127
+ const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : 'sem motivo registrado';
128
+ const where = typeof scope === 'string' && scope.trim() !== '' ? scope.trim() : 'ausente';
129
+ return `dispensa de e2e [${klass}] veio de um verify de escopo "${where}", não da feature:`
130
+ + ` "${literal}" — um stamp de task responde por UMA task, não pela app inteira;`
131
+ + ' rode `morph-spec verify {feature}` (sem task) antes do ship';
132
+ }
133
+
37
134
  /**
38
135
  * @param {object} config - .morph/config/config.json contents (uses `trust`)
39
136
  * @param {object} signals
@@ -75,6 +172,43 @@ export function shouldPauseGate(config, signals) {
75
172
  };
76
173
  }
77
174
 
175
+ // No gate de SHIP apenas: o nó e2e do último verify PULOU. Um skip não
176
+ // reprova o rollup, então `verificationStatus` sai 'pass' e o gate ficava
177
+ // cego — auto-passando um Gate 3 sobre uma app que ninguém subiu. A razão
178
+ // literal decide: dispensa legítima (o projeto não containeriza; o grupo da
179
+ // task não toca runtime) passa e fica REGISTRADA nos signals; ausência de
180
+ // prova (docker indisponível, `--skip-e2e`, razão nenhuma) pausa. É a prosa
181
+ // do morph-review virando regra — "docker indisponível não é dispensa" — e
182
+ // é o que dispensa o `gate-decision --pause false` manual que ela mandava
183
+ // rodar. Só no review: pausar o Gate 1/2 numa máquina sem docker travaria
184
+ // trabalho legítimo de planejamento.
185
+ if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
186
+ const klass = classifyE2eSkip(signals.e2eReason);
187
+ if (E2E_SKIP_CLASSES_THAT_PAUSE.includes(klass)) {
188
+ return { pause: true, reason: e2ePauseReason(klass, signals.e2eReason) };
189
+ }
190
+ // Segunda pergunta, que a razão sozinha não responde: QUEM carimbou. O
191
+ // `verification` é um stamp SINGULAR sobrescrito por qualquer verify, de
192
+ // qualquer escopo — então um stamp de escopo `task:{id}` no momento do
193
+ // ship significa literalmente que o último verify NÃO foi o da feature. A
194
+ // razão que ele carrega descreve outra corrida: 'task group "docs" does
195
+ // not touch runtime' é dispensa legítima do que AQUELA task verificou, e a
196
+ // última task de uma feature tipicamente é `group: docs` — ela compraria o
197
+ // Gate 3 da feature que acabou de escrever, numa máquina onde a app nunca
198
+ // subiu. Vale para as duas classes de dispensa, e não só para `scope`:
199
+ // mesmo um "project does not containerize" carimbado por task é a resposta
200
+ // a uma pergunta que o gate não fez. O caminho de volta é barato e nomeado
201
+ // (rodar `verify {feature}`), e é o que morph-review já manda fazer antes
202
+ // do Gate 3. Narrow, nunca widen: nenhuma razão nova passa a valer aqui —
203
+ // uma que já valia deixa de valer fora do escopo que responde pela app.
204
+ if (signals.verificationScope !== FEATURE_SCOPE) {
205
+ return {
206
+ pause: true,
207
+ reason: e2eScopeRefusal(klass, signals.verificationScope, signals.e2eReason),
208
+ };
209
+ }
210
+ }
211
+
78
212
  // Furo 2: no gate de SHIP (review) apenas, um stamp de verification cujo
79
213
  // sha diverge do HEAD atual significa que código mudou depois do último
80
214
  // `morph-spec verify` — o resultado carimbado não cobre mais o que está
@@ -111,6 +245,103 @@ export function shouldPauseGate(config, signals) {
111
245
  };
112
246
  }
113
247
 
248
+ // No gate de SHIP: o relatório do avaliador chegou SEM a seção `## Mutações`.
249
+ // O score diz o que o avaliador ACHOU; a seção diz o que ele PROVOU — e um
250
+ // teste verde só prova que o código roda, nunca que o teste morde. Medido:
251
+ // 11 testes verdes sobre um artefato que o build recusava, porque nenhum
252
+ // deles CARREGAVA o arquivo; e um caminho de escrita reduzido a `return 0;`
253
+ // que passou 23 de 23, todos de recusa.
254
+ //
255
+ // DEPOIS da cláusula de procedência e ANTES da de score, e a ordem é
256
+ // POLÍTICA, não acaso: quando faltam avaliador E seção, a razão reportada tem
257
+ // de ser a mais fundamental ("sem avaliador independente"), porque exigir a
258
+ // prova de um relatório que nem devia decidir o gate manda o humano consertar
259
+ // a coisa errada.
260
+ //
261
+ // FAIL-OPEN POR AUSÊNCIA, em duas direções: `productionCode === true` é
262
+ // exigido EXPLICITAMENTE, então uma feature legada (sinal ausente, `undefined`)
263
+ // não pausa, e uma feature 100% `docs` — que não tem o que mutar — também
264
+ // não. Quem decide as duas coisas é gate-check.js, lendo o disco; aqui só se
265
+ // aplica a política sobre fatos já reunidos.
266
+ if (signals.gate === 'review'
267
+ && signals.mutationProof?.productionCode === true
268
+ && signals.mutationProof?.section !== true) {
269
+ return {
270
+ pause: true,
271
+ reason: 'relatório do avaliador sem a seção `## Mutações` — prova de mutação não registrada'
272
+ + ' (rubrica evals/proof-rubrics/mutation-proof.md): um teste verde prova que o código'
273
+ + ' roda, não que o teste morde',
274
+ };
275
+ }
276
+
277
+ // No gate de SHIP apenas: a feature mexeu num caminho que o PROJETO declarou
278
+ // como prompt ou modelo, e não há eval verde carimbado. Prompt e modelo são a
279
+ // única parte do produto cujo comportamento não é lido por build nem por teste
280
+ // unitário: os dois continuam verdes com o prompt trocado.
281
+ //
282
+ // DEPOIS da cláusula de procedência do score, de propósito: quando as duas
283
+ // valem, "Gate 3 sem score do avaliador independente" continua sendo a razão
284
+ // reportada — é o defeito mais grave dos dois.
285
+ //
286
+ // PAUSA, jamais bloqueia: `approve review` não ganha precondição nova. Uma
287
+ // segunda precondição dura ali tornaria inaprovável, inclusive por decisão
288
+ // humana, toda feature legada. Pausa é a severidade certa — o humano lê a
289
+ // razão e decide.
290
+ if (signals.gate === 'review' && (signals.promptModel?.touched?.length ?? 0) > 0) {
291
+ const touched = signals.promptModel.touched;
292
+ const where = `${touched.slice(0, 3).join(', ')}${touched.length > 3 ? ` (+${touched.length - 3})` : ''}`;
293
+
294
+ // `null` é "o projeto não declara nó de eval"; 'skip' é "declara, e esta
295
+ // corrida não o executou". Razões distintas porque as ações são distintas.
296
+ if (signals.evalsStatus === null || signals.evalsStatus === undefined) {
297
+ return {
298
+ pause: true,
299
+ reason: `mudou prompt/modelo (${where}) e o projeto não declara nó de eval`
300
+ + ' — nenhum build ou teste unitário fica vermelho por um prompt trocado;'
301
+ + ' declare um `runner: "evals"` em project.tests[] ou registre a dispensa com `morph-spec gate-decision`',
302
+ };
303
+ }
304
+ if (signals.evalsStatus === 'skip') {
305
+ return {
306
+ pause: true,
307
+ reason: `mudou prompt/modelo (${where}) e o eval não foi executado neste verify`
308
+ + ' — rode `morph-spec verify {feature}` (escopo de feature) antes do ship',
309
+ };
310
+ }
311
+ // 'fail' cai fora: um eval vermelho já avermelha o rollup e a regra de
312
+ // `verificationStatus` acima pausou antes daqui. 'pass' com stamp velho
313
+ // também já pausou, por `verificationStale`.
314
+ }
315
+
316
+ // No gate de SHIP: uma feature que GERA MÍDIA cujo relatório chegou sem a
317
+ // seção `## Mídia gerada`. As quatro afirmações que essa seção carrega —
318
+ // custo, revisão humana, marcação e hot path não bloqueado — são fáceis de
319
+ // escrever e difíceis de provar, e a mais cara delas é a que ninguém percebe
320
+ // falhando: mídia gerada dentro de um turno de conversa, com latência
321
+ // documentada de até 2 min, custo por chamada e ZERO revisão possível.
322
+ //
323
+ // DEPOIS da cláusula de procedência e da de mutação, e a ordem é POLÍTICA,
324
+ // como lá: quando faltam avaliador, seção de mutação E seção de mídia, a razão
325
+ // reportada tem de ser a mais fundamental, porque mandar o humano consertar a
326
+ // terceira enquanto a primeira está aberta manda consertar a coisa errada.
327
+ //
328
+ // FAIL-OPEN POR AUSÊNCIA, igual: `featureGeneratesMedia === true` é exigido
329
+ // EXPLICITAMENTE, então uma feature legada (sinal ausente, `undefined`) não
330
+ // pausa, e a maioria das features — que não gera mídia — fica isenta
331
+ // automaticamente, sem ninguém precisar lembrar de aplicar a exceção. Quem
332
+ // decide isso é gate-check.js, lendo `standards[]` das tasks; aqui só se
333
+ // aplica a política sobre fatos já reunidos.
334
+ if (signals.gate === 'review'
335
+ && signals.mediaClaims?.featureGeneratesMedia === true
336
+ && signals.mediaClaims.section !== true) {
337
+ return {
338
+ pause: true,
339
+ reason: 'relatório do avaliador sem a seção `## Mídia gerada` — as quatro afirmações de mídia'
340
+ + ' (custo, revisão humana, marcação, hot path) não foram provadas'
341
+ + ' (rubrica evals/proof-rubrics/media-claims.md)',
342
+ };
343
+ }
344
+
114
345
  if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
115
346
  return {
116
347
  pause: true,
@@ -140,18 +371,70 @@ export function getRiskSignals(signals) {
140
371
  out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
141
372
  }
142
373
  if (signals.verificationStatus === 'fail') out.push('verify vermelho');
374
+ // Listado mesmo quando NÃO pausa: uma dispensa que passa silenciosamente é
375
+ // indistinguível de uma omissão. A classe diz de quem é a ausência de prova.
376
+ if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
377
+ const klass = classifyE2eSkip(signals.e2eReason);
378
+ const literal = typeof signals.e2eReason === 'string' && signals.e2eReason.trim() !== ''
379
+ ? signals.e2eReason.trim()
380
+ : 'sem motivo registrado';
381
+ const where = typeof signals.verificationScope === 'string' && signals.verificationScope.trim() !== ''
382
+ ? signals.verificationScope.trim()
383
+ : 'escopo ausente';
384
+ out.push(`e2e pulado [${klass}] (stamp de ${where}): ${literal}`);
385
+ }
143
386
  if (signals.gate === 'review' && signals.verificationStale === true) {
144
387
  out.push('verification stamp desatualizado (stale) no ship');
145
388
  }
146
389
  if (signals.gate === 'review' && signals.evalScoreSource !== TRUSTED_EVAL_SOURCE) {
147
390
  out.push(`score do Gate 3 sem avaliador independente (fonte: ${signals.evalScoreSource ?? 'nenhuma'})`);
148
391
  }
392
+ // Listado mesmo quando NÃO pausa (feature só de docs, ou prova presente com
393
+ // score baixo): a prova é o insumo do teto de `test-coverage.md`, e um teto
394
+ // que ninguém enxerga não é aplicado por ninguém.
395
+ if (signals.gate === 'review' && signals.mutationProof) {
396
+ const { section, min, productionCode } = signals.mutationProof;
397
+ if (section !== true) {
398
+ out.push(productionCode === true
399
+ ? 'relatório sem a seção `## Mutações` (prova de mutação ausente)'
400
+ : 'relatório sem a seção `## Mutações` — feature sem código de produção, não pausa');
401
+ } else if (typeof min === 'number') {
402
+ out.push(`prova de mutação registrada (menor Proof-Score: ${min})`);
403
+ } else {
404
+ out.push('prova de mutação registrada (sem `**Proof-Scores:**` — nenhum teto a aplicar)');
405
+ }
406
+ }
407
+ // Listado mesmo quando NÃO pausa, pela mesma razão do e2e: um projeto que não
408
+ // declara caminho de prompt/modelo é indistinguível, no relatório, de um que
409
+ // declara e não tocou em nada — e as duas coisas significam coisas diferentes.
410
+ if (signals.gate === 'review' && signals.promptModel) {
411
+ const { declared, touched } = signals.promptModel;
412
+ if (!declared) {
413
+ out.push('projeto não declara caminhos de prompt/modelo (project.evals.watch)');
414
+ } else if ((touched?.length ?? 0) > 0) {
415
+ out.push(`prompt/modelo tocado (${touched.length}) com eval ${signals.evalsStatus ?? 'não declarado'}`);
416
+ }
417
+ }
149
418
  if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
150
419
  out.push(`eval score ${signals.evalScore}/10`);
151
420
  }
152
421
  if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
153
422
  out.push(`${signals.taskCount} tasks (over threshold)`);
154
423
  }
424
+ // Listado mesmo quando NÃO pausa, pela mesma razão do sinal irmão: a isenção
425
+ // de uma feature que não gera mídia tem de ser VISÍVEL, senão é
426
+ // indistinguível de uma omissão.
427
+ if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
428
+ const { section, score } = signals.mediaClaims;
429
+ if (section !== true) {
430
+ out.push('relatório sem a seção `## Mídia gerada` (prova de mídia ausente)');
431
+ } else if (typeof score === 'number') {
432
+ out.push(`prova de mídia registrada (media=${score}; teto em arquitetura)`);
433
+ } else {
434
+ out.push('prova de mídia registrada (sem `media=` nos Proof-Scores — nenhum teto a aplicar)');
435
+ }
436
+ }
437
+
155
438
  return out;
156
439
  }
157
440
 
@@ -160,4 +443,5 @@ export const RISK_CONSTANTS = {
160
443
  MIN_EVAL_SCORE,
161
444
  FORCED_PAUSE_WORK_TYPES,
162
445
  TRUSTED_EVAL_SOURCE,
446
+ E2E_SKIP_CLASSES_THAT_PAUSE,
163
447
  };
@@ -0,0 +1,276 @@
1
+ /**
2
+ * src/lib/standards/verified-against.js
3
+ *
4
+ * THE parser for a standard's verification header and for its age against the
5
+ * AI package pin. Single source of truth, on purpose.
6
+ *
7
+ * A standard declares what it was checked against, and when:
8
+ *
9
+ * **Verified against:** Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08);
10
+ * provado por `templates/dotnet/ai-kit/.../AgentFactory.cs`. Last-verified: 2026-09-08.
11
+ *
12
+ * Three places need to read that same fact — `scripts/check-freshness.js`
13
+ * (the freshness gate), `test/framework/standard-header.test.js` (the header
14
+ * contract) and the "ai-agents vs ai-pin" report. Three copies of the regex
15
+ * would drift apart, which is exactly the failure mode CLAUDE.md's Dependency
16
+ * Matrix exists to catalogue. So the regex lives here, once.
17
+ *
18
+ * The age is measured against `framework/ai-pin.json`'s `pinnedAt`, never
19
+ * against "today": `pinnedAt` is the date a human checked the packages, so the
20
+ * obligation is tied to a human act. Measuring against today would make a
21
+ * parked repository change colour on its own.
22
+ *
23
+ * NOTE on the 90-day window: this module EXPOSES the age and the threshold; it
24
+ * enforces nothing — enforcement lives in its consumers. Issue #93 re-verified
25
+ * the `ai-agents` standards and turned the window into a HARD FAIL in
26
+ * scripts/check-freshness.js, so the number below is no longer only a warning.
27
+ * The name kept the `WARN` in it because renaming an export is a change to the
28
+ * module's surface, not to its documentation; scripts/check-freshness.js aliases
29
+ * it to `AI_PIN_STALE_DAYS` rather than re-declaring `90` — see that file's
30
+ * docblock and CONTRIBUTING.md's "two rulers" table.
31
+ */
32
+
33
+ import { readFileSync, existsSync } from 'fs';
34
+ import { join, dirname } from 'path';
35
+ import { fileURLToPath } from 'url';
36
+
37
+ /** Repository root, resolved from this file: src/lib/standards → ../../.. */
38
+ const DEFAULT_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
39
+
40
+ /** Path of the pin relative to a root. */
41
+ const AI_PIN_RELATIVE = join('framework', 'ai-pin.json');
42
+
43
+ /**
44
+ * The verification header, on ONE line.
45
+ *
46
+ * Group 1 is the subject (what the standard was checked against), group 2 the
47
+ * ISO date. `[^\n]*?` keeps the match inside a single line: a `Last-verified:`
48
+ * two paragraphs below belongs to another claim, and stitching the two would
49
+ * date a header that never carried a date.
50
+ *
51
+ * @type {RegExp}
52
+ */
53
+ export const LAST_VERIFIED_RE = /\*\*Verified against:\*\*([^\n]*?)Last-verified:\s*(\d{4}-\d{2}-\d{2})/;
54
+
55
+ /**
56
+ * An unresolved `// VERIFY` marker: a code detail the author could not confirm
57
+ * against the installed package.
58
+ *
59
+ * Deliberately broader than the regex it replaces (`/^\s*\/\/\s*VERIFY:/`),
60
+ * which demanded line-start AND a colon. Every real marker in
61
+ * `framework/standards/ai-agents/` is an end-of-line comment without a colon —
62
+ *
63
+ * using OpenAI.Batch; // VERIFY namespace — confirme contra o pacote OpenAI 2.*
64
+ *
65
+ * — so the old counter reported 0 while two standards openly declared
66
+ * unconfirmed C# API. `\b` keeps `// VERIFYING` out; the fence requirement is
67
+ * NOT in the regex (a regex cannot see a fence) — it lives in
68
+ * {@link countVerifyMarkers}, so prose quoting `// VERIFY:` never counts.
69
+ *
70
+ * @type {RegExp}
71
+ */
72
+ export const VERIFY_MARKER_RE = /(^|\s)\/\/\s*VERIFY\b/;
73
+
74
+ /**
75
+ * Age, in days against `pinnedAt`, above which an `ai-agents/**` standard is
76
+ * due for re-verification.
77
+ *
78
+ * READ UNDER TWO NAMES, ONE VALUE. `scripts/check-freshness.js` imports it as
79
+ * `AI_PIN_STALE_DAYS` and **hard-fails** the run above it (issue #93), for
80
+ * standards under `framework/standards/ai-agents/` measured against `pinnedAt`.
81
+ * The alias exists so the number is declared once: a second literal `90` is the
82
+ * same drift that once blinded the `// VERIFY` counter.
83
+ *
84
+ * The `WARN` in the name is now historical — it shipped in issue #92, when the
85
+ * window was a measurement. Kept because renaming an export changes the module's
86
+ * surface; this comment changes only what the reader is told.
87
+ *
88
+ * @type {number}
89
+ */
90
+ export const AI_AGENTS_WARN_DAYS = 90;
91
+
92
+ /** ISO calendar date, the only date shape this module accepts. */
93
+ const ISO_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
94
+
95
+ /**
96
+ * Parse a standard's verification header.
97
+ *
98
+ * @param {string} text - Full markdown text of the standard.
99
+ * @returns {{ verifiedAgainst: string, lastVerified: string }|null}
100
+ * `null` when the standard carries no parseable header — the caller decides
101
+ * whether that is a warning (the backfill backlog) or a failure
102
+ * (`ai-agents/**`, where a new standard must be born dated).
103
+ */
104
+ export function parseVerifiedAgainst(text) {
105
+ if (typeof text !== 'string' || text.length === 0) return null;
106
+ const m = LAST_VERIFIED_RE.exec(text);
107
+ if (!m) return null;
108
+ return { verifiedAgainst: m[1].trim(), lastVerified: m[2] };
109
+ }
110
+
111
+ /**
112
+ * The `provado por` clause, and the path token that may follow it.
113
+ *
114
+ * THREE TYPESETTINGS EXIST IN THE ACERVO, and all three are real claims.
115
+ * Counted by re-deriving every one of the 78 standards on disk and summing the
116
+ * resulting paths — 27 paths across 16 entries:
117
+ *
118
+ * A ; provado por `templates/…/AgentFactory.cs` e `.../ConversationStore.cs`
119
+ * B ; `provado por templates/…/AgentFactory.cs` para a **ordem do pipeline**
120
+ * C . O timeout é **provado por** `.../AgentSpecOptions.cs` e `.../AgentFactory.cs`
121
+ *
122
+ * B opens the code span BEFORE the clause; C wraps the clause in bold and can
123
+ * appear MID-SENTENCE, after an earlier clause already closed. Each one was
124
+ * found by measuring, and each was missing from the rule that preceded it:
125
+ *
126
+ * - ignoring B dropped 4 of 16 claims (`middleware-patterns`,
127
+ * `observability-patterns`, `rag-custom-pgvector`, `structured-output`);
128
+ * - ignoring C dropped 2 paths from `llm-runtime-defaults` — 25 instead of 27.
129
+ *
130
+ * READ THE SHAPE OF THAT SECOND MISS, because it is why counting entries is not
131
+ * enough: `llm-runtime-defaults` had two OTHER paths from a clause of form A, so
132
+ * it stayed in the "16 entries with proof" bucket either way. No counter moved,
133
+ * `--check` stayed green, exit 0. The only ruler that saw it is the SUM of
134
+ * paths. A canary that pins entry counts would have gone green over it — which
135
+ * is why `standards-registry.test.js` pins the paths themselves, written out by
136
+ * hand, and not a total produced by this function.
137
+ *
138
+ * The DENIALS must stay empty, and they are the majority (61 of the 77 headers
139
+ * derive to `[]`; 16 say some variant of "sem cláusula `provado por`" or
140
+ * "NÃO carimba `provado por`"). Three properties keep them out without a
141
+ * blacklist of prose:
142
+ *
143
+ * 1. a path must contain a `/` — so "`provado por`. Os nomes" and
144
+ * "`provado por`: o ai-kit" match nothing;
145
+ * 2. a path must be CLOSED BY A BACKTICK — so prose that merely follows the
146
+ * clause ("para a **ordem do pipeline**") cannot be mistaken for a path;
147
+ * 3. the clause must not be the tail of a LONGER word — `aprovado por` and
148
+ * `comprovado por` are different claims. The corpus already contains
149
+ * "aprovado por lucas@…" inside a code sample, so this is a live shape,
150
+ * not a hypothetical.
151
+ *
152
+ * @type {RegExp}
153
+ */
154
+ export const PROVED_BY_TRIGGER_RE = /(?:^|[^a-zA-Z])provado por/gi;
155
+
156
+ /**
157
+ * One path token after the clause, plus the separator that may precede it:
158
+ * whitespace, backtick, `,`, `;`, the word `e`, and the markdown emphasis
159
+ * characters `*` and `_`. The emphasis characters are in the class because of
160
+ * typesetting C — without them, `**provado por**` armed the trigger and then
161
+ * matched no path at all, losing both of its files in silence.
162
+ *
163
+ * Anchored: it only ever reads from the cursor, so prose ends the scan instead
164
+ * of being searched through.
165
+ *
166
+ * @type {RegExp}
167
+ */
168
+ export const PROVED_BY_PATH_RE =
169
+ /^[\s`,;*_]*(?:\be\b[\s`,;*_]*)?([A-Za-z0-9_.][A-Za-z0-9_.\-]*(?:\/[A-Za-z0-9_.\-]*)+)`/;
170
+
171
+ /**
172
+ * Derive the list of files a standard claims to be *proved by*.
173
+ *
174
+ * Reads the SAME header {@link parseVerifiedAgainst} reads — deliberately, and
175
+ * from this module, because a second regex over the same sentence in
176
+ * `scripts/generate-standards-registry.js` is precisely the drift the module
177
+ * docblock opens with.
178
+ *
179
+ * @param {string} verifiedAgainst - The subject captured by
180
+ * {@link parseVerifiedAgainst} (group 1), not the whole markdown file.
181
+ * @returns {string[]} Paths in declaration order, de-duplicated. `[]` means
182
+ * "the header was read and declares no proof" — a documental verification —
183
+ * never "not asked". The caller distinguishes the two by whether the header
184
+ * parsed at all.
185
+ */
186
+ export function deriveProvedBy(verifiedAgainst) {
187
+ if (typeof verifiedAgainst !== 'string' || verifiedAgainst.length === 0) return [];
188
+ const paths = [];
189
+ const trigger = new RegExp(PROVED_BY_TRIGGER_RE.source, PROVED_BY_TRIGGER_RE.flags);
190
+ let clause;
191
+ while ((clause = trigger.exec(verifiedAgainst)) !== null) {
192
+ let rest = verifiedAgainst.slice(clause.index + clause[0].length);
193
+ let hit;
194
+ while ((hit = PROVED_BY_PATH_RE.exec(rest)) !== null) {
195
+ if (!paths.includes(hit[1])) paths.push(hit[1]);
196
+ rest = rest.slice(hit[0].length);
197
+ }
198
+ }
199
+ return paths;
200
+ }
201
+
202
+ /**
203
+ * Count unresolved `// VERIFY` markers that sit INSIDE a fenced code block.
204
+ *
205
+ * The fence rule is what separates a real marker from prose that merely quotes
206
+ * one (`marcados \`// VERIFY:\` no código`). It lives here, next to the regex,
207
+ * so every consumer counts the same thing.
208
+ *
209
+ * @param {string} text - Full markdown text of the standard.
210
+ * @returns {number}
211
+ */
212
+ export function countVerifyMarkers(text) {
213
+ if (typeof text !== 'string' || text.length === 0) return 0;
214
+ let inFence = false;
215
+ let count = 0;
216
+ for (const line of text.split('\n')) {
217
+ if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
218
+ if (inFence && VERIFY_MARKER_RE.test(line)) count++;
219
+ }
220
+ return count;
221
+ }
222
+
223
+ /**
224
+ * Read and parse `framework/ai-pin.json`.
225
+ *
226
+ * Throws — never falls back to a default — when the pin is missing or invalid:
227
+ * without a pin there is no ruler, and pretending there is one is the original
228
+ * defect this feature removes. The message names the file so the failure is
229
+ * diagnosable from a CI log alone.
230
+ *
231
+ * @param {string} [root=DEFAULT_ROOT] - Repository root that contains `framework/`.
232
+ * @returns {{ pinnedAt: string, source: string, targetFramework: string, sdk: string,
233
+ * packages: Record<string, {version: string, published: string, role: string}>,
234
+ * toolchain: Record<string, {version: string}> }}
235
+ * @throws {Error} When the file is absent or is not valid JSON.
236
+ */
237
+ export function readAiPin(root = DEFAULT_ROOT) {
238
+ const pinPath = join(root, AI_PIN_RELATIVE);
239
+ if (!existsSync(pinPath)) {
240
+ throw new Error(`ai-pin.json não encontrado em ${pinPath} — sem pin não há régua de frescor.`);
241
+ }
242
+ try {
243
+ return JSON.parse(readFileSync(pinPath, 'utf8'));
244
+ } catch (err) {
245
+ throw new Error(`ai-pin.json inválido em ${pinPath}: ${err.message}`);
246
+ }
247
+ }
248
+
249
+ /** Parse an ISO calendar date into a UTC epoch, timezone-independent. */
250
+ function isoToUtc(value, label) {
251
+ const m = typeof value === 'string' ? ISO_DATE_RE.exec(value) : null;
252
+ if (!m) {
253
+ throw new TypeError(`${label} deve ser uma data ISO YYYY-MM-DD, recebido ${JSON.stringify(value)}`);
254
+ }
255
+ return Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
256
+ }
257
+
258
+ /**
259
+ * Age of a standard's verification, in whole days, measured against the pin.
260
+ *
261
+ * Both dates are read as UTC calendar days, so the result does not depend on
262
+ * the runner's timezone. The value is NEGATIVE when the standard is newer than
263
+ * the pin — the caller must treat that as a PIN error (a `pinnedAt` in the
264
+ * future), never as a fresh standard: otherwise every standard could be
265
+ * "renewed" by editing one date.
266
+ *
267
+ * @param {string} lastVerified - ISO date from the standard's header.
268
+ * @param {string} pinnedAt - ISO date from `framework/ai-pin.json`.
269
+ * @returns {number} Whole days between the two, possibly negative.
270
+ * @throws {TypeError} When either argument is not an ISO date.
271
+ */
272
+ export function ageInDaysAgainstPin(lastVerified, pinnedAt) {
273
+ const verified = isoToUtc(lastVerified, 'lastVerified');
274
+ const pinned = isoToUtc(pinnedAt, 'pinnedAt');
275
+ return Math.round((pinned - verified) / 86400000);
276
+ }