@polymorphism-tech/morph-spec 8.37.1 → 8.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/CLAUDE.md +37 -13
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +75 -49
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +6 -5
  8. package/framework/MORPH.md +25 -22
  9. package/framework/agents.json +96 -36
  10. package/framework/docs/CLI.md +54 -17
  11. package/framework/evals/rubrics/code-quality.md +5 -0
  12. package/framework/evals/rubrics/condensed.md +6 -2
  13. package/framework/evals/rubrics/contracts.md +20 -0
  14. package/framework/evals/rubrics/test-coverage.md +17 -6
  15. package/framework/hooks/README.md +9 -3
  16. package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
  17. package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
  18. package/framework/hooks/claude-code/core/validate-completion.js +16 -90
  19. package/framework/hooks/claude-code/core/worktree-context.js +10 -0
  20. package/framework/hooks/shared/command-tokens.js +96 -0
  21. package/framework/hooks/shared/git-exclude.js +44 -0
  22. package/framework/hooks/shared/git-paths.js +52 -0
  23. package/framework/hooks/shared/graph-freshness.js +380 -0
  24. package/framework/hooks/shared/graph-refresh.js +416 -0
  25. package/framework/hooks/shared/morph-cli.js +17 -5
  26. package/framework/hooks/shared/redact-secrets.js +55 -0
  27. package/framework/hooks/shared/telemetry-logger.js +25 -2
  28. package/framework/hooks/shared/transcript-usage.js +80 -0
  29. package/framework/mcp/registry.json +33 -2
  30. package/framework/rubrics/backend.json +2 -2
  31. package/framework/rubrics/default.json +2 -2
  32. package/framework/rubrics/frontend.json +2 -2
  33. package/framework/rules/csharp-standards.md +1 -0
  34. package/framework/rules/infrastructure-standards.md +0 -1
  35. package/framework/rules/morph-workflow.md +10 -4
  36. package/framework/rules/nextjs-standards.md +1 -0
  37. package/framework/schemas/config.schema.json +41 -1
  38. package/framework/schemas/feature.schema.json +5 -1
  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 +40 -11
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  45. package/framework/skills/core/morph-implement/SKILL.md +44 -10
  46. package/framework/skills/core/morph-media/SKILL.md +1 -1
  47. package/framework/skills/core/morph-plan/SKILL.md +73 -15
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +51 -13
  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 +39 -13
  53. package/framework/standards/STANDARDS.json +189 -14
  54. package/framework/standards/ai-agents/context-providers.md +16 -26
  55. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  56. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  57. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  58. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  59. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  60. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  61. package/framework/standards/ai-agents/testing-ai.md +2 -1
  62. package/framework/standards/architecture/code-comments.md +100 -0
  63. package/framework/standards/backend/dotnet/testing.md +22 -0
  64. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  65. package/framework/standards/frontend/admin-app/data-table.md +562 -0
  66. package/framework/standards/frontend/admin-app/feedback.md +236 -0
  67. package/framework/standards/frontend/admin-app/forms.md +232 -0
  68. package/framework/standards/frontend/admin-app/page-structure.md +203 -0
  69. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  70. package/framework/standards/frontend/nextjs/components.md +37 -58
  71. package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
  72. package/framework/standards/frontend/nextjs/forms.md +31 -30
  73. package/framework/standards/frontend/nextjs/state-management.md +2 -1
  74. package/framework/standards/frontend/nextjs/testing.md +18 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  76. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  77. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  78. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  79. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  80. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  81. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  82. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  83. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  84. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  85. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  86. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  87. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  88. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  89. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  90. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  91. package/framework/templates/code/dotnet/test.cs +2 -87
  92. package/framework/templates/code/typescript/contracts.ts +140 -167
  93. package/framework/templates/context/design-system.md +3 -3
  94. package/framework/templates/docs/proposal.md +11 -0
  95. package/framework/templates/docs/spec.md +15 -0
  96. package/framework/templates/docs/ui-mockups.md +115 -98
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  150. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  151. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  152. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  153. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  154. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  155. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  156. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  180. package/framework/templates/feature/decisions.md +124 -122
  181. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  182. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  183. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  184. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  185. package/framework/templates/integrations/asaas-client.cs +332 -387
  186. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  187. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  188. package/framework/templates/project/validate.js +97 -120
  189. package/package.json +2 -2
  190. package/src/commands/dashboard/dashboard.js +80 -53
  191. package/src/commands/graph/affected.js +4 -3
  192. package/src/commands/graph/build.js +109 -18
  193. package/src/commands/graph/explain.js +8 -5
  194. package/src/commands/graph/status.js +14 -8
  195. package/src/commands/mcp/mcp-setup.js +37 -9
  196. package/src/commands/observability/cost.js +148 -18
  197. package/src/commands/project/doctor.js +85 -15
  198. package/src/commands/project/init.js +6 -3
  199. package/src/commands/project/install-plugin-cmd.js +1 -1
  200. package/src/commands/project/update.js +30 -0
  201. package/src/commands/project/worktree.js +73 -90
  202. package/src/commands/state/advance.js +18 -0
  203. package/src/commands/state/approve.js +15 -0
  204. package/src/commands/state/archive.js +62 -9
  205. package/src/commands/state/finish.js +62 -14
  206. package/src/commands/state/gate-check.js +8 -1
  207. package/src/commands/state/gate-decision.js +8 -4
  208. package/src/commands/state/report-check.js +90 -9
  209. package/src/commands/state/usecase.js +66 -0
  210. package/src/commands/tasks/dag.js +17 -2
  211. package/src/commands/validation/validate-feature.js +6 -1
  212. package/src/commands/validation/verify.js +20 -1
  213. package/src/dashboard/app/App.jsx +80 -16
  214. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  215. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  216. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  217. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  218. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  219. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  220. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  221. package/src/dashboard/app/components/Markdown.jsx +47 -0
  222. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  223. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  224. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  225. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  226. package/src/dashboard/app/hooks/usePref.js +38 -0
  227. package/src/dashboard/app/lib/format.js +69 -0
  228. package/src/dashboard/app/lib/phases.js +56 -0
  229. package/src/dashboard/app/lib/project.js +50 -0
  230. package/src/dashboard/app/pages/Activity.jsx +24 -0
  231. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  232. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  233. package/src/dashboard/app/pages/Features.jsx +400 -0
  234. package/src/dashboard/app/pages/Guide.jsx +146 -0
  235. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  236. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  237. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  238. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  239. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  240. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  241. package/src/dashboard/app/pages/Standards.jsx +407 -332
  242. package/src/dashboard/app/styles/index.css +187 -47
  243. package/src/dashboard/index.html +14 -12
  244. package/src/dashboard/package.json +32 -24
  245. package/src/dashboard/scanner/activity-scanner.js +351 -0
  246. package/src/dashboard/scanner/agents-scanner.js +63 -1
  247. package/src/dashboard/scanner/docs-scanner.js +59 -0
  248. package/src/dashboard/scanner/feature-status.js +10 -0
  249. package/src/dashboard/scanner/features-scanner.js +360 -0
  250. package/src/dashboard/scanner/hooks-scanner.js +8 -1
  251. package/src/dashboard/scanner/index.js +14 -7
  252. package/src/dashboard/scanner/phases-scanner.js +14 -15
  253. package/src/dashboard/scanner/portfolio.js +153 -0
  254. package/src/dashboard/scanner/project-api.js +195 -0
  255. package/src/dashboard/scanner/rules-scanner.js +42 -37
  256. package/src/dashboard/scanner/skills-scanner.js +105 -43
  257. package/src/dashboard/scanner/standards-scanner.js +4 -1
  258. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  259. package/src/lib/ai/comment-stripper.js +101 -4
  260. package/src/lib/detectors/claude-config-detector.js +2 -7
  261. package/src/lib/detectors/index.js +5 -1
  262. package/src/lib/evaluator-report.js +47 -0
  263. package/src/lib/generators/handoff-generator.js +4 -18
  264. package/src/lib/generators/settings-generator.js +208 -209
  265. package/src/lib/git/default-branch.js +52 -0
  266. package/src/lib/git/tree-fingerprint.js +58 -0
  267. package/src/lib/git/tree-state.js +167 -0
  268. package/src/lib/graph/god-nodes.js +12 -3
  269. package/src/lib/graph/graph-health.js +48 -2
  270. package/src/lib/graph/graph-loader.js +99 -25
  271. package/src/lib/graph/graphifyignore.js +145 -28
  272. package/src/lib/graph/sanitize.js +117 -11
  273. package/src/lib/installers/mcp-installer.js +724 -719
  274. package/src/lib/installers/memory-installer.js +1 -2
  275. package/src/lib/projects/registry.js +154 -0
  276. package/src/lib/risk-detector.js +90 -0
  277. package/src/lib/specs/acervo-format.js +70 -0
  278. package/src/lib/specs/acervo-lifecycle.js +264 -0
  279. package/src/lib/specs/adr-index.js +169 -0
  280. package/src/lib/specs/revocations.js +156 -0
  281. package/src/lib/specs/screen-checklist.js +357 -0
  282. package/src/lib/specs/usecase-check.js +20 -16
  283. package/src/lib/specs/usecase-promoter.js +103 -38
  284. package/src/lib/specs/usecase-registry.js +7 -7
  285. package/src/lib/tasks/tasks-json-parser.js +2 -3
  286. package/src/lib/telemetry/metrics.js +386 -0
  287. package/src/lib/telemetry/telemetry-reader.js +161 -51
  288. package/src/lib/telemetry/verify-event.js +79 -0
  289. package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
  290. package/src/lib/validators/code-budget/comment-scanner.js +75 -0
  291. package/src/lib/validators/code-budget/index.js +9 -0
  292. package/src/lib/validators/reuse/reuse-validator.js +4 -2
  293. package/src/lib/validators/reuse/symbol-scanner.js +27 -22
  294. package/src/lib/validators/validation-runner.js +21 -5
  295. package/src/lib/validators/verify-runner.js +11 -0
  296. package/src/lib/worktree/finish-backup.js +739 -0
  297. package/src/lib/worktree/ignored-triage.js +15 -8
  298. package/src/lib/worktree/worktree-audit.js +5 -0
  299. package/src/scripts/install-plugin.js +1 -1
  300. package/src/scripts/setup-infra.js +31 -22
  301. package/src/utils/agents-installer.js +338 -24
  302. package/src/utils/banner.js +1 -1
  303. package/src/utils/hooks-installer.js +75 -15
  304. package/src/commands/index.js +0 -13
  305. package/src/commands/project/checkpoint.js +0 -209
  306. package/src/core/state/phase-state-machine.js +0 -197
  307. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  308. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  309. package/src/dashboard/scanner/squads-scanner.js +0 -13
  310. package/src/dashboard/scanner/state-scanner.js +0 -85
  311. package/src/lib/generators/index.js +0 -10
  312. package/src/lib/hooks/hook-executor.js +0 -167
  313. package/src/lib/validators/checkpoint-persistence.js +0 -39
  314. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  315. package/src/lib/validators/shared/index.js +0 -12
  316. package/src/lib/validators/shared/issue-counter.js +0 -14
  317. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -5,21 +5,6 @@ using Xunit;
5
5
 
6
6
  namespace Morph.AiKit.Tests;
7
7
 
8
- /// <summary>
9
- /// A companheira obrigatoria de <c>AgentApi.Images</c>/<c>AgentApi.Videos</c> existirem:
10
- /// <b>um alias de midia nao vira agente</b>.
11
- /// </summary>
12
- /// <remarks>
13
- /// <para>
14
- /// Um registry so e um vocabulario so de alias e a decisao (<c>decisions.md</c> D3) — e
15
- /// justamente por isso a recusa precisa existir. Sem ela, um alias de imagem passado por
16
- /// engano viraria um agente de chat apontando para um modelo que nao fala chat: a falha
17
- /// apareceria no primeiro turno, com uma mensagem do provider que nao diz o que aconteceu.
18
- /// </para>
19
- /// <para>
20
- /// Enum novo sem recusa e uma armadilha nova. Este arquivo e o que impede que ela volte.
21
- /// </para>
22
- /// </remarks>
23
8
  public sealed class MediaAliasIsNotAnAgentTests
24
9
  {
25
10
  private const string MediaAliasDocument = """
@@ -52,7 +37,6 @@ public sealed class MediaAliasIsNotAnAgentTests
52
37
  private static AgentSpec Spec(string alias, AgentApi? api = null)
53
38
  => new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
54
39
 
55
- /// <summary>O registry ACEITA <c>images</c> e <c>videos</c> — um vocabulario so de alias.</summary>
56
40
  [Theory]
57
41
  [InlineData("imagem", AgentApi.Images)]
58
42
  [InlineData("video", AgentApi.Videos)]
@@ -63,7 +47,6 @@ public sealed class MediaAliasIsNotAnAgentTests
63
47
  Assert.Equal(expected, registry.Resolve(alias).Api);
64
48
  }
65
49
 
66
- /// <summary>E a fabrica RECUSA, na construcao, com erro nomeado.</summary>
67
50
  [Theory]
68
51
  [InlineData("imagem")]
69
52
  [InlineData("video")]
@@ -78,10 +61,6 @@ public sealed class MediaAliasIsNotAnAgentTests
78
61
  Assert.Contains("EnqueueAsync", ex.Message, StringComparison.Ordinal);
79
62
  }
80
63
 
81
- /// <summary>
82
- /// A recusa tambem cobre o caminho pela SPEC: um alias de conversa com
83
- /// <c>Api = Images</c> na spec e a mesma confusao, pela outra porta.
84
- /// </summary>
85
64
  [Fact]
86
65
  public void TheFactoryRefusesAMediaProtocolDeclaredOnTheSpec()
87
66
  {
@@ -93,7 +72,6 @@ public sealed class MediaAliasIsNotAnAgentTests
93
72
  Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason);
94
73
  }
95
74
 
96
- /// <summary>Alias de conversa continua passando — a recusa nova nao pode ser um bloqueio geral.</summary>
97
75
  [Fact]
98
76
  public void AChatAliasStillBuildsAnAgent()
99
77
  {
@@ -103,7 +81,6 @@ public sealed class MediaAliasIsNotAnAgentTests
103
81
  Assert.NotNull(factory.Create(Spec("texto")));
104
82
  }
105
83
 
106
- /// <summary>O predicado mora num lugar so — duas copias e como as duas discordam.</summary>
107
84
  [Theory]
108
85
  [InlineData(AgentApi.Responses, false)]
109
86
  [InlineData(AgentApi.Chat, false)]
@@ -112,7 +89,6 @@ public sealed class MediaAliasIsNotAnAgentTests
112
89
  public void IsMediaAnswersForEveryProtocol(AgentApi api, bool expected)
113
90
  => Assert.Equal(expected, api.IsMedia());
114
91
 
115
- /// <summary>Um <c>api</c> inventado continua sendo recusado na carga do documento.</summary>
116
92
  [Fact]
117
93
  public void AnUnknownApiIsStillRefusedWithTheFullListOfValidValues()
118
94
  {
@@ -4,37 +4,12 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests;
6
6
 
7
- /// <summary>
8
- /// Canários da MARCAÇÃO — a quarta afirmação da rubrica `media-claims`, e a última que este
9
- /// kit ainda prendia só por prosa.
10
- /// </summary>
11
- /// <remarks>
12
- /// <para>
13
- /// <b>A assimetria que estes testes fecham, medida:</b> a regra do turno de conversa estava
14
- /// presa por TIPO (`internal` + id de job em vez de bytes) e provada por mutação; custo e
15
- /// revisão humana, por ESTADO. A marcação — que é a obrigação legal, o art. 50 do AI Act que
16
- /// motivou a issue — estava escrita em `MediaJobState.cs` ("publicado, com a marcacao
17
- /// aplicada") e em `MediaRequest.cs` ("marcacao visivel obrigatoria"), e um `grep` por label,
18
- /// watermark ou disclosure no kit inteiro voltava <b>vazio</b>. Zero mecanismo.
19
- /// </para>
20
- /// <para>
21
- /// Uma rubrica que cobra de terceiros uma prova que o próprio exemplar não dá é o defeito que
22
- /// esta epic catalogou. Estes testes são o inverso literal dele.
23
- /// </para>
24
- /// <para>
25
- /// <b>Fronteira honesta:</b> nada aqui prova que o rótulo chegou aos olhos de alguém — a
26
- /// renderização é do aplicativo consumidor, e nenhum tipo em C# alcança um pixel. O que se
27
- /// prova é que a publicação <b>não acontece</b> sem a decisão registrada, com texto e
28
- /// superfície nomeados.
29
- /// </para>
30
- /// </remarks>
31
7
  public sealed class MediaDisclosureTests
32
8
  {
33
9
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
34
10
 
35
11
  private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
36
12
 
37
- /// <summary>Um job aprovado, na finalidade pedida.</summary>
38
13
  private static MediaJob Approved(MediaPurpose purpose)
39
14
  {
40
15
  var job = MediaJob.Create(
@@ -50,11 +25,6 @@ public sealed class MediaDisclosureTests
50
25
  return job.Approve(MediaReview.ApprovedBy("lucas", T0)).Value;
51
26
  }
52
27
 
53
- // ---------------------------------------------------------------------------------
54
- // A política, por finalidade — dado, não comentário
55
- // ---------------------------------------------------------------------------------
56
-
57
- /// <summary>Catálogo e marketing exigem marcação; rascunho não chega a publicar.</summary>
58
28
  [Theory]
59
29
  [InlineData(MediaPurpose.CatalogPhoto, true)]
60
30
  [InlineData(MediaPurpose.Marketing, true)]
@@ -62,7 +32,6 @@ public sealed class MediaDisclosureTests
62
32
  public void RequiresDisclosureIsDeclaredPerPurpose(MediaPurpose purpose, bool expected)
63
33
  => Assert.Equal(expected, purpose.RequiresDisclosure());
64
34
 
65
- /// <summary>Rascunho é a única finalidade não publicável.</summary>
66
35
  [Theory]
67
36
  [InlineData(MediaPurpose.CatalogPhoto, true)]
68
37
  [InlineData(MediaPurpose.Marketing, true)]
@@ -70,14 +39,6 @@ public sealed class MediaDisclosureTests
70
39
  public void OnlyDraftIsUnpublishable(MediaPurpose purpose, bool expected)
71
40
  => Assert.Equal(expected, purpose.IsPublishable());
72
41
 
73
- // ---------------------------------------------------------------------------------
74
- // A porta: publicar sem marcação NÃO é um caminho
75
- // ---------------------------------------------------------------------------------
76
-
77
- /// <summary>
78
- /// O caso central. Um job aprovado, de catálogo, sem marcação: <b>não publica</b>, e a
79
- /// razão nomeia o que falta.
80
- /// </summary>
81
42
  [Theory]
82
43
  [InlineData(MediaPurpose.CatalogPhoto)]
83
44
  [InlineData(MediaPurpose.Marketing)]
@@ -93,7 +54,6 @@ public sealed class MediaDisclosureTests
93
54
  Assert.Contains("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
94
55
  }
95
56
 
96
- /// <summary>Com a marcação, publica — e ela fica GRAVADA no job, não descartada.</summary>
97
57
  [Fact]
98
58
  public void PublishSucceedsWithADisclosureAndRecordsIt()
99
59
  {
@@ -106,10 +66,6 @@ public sealed class MediaDisclosureTests
106
66
  Assert.Equal(T0, published.Value.Disclosure.AppliedAt);
107
67
  }
108
68
 
109
- /// <summary>
110
- /// Rascunho não publica, <b>nem com marcação e nem estando aprovado</b>. Era uma frase
111
- /// num comentário XML; agora é uma recusa.
112
- /// </summary>
113
69
  [Fact]
114
70
  public void ADraftNeverPublishesEvenApprovedAndLabelled()
115
71
  {
@@ -119,11 +75,6 @@ public sealed class MediaDisclosureTests
119
75
  Assert.Contains("nao publica", result.Error!.Message, StringComparison.OrdinalIgnoreCase);
120
76
  }
121
77
 
122
- /// <summary>
123
- /// A ordem das portas importa: o estado vem antes da marcação. Um job NÃO aprovado, sem
124
- /// marcação, tem de reclamar da revisão humana — mandar consertar a marcação de algo que
125
- /// nem foi revisado manda consertar a coisa errada.
126
- /// </summary>
127
78
  [Fact]
128
79
  public void TheStateGateIsReportedBeforeTheDisclosureGate()
129
80
  {
@@ -142,29 +93,6 @@ public sealed class MediaDisclosureTests
142
93
  Assert.DoesNotContain("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
143
94
  }
144
95
 
145
- // ---------------------------------------------------------------------------------
146
- // A marcação vazia é o campo preenchido "para passar"
147
- // ---------------------------------------------------------------------------------
148
-
149
- /// <summary>
150
- /// <b>A construção não tem outra porta.</b> Este é o teste que o nome do anterior prometia
151
- /// e não entregava.
152
- /// </summary>
153
- /// <remarks>
154
- /// <para>
155
- /// A primeira versão de <see cref="MediaDisclosure"/> era um record POSICIONAL, e record
156
- /// posicional gera construtor <b>público</b>. A validação vivia só em
157
- /// <see cref="MediaDisclosure.Create"/>, então um consumidor externo fazia
158
- /// <c>new MediaDisclosure("", "", at)</c> e publicava com rótulo vazio — medido:
159
- /// <c>IsSuccess=True State=Published</c>. O canário de então exercitava a FÁBRICA e passava
160
- /// verde o tempo todo: media a copia, nao o objeto.
161
- /// </para>
162
- /// <para>
163
- /// Por isso a asserção é por REFLEXÃO sobre o tipo. Ela não pode ser escrita como
164
- /// <c>new MediaDisclosure(...)</c> — hoje isso nem compila, e é esse o ponto: uma asserção
165
- /// que só existe enquanto o defeito existe não é canário.
166
- /// </para>
167
- /// </remarks>
168
96
  [Fact]
169
97
  public void TheTypeExposesNoPublicConstructor()
170
98
  {
@@ -179,7 +107,6 @@ public sealed class MediaDisclosureTests
179
107
  "MediaReview. Encontrados: " + string.Join(" | ", publicCtors.Select(c => c.ToString())));
180
108
  }
181
109
 
182
- /// <summary>Texto ou superfície em branco é recusado pela única porta que existe.</summary>
183
110
  [Theory]
184
111
  [InlineData("", "legenda")]
185
112
  [InlineData(" ", "legenda")]
@@ -188,10 +115,6 @@ public sealed class MediaDisclosureTests
188
115
  public void ABlankDisclosureIsRefusedByTheOnlyFactory(string text, string surface)
189
116
  => Assert.Throws<ArgumentException>(() => MediaDisclosure.Create(text, surface, T0));
190
117
 
191
- /// <summary>
192
- /// A superfície é obrigatória junto do texto: "marcado" sem dizer ONDE não responde se
193
- /// alguém consegue ver.
194
- /// </summary>
195
118
  [Fact]
196
119
  public void TheDisclosureCarriesBothTheTextAndWhereItAppears()
197
120
  {
@@ -201,7 +124,6 @@ public sealed class MediaDisclosureTests
201
124
  Assert.Equal("sobreposicao", disclosure.Surface);
202
125
  }
203
126
 
204
- /// <summary>Uma publicação recusada não muda o job — o registro continua imutável.</summary>
205
127
  [Fact]
206
128
  public void ARefusedPublishLeavesTheJobUntouched()
207
129
  {
@@ -5,15 +5,6 @@ using Xunit;
5
5
 
6
6
  namespace Morph.AiKit.Tests;
7
7
 
8
- /// <summary>
9
- /// Canarios do enqueue e do worker. <b>Nenhum deles toca API paga</b> — o unico ponto que
10
- /// gastaria dinheiro e o <see cref="FakeMediaProvider"/>, e a suite conta as chamadas dele.
11
- /// </summary>
12
- /// <remarks>
13
- /// As afirmacoes aqui sao as tres que o dinheiro cobra: um retry nao paga de novo, um
14
- /// estouro de teto recusa <b>antes</b> da chamada, e um bloqueio de moderacao vai para
15
- /// <c>RejectedByPolicy</c> em vez de virar retry.
16
- /// </remarks>
17
8
  public sealed class MediaGeneratorTests
18
9
  {
19
10
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
@@ -42,11 +33,6 @@ public sealed class MediaGeneratorTests
42
33
  jobs, store, provider);
43
34
  }
44
35
 
45
- // ---------------------------------------------------------------------------------
46
- // Enqueue: id de job, nunca bytes
47
- // ---------------------------------------------------------------------------------
48
-
49
- /// <summary>Um pedido valido vira job em <c>Pending</c>, e o retorno e um id.</summary>
50
36
  [Fact]
51
37
  public async Task EnqueueReturnsAJobIdAndTheJobStartsPending()
52
38
  {
@@ -58,11 +44,9 @@ public sealed class MediaGeneratorTests
58
44
  var job = await generator.GetAsync(enqueued.Value, Ct);
59
45
  Assert.Equal(MediaJobState.Pending, job.Value.State);
60
46
 
61
- // Nada foi gasto: o enqueue NAO chama provider.
62
47
  Assert.Equal(0, provider.Calls);
63
48
  }
64
49
 
65
- /// <summary>Pedido sem prompt e recusa nomeada, nao excecao.</summary>
66
50
  [Fact]
67
51
  public async Task AnInvalidRequestIsRefusedByName()
68
52
  {
@@ -75,7 +59,6 @@ public sealed class MediaGeneratorTests
75
59
  Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
76
60
  }
77
61
 
78
- /// <summary>Alias desconhecido: recusa, e nenhum job criado.</summary>
79
62
  [Fact]
80
63
  public async Task AnUnknownAliasIsRefused()
81
64
  {
@@ -88,14 +71,6 @@ public sealed class MediaGeneratorTests
88
71
  Assert.Equal(0, jobs.Saves);
89
72
  }
90
73
 
91
- // ---------------------------------------------------------------------------------
92
- // Idempotencia: o retry nao paga de novo
93
- // ---------------------------------------------------------------------------------
94
-
95
- /// <summary>
96
- /// O MESMO pedido, enfileirado duas vezes, devolve o MESMO job. E o que faz um retry de
97
- /// fila, de worker reiniciado ou de webhook duplicado custar zero.
98
- /// </summary>
99
74
  [Fact]
100
75
  public async Task TheSameRequestEnqueuedTwiceReusesTheSameJob()
101
76
  {
@@ -108,7 +83,6 @@ public sealed class MediaGeneratorTests
108
83
  Assert.Equal(1, jobs.Saves);
109
84
  }
110
85
 
111
- /// <summary>Pedido diferente, job diferente — a dedupe nao pode engolir trabalho novo.</summary>
112
86
  [Fact]
113
87
  public async Task ADifferentRequestGetsItsOwnJob()
114
88
  {
@@ -121,14 +95,6 @@ public sealed class MediaGeneratorTests
121
95
  Assert.NotEqual(first.Value, second.Value);
122
96
  }
123
97
 
124
- // ---------------------------------------------------------------------------------
125
- // Teto de custo: recusa ANTES da chamada
126
- // ---------------------------------------------------------------------------------
127
-
128
- /// <summary>
129
- /// O teto e confrontado com o custo estimado, e a recusa acontece no enqueue — antes de
130
- /// existir qualquer chamada. Depois dela, o dinheiro ja saiu.
131
- /// </summary>
132
98
  [Fact]
133
99
  public async Task ABudgetOverrunIsRefusedAtEnqueueBeforeAnyCall()
134
100
  {
@@ -145,7 +111,6 @@ public sealed class MediaGeneratorTests
145
111
  Assert.Equal([0.211m], budget.Seen);
146
112
  }
147
113
 
148
- /// <summary>O custo estimado que chega ao teto e o da tarifa, na quantidade pedida.</summary>
149
114
  [Fact]
150
115
  public async Task TheEstimateSentToTheBudgetIsTariffTimesCount()
151
116
  {
@@ -157,11 +122,6 @@ public sealed class MediaGeneratorTests
157
122
  Assert.Equal([0.024m], budget.Seen);
158
123
  }
159
124
 
160
- // ---------------------------------------------------------------------------------
161
- // Worker: estados, storage e a distincao que vale dinheiro
162
- // ---------------------------------------------------------------------------------
163
-
164
- /// <summary>Caminho feliz: os bytes vao para o storage ANTES de o job virar Generated.</summary>
165
125
  [Fact]
166
126
  public async Task TheWorkerStoresTheBytesAndThenMarksGenerated()
167
127
  {
@@ -178,7 +138,6 @@ public sealed class MediaGeneratorTests
178
138
  Assert.EndsWith(".png", ran.Value.RelativePath, StringComparison.Ordinal);
179
139
  }
180
140
 
181
- /// <summary>A proveniencia e gravada com o modelo, o prompt reescrito e o custo estimado.</summary>
182
141
  [Fact]
183
142
  public async Task TheWorkerRecordsProvenanceWithTheEstimatedCost()
184
143
  {
@@ -197,10 +156,6 @@ public sealed class MediaGeneratorTests
197
156
  Assert.True(provenance.C2paPreserved);
198
157
  }
199
158
 
200
- /// <summary>
201
- /// Bloqueio de moderacao vira <c>RejectedByPolicy</c>, terminal. Um erro tecnico vira
202
- /// <c>Failed</c>, retryable. Colapsar os dois e como um laco de retry vira fatura.
203
- /// </summary>
204
159
  [Theory]
205
160
  [InlineData(MediaErrorCode.PolicyBlocked, MediaJobState.RejectedByPolicy, false)]
206
161
  [InlineData(MediaErrorCode.ProviderFailure, MediaJobState.Failed, true)]
@@ -218,7 +173,6 @@ public sealed class MediaGeneratorTests
218
173
  Assert.Equal(0, store.Blobs.Writes);
219
174
  }
220
175
 
221
- /// <summary>Job inexistente e recusa nomeada, nunca uma excecao de referencia nula.</summary>
222
176
  [Fact]
223
177
  public async Task RunningAnUnknownJobIsRefusedByName()
224
178
  {
@@ -230,10 +184,6 @@ public sealed class MediaGeneratorTests
230
184
  Assert.Equal(MediaErrorCode.NotFound, ran.Error!.Code);
231
185
  }
232
186
 
233
- /// <summary>
234
- /// Um alias de VIDEO pedido como imagem nao vira job: as unidades de cobranca sao
235
- /// diferentes, e um pedido com a tarifa errada estima o custo errado em silencio.
236
- /// </summary>
237
187
  [Fact]
238
188
  public async Task AVideoAliasCannotServeAnImageRequest()
239
189
  {
@@ -246,9 +196,6 @@ public sealed class MediaGeneratorTests
246
196
  Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
247
197
  }
248
198
 
249
- /// <summary>
250
- /// Video sem duracao nao tem custo estimavel — e arbitrar segundos e arbitrar dinheiro.
251
- /// </summary>
252
199
  [Fact]
253
200
  public async Task AVideoRequestWithoutDurationCannotBePriced()
254
201
  {
@@ -4,15 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests;
6
6
 
7
- /// <summary>
8
- /// Canarios de <see cref="MediaIdempotencyKey"/>: a chave que dedupe a CHAMADA PAGA.
9
- /// </summary>
10
- /// <remarks>
11
- /// Duas propriedades, e as duas custam dinheiro quando falham. <b>Estabilidade</b>: o mesmo
12
- /// pedido tem de dar a mesma chave, senao todo retry paga de novo. <b>Sensibilidade</b>:
13
- /// qualquer campo participante que mude tem de mudar a chave, senao um pedido reusa o
14
- /// artefato pago de OUTRO — pior que pagar duas vezes.
15
- /// </remarks>
16
7
  public sealed class MediaIdempotencyKeyTests
17
8
  {
18
9
  private static MediaRequest Request(
@@ -29,32 +20,18 @@ public sealed class MediaIdempotencyKeyTests
29
20
  private static string KeyOf(MediaRequest request)
30
21
  => MediaIdempotencyKey.From(request, "openai", "gpt-image-2").Value;
31
22
 
32
- // ---------------------------------------------------------------------------------
33
- // Estabilidade
34
- // ---------------------------------------------------------------------------------
35
-
36
- /// <summary>Duas construcoes do mesmo pedido dao a mesma chave.</summary>
37
23
  [Fact]
38
24
  public void TheSameRequestAlwaysGivesTheSameKey()
39
25
  => Assert.Equal(KeyOf(Request()), KeyOf(Request()));
40
26
 
41
- /// <summary>
42
- /// Espaco em branco a mais nao muda o pedido, entao nao muda a chave. E o que impede um
43
- /// prompt re-serializado com quebra de linha diferente de virar outra fatura.
44
- /// </summary>
45
27
  [Fact]
46
28
  public void WhitespaceIsNormalisedAway()
47
29
  => Assert.Equal(KeyOf(Request("um sofa verde")), KeyOf(Request(" um sofa\n verde ")));
48
30
 
49
- /// <summary>
50
- /// A caixa NAO e normalizada, e e deliberado: num prompt ela muda o resultado (nome de
51
- /// marca, texto literal que vai aparecer na imagem).
52
- /// </summary>
53
31
  [Fact]
54
32
  public void CaseIsPreservedBecauseItChangesThePicture()
55
33
  => Assert.NotEqual(KeyOf(Request("um sofa VERDE")), KeyOf(Request("um sofa verde")));
56
34
 
57
- /// <summary>A chave e um sha256 em hex minusculo: 64 digitos.</summary>
58
35
  [Fact]
59
36
  public void TheKeyIsASha256InLowercaseHex()
60
37
  {
@@ -64,58 +41,43 @@ public sealed class MediaIdempotencyKeyTests
64
41
  Assert.Matches("^[0-9a-f]{64}$", key);
65
42
  }
66
43
 
67
- // ---------------------------------------------------------------------------------
68
- // Sensibilidade — uma rodada POR CAMPO participante
69
- // ---------------------------------------------------------------------------------
70
-
71
- /// <summary>Prompt diferente, chave diferente.</summary>
72
44
  [Fact]
73
45
  public void ADifferentPromptChangesTheKey()
74
46
  => Assert.NotEqual(KeyOf(Request("um sofa azul")), KeyOf(Request()));
75
47
 
76
- /// <summary>Modelo diferente, chave diferente — o alias pode ser repontado.</summary>
77
48
  [Fact]
78
49
  public void ADifferentModelChangesTheKey()
79
50
  => Assert.NotEqual(
80
51
  MediaIdempotencyKey.From(Request(), "openai", "gpt-image-2").Value,
81
52
  MediaIdempotencyKey.From(Request(), "openai", "outro-modelo").Value);
82
53
 
83
- /// <summary>Provider diferente, chave diferente.</summary>
84
54
  [Fact]
85
55
  public void ADifferentProviderChangesTheKey()
86
56
  => Assert.NotEqual(
87
57
  MediaIdempotencyKey.From(Request(), "openai", "m").Value,
88
58
  MediaIdempotencyKey.From(Request(), "google", "m").Value);
89
59
 
90
- /// <summary>
91
- /// <b>Trocar a foto de referencia E outro pedido</b>, mesmo com o prompt identico. Por
92
- /// isso o sha256 de CADA referencia participa, e nao a contagem delas.
93
- /// </summary>
94
60
  [Fact]
95
61
  public void ADifferentReferenceChangesTheKey()
96
62
  => Assert.NotEqual(
97
63
  KeyOf(Request(references: ["foto-a"])),
98
64
  KeyOf(Request(references: ["foto-b"])));
99
65
 
100
- /// <summary>Mesma quantidade de referencias, conteudo trocado de ordem: outro pedido.</summary>
101
66
  [Fact]
102
67
  public void TheOrderOfReferencesIsPartOfTheRequest()
103
68
  => Assert.NotEqual(
104
69
  KeyOf(Request(references: ["a", "b"])),
105
70
  KeyOf(Request(references: ["b", "a"])));
106
71
 
107
- /// <summary>Sem referencia e com referencia sao pedidos diferentes.</summary>
108
72
  [Fact]
109
73
  public void AddingAReferenceChangesTheKey()
110
74
  => Assert.NotEqual(KeyOf(Request()), KeyOf(Request(references: ["foto-a"])));
111
75
 
112
- /// <summary>Cada opcao participante, uma rodada.</summary>
113
76
  [Theory]
114
77
  [MemberData(nameof(OptionVariations))]
115
78
  public void EveryParticipatingOptionChangesTheKey(MediaOptions variation)
116
79
  => Assert.NotEqual(KeyOf(Request()), KeyOf(Request(options: variation)));
117
80
 
118
- /// <summary>As variacoes de opcao exercitadas, uma por campo canonico.</summary>
119
81
  public static TheoryData<MediaOptions> OptionVariations() =>
120
82
  [
121
83
  new MediaOptions { Quality = MediaQuality.High },
@@ -126,18 +88,12 @@ public sealed class MediaIdempotencyKeyTests
126
88
  new MediaOptions { AspectRatio = "9:16" },
127
89
  ];
128
90
 
129
- /// <summary>
130
- /// O separador e um caractere de CONTROLE, e nao <c>|</c>. Um prompt que contivesse o
131
- /// separador visivel emendaria campos e faria dois pedidos diferentes colidirem — que
132
- /// e o defeito que faz um pedido reusar o artefato pago do outro.
133
- /// </summary>
134
91
  [Fact]
135
92
  public void APromptFullOfPipesDoesNotCollideWithAnotherRequest()
136
93
  => Assert.NotEqual(
137
94
  KeyOf(Request("gpt-image-2|Image|um sofa")),
138
95
  KeyOf(Request("um sofa")));
139
96
 
140
- /// <summary>Normalizacao de prompt e pura e conferivel sozinha.</summary>
141
97
  [Theory]
142
98
  [InlineData(" a b ", "a b")]
143
99
  [InlineData("a\n\nb", "a b")]
@@ -3,23 +3,10 @@ using Xunit;
3
3
 
4
4
  namespace Morph.AiKit.Tests;
5
5
 
6
- /// <summary>
7
- /// Canarios da maquina de estados de <see cref="MediaJob"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// Tres afirmacoes carregam o standard <c>ai-agents/media-pipeline.md</c> inteiro, e cada
11
- /// uma e o inverso literal de um defeito caro:
12
- /// <list type="number">
13
- /// <item><c>Publish</c> falha fora de <c>Approved</c> — publicar sem revisao humana.</item>
14
- /// <item><c>Approved</c> so existe por <c>Approve(revisor)</c> — "aprovado" sem quem.</item>
15
- /// <item><c>RejectedByPolicy</c> nao e retryable — retry que vira fatura.</item>
16
- /// </list>
17
- /// </remarks>
18
6
  public sealed class MediaJobStateTests
19
7
  {
20
8
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
21
9
 
22
- /// <summary>Uma marcacao valida, para os casos que nao estao medindo a marcacao.</summary>
23
10
  private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
24
11
 
25
12
  private static MediaJob Pending() => MediaJob.Create(
@@ -37,14 +24,6 @@ public sealed class MediaJobStateTests
37
24
  .Value;
38
25
  }
39
26
 
40
- // ---------------------------------------------------------------------------------
41
- // 1. Publish so a partir de Approved
42
- // ---------------------------------------------------------------------------------
43
-
44
- /// <summary>
45
- /// O atalho que alguem sempre quer: publicar direto de <c>Generated</c>. Ele nao
46
- /// existe, e a mensagem diz por que.
47
- /// </summary>
48
27
  [Fact]
49
28
  public void PublishFailsFromGeneratedBecauseNobodyReviewedIt()
50
29
  {
@@ -55,7 +34,6 @@ public sealed class MediaJobStateTests
55
34
  Assert.Contains("Approved", result.Error.Message, StringComparison.Ordinal);
56
35
  }
57
36
 
58
- /// <summary>Publish falha a partir de TODO estado que nao seja Approved — nao so de Generated.</summary>
59
37
  [Theory]
60
38
  [InlineData(MediaJobState.Pending)]
61
39
  [InlineData(MediaJobState.Generating)]
@@ -71,7 +49,6 @@ public sealed class MediaJobStateTests
71
49
  Assert.False(job.Publish(Label, T0).IsSuccess);
72
50
  }
73
51
 
74
- /// <summary>E, com a revisao humana no lugar, publica.</summary>
75
52
  [Fact]
76
53
  public void PublishSucceedsFromApproved()
77
54
  {
@@ -84,11 +61,6 @@ public sealed class MediaJobStateTests
84
61
  Assert.Equal(T0, published.Value.PublishedAt);
85
62
  }
86
63
 
87
- // ---------------------------------------------------------------------------------
88
- // 2. Approved so por Approve(revisor)
89
- // ---------------------------------------------------------------------------------
90
-
91
- /// <summary>A aprovacao carrega quem e quando — sem isso e um campo booleano.</summary>
92
64
  [Fact]
93
65
  public void ApproveStampsTheReviewerAndTheTime()
94
66
  {
@@ -100,15 +72,10 @@ public sealed class MediaJobStateTests
100
72
  Assert.Equal("cor confere", approved.Review.Note);
101
73
  }
102
74
 
103
- /// <summary>Revisor em branco nao carimba nada, e a recusa e na construcao da revisao.</summary>
104
75
  [Fact]
105
76
  public void AnAnonymousReviewIsRefusedAtConstruction()
106
77
  => Assert.Throws<ArgumentException>(() => MediaReview.ApprovedBy(" ", T0));
107
78
 
108
- /// <summary>
109
- /// Aprovar com um veredito de REJEICAO seria registrar o oposto do que o humano
110
- /// decidiu. O tipo recusa.
111
- /// </summary>
112
79
  [Fact]
113
80
  public void ApproveRefusesAReviewThatSaysRejected()
114
81
  {
@@ -118,19 +85,10 @@ public sealed class MediaJobStateTests
118
85
  Assert.Equal(MediaErrorCode.InvalidTransition, result.Error!.Code);
119
86
  }
120
87
 
121
- /// <summary>Aprovar antes de existirem bytes nao e transicao.</summary>
122
88
  [Fact]
123
89
  public void ApproveFailsBeforeTheBytesExist()
124
90
  => Assert.False(Pending().Approve(MediaReview.ApprovedBy("lucas", T0)).IsSuccess);
125
91
 
126
- // ---------------------------------------------------------------------------------
127
- // 3. RejectedByPolicy nao e retryable; Failed e
128
- // ---------------------------------------------------------------------------------
129
-
130
- /// <summary>
131
- /// A distincao que vale dinheiro. Bloqueio de moderacao e resposta definitiva; erro
132
- /// tecnico e tentativa perdida.
133
- /// </summary>
134
92
  [Fact]
135
93
  public void PolicyBlockIsTerminalAndTechnicalFailureIsNot()
136
94
  {
@@ -146,7 +104,6 @@ public sealed class MediaJobStateTests
146
104
  Assert.True(failed.StartGenerating().IsSuccess);
147
105
  }
148
106
 
149
- /// <summary>Um job em <c>Generated</c> carrega hash, caminho e proveniencia — os tres juntos.</summary>
150
107
  [Fact]
151
108
  public void GeneratedCarriesHashPathAndProvenanceTogether()
152
109
  {
@@ -158,7 +115,6 @@ public sealed class MediaJobStateTests
158
115
  Assert.Equal("gpt-image-2", job.Provenance!.ModelId);
159
116
  }
160
117
 
161
- /// <summary>Uma transicao recusada NAO muda o job: o registro e imutavel.</summary>
162
118
  [Fact]
163
119
  public void ARefusedTransitionLeavesTheJobUntouched()
164
120
  {