@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
@@ -1,23 +1,5 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// Tarifa de midia.
5
- /// </summary>
6
- /// <remarks>
7
- /// <para>
8
- /// <b>Nao e o <c>ModelPricing</c> do registry, e a diferenca e a UNIDADE.</b>
9
- /// <c>ModelPricing</c> cobra por 1M de tokens; imagem cobra por imagem e por qualidade, e
10
- /// video cobra por segundo de saida. Espremer as tres unidades na mesma tarifa produz custo
11
- /// errado em silencio — que e a pior forma de custo errado, porque nada grita.
12
- /// </para>
13
- /// <para>
14
- /// Os numeros NAO moram aqui. Eles moram nas tabelas datadas de
15
- /// <c>ai-agents/modalities-image-gen.md</c> (secao "Custo por chamada") e
16
- /// <c>ai-agents/media-video.md</c> (secao "Custo por segundo"), e sao carregados do
17
- /// <c>model-registry.v2.json</c> do projeto. Um preco chumbado em codigo envelhece sem que
18
- /// ninguem reveja o standard.
19
- /// </para>
20
- /// </remarks>
21
3
  public sealed record MediaPricing
22
4
  {
23
5
  private readonly decimal _low;
@@ -34,11 +16,8 @@ public sealed record MediaPricing
34
16
  _perSecond = perSecond;
35
17
  }
36
18
 
37
- /// <summary>A que tipo de midia esta tarifa se aplica.</summary>
38
19
  public MediaKind Kind { get; }
39
20
 
40
- /// <summary>Tarifa de imagem, em USD por imagem, por faixa de qualidade.</summary>
41
- /// <exception cref="ArgumentOutOfRangeException">Tarifa negativa.</exception>
42
21
  public static MediaPricing PerImage(decimal low, decimal medium, decimal high)
43
22
  {
44
23
  ArgumentOutOfRangeException.ThrowIfNegative(low);
@@ -48,8 +27,6 @@ public sealed record MediaPricing
48
27
  return new MediaPricing(MediaKind.Image, low, medium, high, 0m);
49
28
  }
50
29
 
51
- /// <summary>Tarifa de video, em USD por segundo de saida.</summary>
52
- /// <exception cref="ArgumentOutOfRangeException">Tarifa negativa.</exception>
53
30
  public static MediaPricing PerSecond(decimal usdPerSecond)
54
31
  {
55
32
  ArgumentOutOfRangeException.ThrowIfNegative(usdPerSecond);
@@ -57,16 +34,6 @@ public sealed record MediaPricing
57
34
  return new MediaPricing(MediaKind.Video, 0m, 0m, 0m, usdPerSecond);
58
35
  }
59
36
 
60
- /// <summary>
61
- /// Custo estimado do pedido, <b>antes</b> da chamada. E este numero que o teto por
62
- /// tenant/dia confronta e que a proveniencia grava: estimar depois e contabilidade,
63
- /// estimar antes e controle.
64
- /// </summary>
65
- /// <remarks>
66
- /// Qualidade ausente conta como <see cref="MediaQuality.Low"/> porque a escada de custo
67
- /// da casa comeca em <c>low</c>; duracao ausente num pedido de video e recusa, e nao um
68
- /// palpite — arbitrar segundos e arbitrar dinheiro.
69
- /// </remarks>
70
37
  public MediaResult<decimal> EstimateUsd(MediaRequest request)
71
38
  {
72
39
  ArgumentNullException.ThrowIfNull(request);
@@ -1,36 +1,5 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// A proveniencia de uma midia gerada: o que foi pedido, a quem, com que opcoes, por quanto.
5
- /// </summary>
6
- /// <remarks>
7
- /// <para>
8
- /// Gravada no momento da geracao, uma por job. Sem ela nao existe auditoria de "que modelo
9
- /// gerou isto", nao existe reproducao do pedido, e nao existe base para responder a uma
10
- /// reclamacao sobre o que a imagem mostra.
11
- /// </para>
12
- /// <para>
13
- /// <see cref="RevisedPrompt"/> merece o campo proprio porque o provider frequentemente
14
- /// reescreve o prompt antes de gerar — guardar so o enviado esconde metade do que
15
- /// aconteceu.
16
- /// </para>
17
- /// <para>
18
- /// <see cref="C2paPreserved"/> nao e enfeite: os metadados C2PA sao a unica prova tecnica
19
- /// de origem que a imagem carrega, e um resize "para economizar CDN" os apaga. O campo
20
- /// registra se o SEU pipeline os manteve. E ele nao substitui a marcacao visivel: C2PA cai
21
- /// com screenshot e re-encode, e <c>not_detected</c> nunca significa "feito por humano".
22
- /// </para>
23
- /// </remarks>
24
- /// <param name="Provider">Familia do SDK: <c>openai</c>, <c>google</c>.</param>
25
- /// <param name="ModelId">Id do modelo no provider — o resolvido pelo alias, nunca o alias.</param>
26
- /// <param name="PromptFinal">O texto efetivamente enviado, ja montado pelo template.</param>
27
- /// <param name="RevisedPrompt">O que o provider reescreveu, ou nulo quando ele nao devolve.</param>
28
- /// <param name="RefsHash">sha256 das referencias, na mesma ordem da chave de pedido.</param>
29
- /// <param name="Options">Forma canonica das opcoes enviadas.</param>
30
- /// <param name="ResponseId">Id do lado do provider, para abrir chamado.</param>
31
- /// <param name="EstimatedCostUsd">Custo estimado <b>antes</b> da chamada.</param>
32
- /// <param name="CreatedAt">Quando.</param>
33
- /// <param name="C2paPreserved">Se os metadados de proveniencia sobreviveram ao pipeline.</param>
34
3
  public sealed record MediaProvenance(
35
4
  string Provider,
36
5
  string ModelId,
@@ -2,74 +2,33 @@ using System.Security.Cryptography;
2
2
 
3
3
  namespace Morph.AiKit.Media;
4
4
 
5
- /// <summary>Que tipo de midia o pedido produz. Decide provider, tarifa e limites.</summary>
6
5
  public enum MediaKind
7
6
  {
8
- /// <summary>Imagem.</summary>
9
7
  Image = 0,
10
8
 
11
- /// <summary>Video.</summary>
12
9
  Video = 1,
13
10
  }
14
11
 
15
- /// <summary>
16
- /// Para que serve a midia pedida.
17
- /// </summary>
18
- /// <remarks>
19
- /// <b>E dado, nao convencao de prompt.</b> A finalidade decide qualidade minima, teto de
20
- /// custo, se a midia pode ser publicada e se a marcacao visivel e obrigatoria — decisoes que
21
- /// o codigo precisa tomar. Deixa-las implicitas numa frase do prompt e como uma regra vira
22
- /// folclore; as duas ultimas sao lidas por <see cref="MediaPurposeExtensions"/> e cobradas em
23
- /// <see cref="MediaJob.Publish"/>.
24
- /// </remarks>
25
12
  public enum MediaPurpose
26
13
  {
27
- /// <summary>
28
- /// Foto de catalogo. Revisao humana obrigatoria; qualidade alta na aprovada; marcacao
29
- /// visivel obrigatoria para publicar — cobrada por <see cref="MediaJob.Publish"/>.
30
- /// </summary>
31
14
  CatalogPhoto = 0,
32
15
 
33
- /// <summary>
34
- /// Peca de marketing. Revisao humana obrigatoria; marcacao visivel obrigatoria — e a
35
- /// obrigatoriedade e COBRADA por <see cref="MediaJob.Publish"/>, nao prometida aqui.
36
- /// </summary>
37
16
  Marketing = 1,
38
17
 
39
- /// <summary>
40
- /// Rascunho interno. <b>Nunca publicavel</b> — e <see cref="MediaJob.Publish"/> recusa,
41
- /// em vez de deixar a frase valendo sozinha.
42
- /// </summary>
43
18
  Draft = 2,
44
19
  }
45
20
 
46
- /// <summary>Qualidade pedida. <c>null</c> em <see cref="MediaOptions"/> = nao envia ao provider.</summary>
47
21
  public enum MediaQuality
48
22
  {
49
- /// <summary>Candidatas. A escada de custo comeca aqui, sempre.</summary>
50
23
  Low = 0,
51
24
 
52
- /// <summary>Meio-termo.</summary>
53
25
  Medium = 1,
54
26
 
55
- /// <summary>So na vencedora, depois da escolha humana.</summary>
56
27
  High = 2,
57
28
  }
58
29
 
59
- /// <summary>
60
- /// Uma imagem de referencia do pedido — a foto real do produto que a edicao preserva.
61
- /// </summary>
62
- /// <remarks>
63
- /// <see cref="Sha256Hex"/> e calculado no construtor e entra na chave de idempotencia do
64
- /// PEDIDO: trocar a foto de referencia <b>e</b> outro pedido, mesmo com o prompt identico.
65
- /// Por isso o hash de CADA referencia participa, e nao a contagem delas.
66
- /// </remarks>
67
30
  public sealed class MediaReference
68
31
  {
69
- /// <summary>Cria a referencia e ja fixa o hash do conteudo.</summary>
70
- /// <param name="name">Como o prompt se refere a ela ("image 1 = o sofa").</param>
71
- /// <param name="mediaType">MIME dos bytes.</param>
72
- /// <param name="bytes">O conteudo.</param>
73
32
  public MediaReference(string name, string mediaType, ReadOnlyMemory<byte> bytes)
74
33
  {
75
34
  ArgumentException.ThrowIfNullOrWhiteSpace(name);
@@ -81,54 +40,36 @@ public sealed class MediaReference
81
40
  Sha256Hex = Convert.ToHexStringLower(SHA256.HashData(bytes.Span));
82
41
  }
83
42
 
84
- /// <summary>Como o prompt indexa esta referencia.</summary>
85
43
  public string Name { get; }
86
44
 
87
- /// <summary>MIME dos bytes.</summary>
88
45
  public string MediaType { get; }
89
46
 
90
- /// <summary>O conteudo.</summary>
91
47
  public ReadOnlyMemory<byte> Bytes { get; }
92
48
 
93
- /// <summary>sha256 do conteudo, em hex minusculo.</summary>
94
49
  public string Sha256Hex { get; }
95
50
  }
96
51
 
97
- /// <summary>
98
- /// Opcoes do pedido. <b><c>null</c> significa "nao envia ao provider"</b> — nunca "manda o
99
- /// default do kit": inventar default aqui e afirmar sobre o provider o que o kit nao sabe.
100
- /// </summary>
52
+ // null = nao envia ao provider; inventar default aqui afirmaria sobre o provider o que o kit nao sabe.
101
53
  public sealed record MediaOptions
102
54
  {
103
- /// <summary>Nenhuma opcao declarada.</summary>
104
55
  public static readonly MediaOptions None = new();
105
56
 
106
- /// <summary>Qualidade pedida.</summary>
107
57
  public MediaQuality? Quality { get; init; }
108
58
 
109
- /// <summary>Quantas candidatas numa chamada. Cobra por unidade.</summary>
110
59
  public int? Count { get; init; }
111
60
 
112
- /// <summary>Largura em pixels.</summary>
113
61
  public int? Width { get; init; }
114
62
 
115
- /// <summary>Altura em pixels.</summary>
116
63
  public int? Height { get; init; }
117
64
 
118
- /// <summary>MIME de saida.</summary>
119
65
  public string? MediaType { get; init; }
120
66
 
121
- /// <summary>Duracao, para video. O Veo aceita 4, 6 ou 8 s — e exige 8 s com referencias.</summary>
67
+ // O Veo aceita 4, 6 ou 8 s, e exige 8 s com referencias.
122
68
  public int? DurationSeconds { get; init; }
123
69
 
124
- /// <summary>Proporcao, para video ("16:9", "9:16").</summary>
125
70
  public string? AspectRatio { get; init; }
126
71
 
127
- /// <summary>
128
- /// Forma canonica das opcoes, para a chave de idempotencia. Ordem fixa e escrita a mao
129
- /// de proposito: reflexao sobre as propriedades faria a chave mudar quando alguem
130
- /// reordenasse o tipo, e uma chave que muda sozinha refaz a chamada paga.
131
- /// </summary>
72
+ // Ordem fixa e escrita a mao: reordenar o tipo nao pode mudar a chave e refazer a chamada paga.
132
73
  public string ToCanonicalString()
133
74
  => string.Join('|',
134
75
  $"quality={Quality?.ToString() ?? "-"}",
@@ -140,27 +81,14 @@ public sealed record MediaOptions
140
81
  $"ar={AspectRatio ?? "-"}");
141
82
  }
142
83
 
143
- /// <summary>
144
- /// O pedido de midia: tudo o que decide a chamada paga, e nada mais.
145
- /// </summary>
146
- /// <param name="Alias">O apelido do <c>model-registry.v2.json</c>. O pedido nunca cita modelo.</param>
147
- /// <param name="Prompt">O prompt final, ja montado pelo template versionado.</param>
148
- /// <param name="Purpose">Para que serve. Ver <see cref="MediaPurpose"/>.</param>
149
84
  public sealed record MediaRequest(string Alias, string Prompt, MediaPurpose Purpose)
150
85
  {
151
- /// <summary>Imagem ou video.</summary>
152
86
  public MediaKind Kind { get; init; } = MediaKind.Image;
153
87
 
154
- /// <summary>As referencias de produto. Vazio = geracao do zero.</summary>
155
88
  public IReadOnlyList<MediaReference> References { get; init; } = [];
156
89
 
157
- /// <summary>As opcoes. Ver <see cref="MediaOptions"/>.</summary>
158
90
  public MediaOptions Options { get; init; } = MediaOptions.None;
159
91
 
160
- /// <summary>
161
- /// Recusa um pedido sem o minimo. Result, e nao excecao: pedido malformado vem de
162
- /// entrada de usuario, que e caminho previsto.
163
- /// </summary>
164
92
  public MediaResult<MediaRequest> Validate()
165
93
  {
166
94
  if (string.IsNullOrWhiteSpace(Alias))
@@ -1,59 +1,24 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// Por que uma operacao de midia recusou. Discriminado por codigo, e nao por tipo de
5
- /// excecao, porque quem trata precisa do MOTIVO — e porque recusa aqui e caminho previsto,
6
- /// nunca fluxo de controle por excecao.
7
- /// </summary>
8
3
  public enum MediaErrorCode
9
4
  {
10
- /// <summary>O pedido nao tem o minimo para ser aceito (prompt vazio, alias vazio).</summary>
11
5
  InvalidRequest = 0,
12
6
 
13
- /// <summary>O teto de custo do tenant/dia recusaria a chamada. Recusa no ENQUEUE.</summary>
14
7
  BudgetExceeded = 1,
15
8
 
16
- /// <summary>Falha tecnica do provider (429, 5xx, timeout). Retryable.</summary>
17
9
  ProviderFailure = 2,
18
10
 
19
- /// <summary>
20
- /// Bloqueio de moderacao do provider. <b>Terminal</b>: retentar gasta para receber o
21
- /// mesmo "nao", e em provider que cobra o bloqueio gasta duas vezes.
22
- /// </summary>
23
11
  PolicyBlocked = 3,
24
12
 
25
- /// <summary>A transicao pedida nao existe a partir do estado atual do job.</summary>
26
13
  InvalidTransition = 4,
27
14
 
28
- /// <summary>O job pedido nao existe no acervo do projeto.</summary>
29
15
  NotFound = 5,
30
16
 
31
- /// <summary>Nao ha tarifa declarada para o alias — o custo nao pode ser estimado.</summary>
32
17
  PricingMissing = 6,
33
18
  }
34
19
 
35
- /// <summary>Uma recusa, com codigo e frase.</summary>
36
- /// <param name="Code">O motivo, para quem decide.</param>
37
- /// <param name="Message">A frase, para quem le o log.</param>
38
20
  public sealed record MediaError(MediaErrorCode Code, string Message);
39
21
 
40
- /// <summary>
41
- /// Result do subsistema de midia.
42
- /// </summary>
43
- /// <remarks>
44
- /// <para>
45
- /// <b>Por que <c>MediaResult</c> e nao <c>Result</c>:</b> este kit nao tem — e nao ganha
46
- /// aqui — um Result de proposito geral. Introduzir um tipo generico na raiz de
47
- /// <c>Morph.AiKit</c> e uma decisao transversal, que afeta toda superficie futura do kit e
48
- /// que uma feature de midia nao deve tomar de passagem. O escopo esta no nome.
49
- /// </para>
50
- /// <para>
51
- /// Excecao continua existindo para <b>falha de composicao</b> (argumento nulo na
52
- /// montagem), como na <c>AgentFactory</c>. O que nao existe e excecao para dizer "este job
53
- /// nao pode ser publicado neste estado": isso e informacao, e informacao viaja em Result.
54
- /// </para>
55
- /// </remarks>
56
- /// <typeparam name="T">O valor devolvido no sucesso.</typeparam>
57
22
  public sealed record MediaResult<T>
58
23
  {
59
24
  private readonly T _value;
@@ -65,28 +30,21 @@ public sealed record MediaResult<T>
65
30
  Error = error;
66
31
  }
67
32
 
68
- /// <summary>Se a operacao produziu valor.</summary>
69
33
  public bool IsSuccess { get; }
70
34
 
71
- /// <summary>A recusa, ou nulo quando <see cref="IsSuccess"/>.</summary>
72
35
  public MediaError? Error { get; }
73
36
 
74
- /// <summary>O valor. Ler no caminho de falha e erro de programacao, e grita.</summary>
75
- /// <exception cref="InvalidOperationException">Quando a operacao falhou.</exception>
76
37
  public T Value => IsSuccess
77
38
  ? _value
78
39
  : throw new InvalidOperationException(
79
40
  $"MediaResult<{typeof(T).Name}> em falha ({Error?.Code}): {Error?.Message}. " +
80
41
  "Confira IsSuccess antes de ler Value.");
81
42
 
82
- /// <summary>Sucesso com valor.</summary>
83
43
  public static MediaResult<T> Ok(T value) => new(true, value, null);
84
44
 
85
- /// <summary>Recusa nomeada.</summary>
86
45
  public static MediaResult<T> Fail(MediaErrorCode code, string message)
87
46
  => new(false, default!, new MediaError(code, message));
88
47
 
89
- /// <summary>Repassa a recusa de outro Result, preservando codigo e frase.</summary>
90
48
  public static MediaResult<T> FailLike<TOther>(MediaResult<TOther> other)
91
49
  => other.Error is { } e
92
50
  ? new MediaResult<T>(false, default!, e)
@@ -1,37 +1,12 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>O veredito de um humano sobre uma midia gerada.</summary>
4
3
  public enum MediaReviewDecision
5
4
  {
6
- /// <summary>Aprovada. Unica decisao que leva a <see cref="MediaJobState.Approved"/>.</summary>
7
5
  Approved = 0,
8
6
 
9
- /// <summary>Recusada.</summary>
10
7
  Rejected = 1,
11
8
  }
12
9
 
13
- /// <summary>
14
- /// Quem decidiu, o que decidiu, quando, e por que.
15
- /// </summary>
16
- /// <remarks>
17
- /// <para>
18
- /// <b>"Aprovado" sem quem e sem quando nao e revisao, e um campo booleano.</b> Por isso
19
- /// <see cref="Reviewer"/> e <see cref="DecidedAt"/> sao obrigatorios na construcao, e a
20
- /// construcao recusa um revisor em branco: um carimbo anonimo nao responde a pergunta que
21
- /// alguem vai fazer seis meses depois.
22
- /// </para>
23
- /// <para>
24
- /// <b>Este tipo nao reimplementa as transicoes.</b> Elas moram no <see cref="MediaJob"/>,
25
- /// que e o dono do estado; os metodos estaticos daqui sao a forma conveniente de aplicar
26
- /// uma revisao, e delegam. Uma segunda copia da regra "so Approved publica" e exatamente
27
- /// como as duas divergem.
28
- /// </para>
29
- /// <para>
30
- /// Um juiz LLM pode preencher <see cref="Note"/> como pre-filtro, mas nao pode ser o
31
- /// <see cref="Reviewer"/> de um <c>CatalogPhoto</c>: ele economiza o tempo do humano, nao
32
- /// assume a responsabilidade por um claim visual.
33
- /// </para>
34
- /// </remarks>
35
10
  public sealed record MediaReview
36
11
  {
37
12
  private MediaReview(string reviewer, MediaReviewDecision decision, DateTimeOffset decidedAt, string? note)
@@ -42,52 +17,38 @@ public sealed record MediaReview
42
17
  Note = note;
43
18
  }
44
19
 
45
- /// <summary>Identificador do humano que decidiu.</summary>
46
20
  public string Reviewer { get; }
47
21
 
48
- /// <summary>O veredito.</summary>
49
22
  public MediaReviewDecision Decision { get; }
50
23
 
51
- /// <summary>Quando.</summary>
52
24
  public DateTimeOffset DecidedAt { get; }
53
25
 
54
- /// <summary>Observacao livre — inclusive o veredito de um juiz LLM usado como pre-filtro.</summary>
55
26
  public string? Note { get; }
56
27
 
57
- /// <summary>Uma aprovacao humana.</summary>
58
- /// <exception cref="ArgumentException">Revisor em branco.</exception>
59
28
  public static MediaReview ApprovedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
60
29
  {
61
30
  ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
62
31
  return new MediaReview(reviewer, MediaReviewDecision.Approved, decidedAt, note);
63
32
  }
64
33
 
65
- /// <summary>Uma recusa humana.</summary>
66
- /// <exception cref="ArgumentException">Revisor em branco.</exception>
67
34
  public static MediaReview RejectedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
68
35
  {
69
36
  ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
70
37
  return new MediaReview(reviewer, MediaReviewDecision.Rejected, decidedAt, note);
71
38
  }
72
39
 
73
- /// <summary>Aprova o job. Delega a <see cref="MediaJob.Approve"/> — nao reimplementa a regra.</summary>
74
40
  public static MediaResult<MediaJob> Approve(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
75
41
  {
76
42
  ArgumentNullException.ThrowIfNull(job);
77
43
  return job.Approve(ApprovedBy(reviewer, decidedAt, note));
78
44
  }
79
45
 
80
- /// <summary>Recusa o job. Delega a <see cref="MediaJob.Reject"/>.</summary>
81
46
  public static MediaResult<MediaJob> Reject(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
82
47
  {
83
48
  ArgumentNullException.ThrowIfNull(job);
84
49
  return job.Reject(RejectedBy(reviewer, decidedAt, note));
85
50
  }
86
51
 
87
- /// <summary>
88
- /// Publica o job. Delega a <see cref="MediaJob.Publish"/>, que cobra as tres portas
89
- /// (estado Approved, finalidade publicavel, marcacao quando exigida).
90
- /// </summary>
91
52
  public static MediaResult<MediaJob> Publish(
92
53
  MediaJob job,
93
54
  MediaDisclosure? disclosure,
@@ -1,18 +1,5 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// A resolucao de um alias de midia: para quem ir, com que modelo, por qual tarifa.
5
- /// </summary>
6
- /// <remarks>
7
- /// O pedido cita o <b>alias</b>; a chave de idempotencia e a proveniencia citam o
8
- /// <b>modelo</b>. A diferenca importa: um alias pode ser repontado para outro modelo, e
9
- /// duas geracoes com o mesmo alias e modelos diferentes nao sao o mesmo pedido.
10
- /// </remarks>
11
- /// <param name="Alias">O apelido citado pelo pedido.</param>
12
- /// <param name="Provider">Familia do SDK.</param>
13
- /// <param name="Model">O id do modelo no provider.</param>
14
- /// <param name="Kind">Imagem ou video.</param>
15
- /// <param name="Pricing">A tarifa, na unidade certa. Ver <see cref="MediaPricing"/>.</param>
16
3
  public sealed record MediaAliasBinding(
17
4
  string Alias,
18
5
  string Provider,
@@ -20,78 +7,37 @@ public sealed record MediaAliasBinding(
20
7
  MediaKind Kind,
21
8
  MediaPricing Pricing);
22
9
 
23
- /// <summary>Resolve um alias de midia. Alias desconhecido e recusa nomeada, nunca nulo.</summary>
24
10
  public interface IMediaAliasResolver
25
11
  {
26
- /// <summary>O binding do alias.</summary>
27
12
  MediaResult<MediaAliasBinding> Resolve(string alias);
28
13
  }
29
14
 
30
- /// <summary>
31
- /// Onde os jobs ficam guardados. <b>A costura entre o kit e o banco do projeto.</b>
32
- /// </summary>
33
- /// <remarks>
34
- /// Sem tipo de infraestrutura na assinatura, pela mesma razao de
35
- /// <c>IConversationStore</c>: o kit nao conhece banco. No projeto, o handler fala com o
36
- /// <c>ApplicationDbContext</c> direto — sem camada de service, sem repository generico.
37
- /// Dentro de um job, <c>IDbContextFactory</c>, como o standard de Hangfire ja exige.
38
- /// </remarks>
39
15
  public interface IMediaJobRepository
40
16
  {
41
- /// <summary>
42
- /// O job de mesma chave de PEDIDO, se existir. <b>E a consulta que evita pagar duas
43
- /// vezes</b>, e por isso ela vem antes de qualquer chamada ao provider.
44
- /// </summary>
45
17
  ValueTask<MediaJob?> FindByIdempotencyKeyAsync(MediaIdempotencyKey key, CancellationToken cancellationToken);
46
18
 
47
- /// <summary>O job por id, ou nulo.</summary>
48
19
  ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken);
49
20
 
50
- /// <summary>Grava o estado corrente do job.</summary>
51
21
  ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken);
52
22
  }
53
23
 
54
- /// <summary>
55
- /// O teto de custo por tenant/dia.
56
- /// </summary>
57
- /// <remarks>
58
- /// <b>A reserva acontece no ENQUEUE</b>, com o custo estimado ANTES da chamada. Um alerta
59
- /// que chega depois da chamada e contabilidade, nao controle: o dinheiro ja saiu.
60
- /// </remarks>
61
24
  public interface IMediaBudget
62
25
  {
63
- /// <summary>
64
- /// Reserva o custo estimado. <c>false</c> = estourou o teto, e o enqueue recusa com
65
- /// <see cref="MediaErrorCode.BudgetExceeded"/>.
66
- /// </summary>
26
+ // Reservado no ENQUEUE, antes da chamada: alerta depois dela e contabilidade, nao controle.
67
27
  ValueTask<bool> TryReserveAsync(MediaRequest request, decimal estimatedCostUsd, CancellationToken cancellationToken);
68
28
  }
69
29
 
70
- /// <summary>
71
- /// Executa UM job: chama o provider, guarda os bytes, move o estado.
72
- /// </summary>
73
- /// <remarks>
74
- /// A interface e publica para que o <c>BackgroundService</c> do projeto (ou o job Hangfire)
75
- /// possa dispara-la; a <b>implementacao</b> e <c>internal</c>, porque e ela que segura o
76
- /// <c>IMediaProvider</c>. Um handler pode pedir "rode este job"; nao pode pedir "gere estes
77
- /// bytes agora".
78
- /// </remarks>
79
30
  public interface IMediaJobRunner
80
31
  {
81
- /// <summary>Roda o job ate o proximo estado estavel e devolve como ele ficou.</summary>
82
32
  Task<MediaResult<MediaJob>> RunAsync(MediaJobId id, CancellationToken cancellationToken = default);
83
33
  }
84
34
 
85
- /// <summary>Relogio, para que o carimbo de tempo seja testavel sem esperar o mundo girar.</summary>
86
35
  public interface IMediaClock
87
36
  {
88
- /// <summary>Agora.</summary>
89
37
  DateTimeOffset UtcNow { get; }
90
38
  }
91
39
 
92
- /// <summary>O relogio do sistema.</summary>
93
40
  public sealed class SystemMediaClock : IMediaClock
94
41
  {
95
- /// <inheritdoc />
96
42
  public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
97
43
  }
@@ -2,98 +2,43 @@ using System.Security.Cryptography;
2
2
 
3
3
  namespace Morph.AiKit.Media;
4
4
 
5
- /// <summary>
6
- /// O endereco de um conteudo: sha256 dos BYTES.
7
- /// </summary>
8
- /// <remarks>
9
- /// Responde "ja guardamos isto?" — nunca "ja pagamos por isto?", que e a pergunta do
10
- /// <see cref="MediaIdempotencyKey"/>. Confundir as duas e o defeito caro: dois pedidos
11
- /// diferentes podem produzir bytes identicos (e devem compartilhar UM arquivo), e o mesmo
12
- /// pedido nunca deve ser pago duas vezes (mesmo que os bytes saiam diferentes).
13
- /// </remarks>
14
- /// <param name="Value">sha256 em hex minusculo.</param>
5
+ // Endereca BYTES ("ja guardamos?"), nunca o pedido ("ja pagamos?"), que e o MediaIdempotencyKey.
15
6
  public readonly record struct MediaHash(string Value)
16
7
  {
17
- /// <summary>O hash do conteudo.</summary>
18
8
  public static MediaHash Of(ReadOnlySpan<byte> bytes)
19
9
  => new(Convert.ToHexStringLower(SHA256.HashData(bytes)));
20
10
 
21
- /// <summary>O hash, para log e para o caminho no blob.</summary>
22
11
  public override string ToString() => Value;
23
12
  }
24
13
 
25
- /// <summary>
26
- /// Onde os bytes ficam guardados. <b>A costura entre o kit e o blob do projeto</b> — o
27
- /// unico ponto do <see cref="MediaStore"/> que um projeto precisa implementar.
28
- /// </summary>
29
- /// <remarks>
30
- /// Deliberadamente sem tipo de infraestrutura na assinatura: nenhum <c>BlobClient</c>,
31
- /// nenhum <c>S3</c>, nenhum caminho absoluto. A implementacao de producao vive no projeto;
32
- /// a de teste vive nos fakes da suite. O kit nao conhece storage, pela mesma razao pela
33
- /// qual <c>IConversationStore</c> nao conhece banco.
34
- /// </remarks>
35
14
  public interface IMediaBlobs
36
15
  {
37
- /// <summary>Se o caminho relativo ja existe. Nunca lanca por ausencia.</summary>
38
16
  ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken);
39
17
 
40
- /// <summary>Grava o conteudo no caminho relativo.</summary>
41
18
  ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken);
42
19
  }
43
20
 
44
- /// <summary>O que o storage devolve sobre um conteudo guardado.</summary>
45
- /// <param name="Hash">sha256 dos bytes.</param>
46
- /// <param name="RelativePath">
47
- /// O caminho RELATIVO, que e o que o banco guarda. Caminho absoluto ou URL de provider
48
- /// amarram a linha ao disco de hoje ou a uma URL que expira — o Veo apaga o arquivo em 2
49
- /// dias, e todo agregador devolve URL temporaria.
50
- /// </param>
51
- /// <param name="MediaType">MIME.</param>
52
- /// <param name="AlreadyExisted">
53
- /// Se o conteudo ja estava la. <b>Nao e enfeite:</b> e como o chamador sabe que a gravacao
54
- /// foi deduplicada em vez de repetida.
55
- /// </param>
21
+ // Caminho RELATIVO, nunca URL de provider: o Veo apaga o arquivo em 2 dias e agregadores devolvem URL temporaria.
56
22
  public sealed record MediaStoreEntry(MediaHash Hash, string RelativePath, string MediaType, bool AlreadyExisted);
57
23
 
58
- /// <summary>Storage de midia enderecado por conteudo.</summary>
59
24
  public interface IMediaStore
60
25
  {
61
- /// <summary>Guarda os bytes e devolve o endereco. Conteudo repetido NAO e regravado.</summary>
62
26
  ValueTask<MediaStoreEntry> PutAsync(
63
27
  ReadOnlyMemory<byte> bytes,
64
28
  string mediaType,
65
29
  CancellationToken cancellationToken);
66
30
  }
67
31
 
68
- /// <summary>
69
- /// Storage enderecado por conteudo: o caminho e derivado do sha256, e gravar o mesmo
70
- /// conteudo duas vezes grava uma.
71
- /// </summary>
72
- /// <remarks>
73
- /// <para>
74
- /// O caminho e <c>ab/cd/&lt;hash&gt;.&lt;ext&gt;</c>. Os dois niveis de prefixo existem
75
- /// porque um diretorio unico com centenas de milhares de arquivos degrada listagem em
76
- /// praticamente todo sistema de arquivos e em varios object stores.
77
- /// </para>
78
- /// <para>
79
- /// A extensao vem do MIME e e o unico lugar do subsistema que faz esse mapa. Um MIME
80
- /// desconhecido vira <c>.bin</c> em vez de recusa: perder a extensao e cosmetico, perder os
81
- /// bytes pelos quais se pagou nao e.
82
- /// </para>
83
- /// </remarks>
84
32
  public sealed class MediaStore : IMediaStore
85
33
  {
86
34
  private readonly IMediaBlobs _blobs;
87
35
 
88
- /// <summary>Cria o storage sobre o blob do projeto.</summary>
89
- /// <exception cref="ArgumentNullException">Blob nulo.</exception>
90
36
  public MediaStore(IMediaBlobs blobs)
91
37
  {
92
38
  ArgumentNullException.ThrowIfNull(blobs);
93
39
  _blobs = blobs;
94
40
  }
95
41
 
96
- /// <inheritdoc />
97
42
  public async ValueTask<MediaStoreEntry> PutAsync(
98
43
  ReadOnlyMemory<byte> bytes,
99
44
  string mediaType,
@@ -113,14 +58,13 @@ public sealed class MediaStore : IMediaStore
113
58
  return new MediaStoreEntry(hash, path, mediaType, AlreadyExisted: false);
114
59
  }
115
60
 
116
- /// <summary>O caminho relativo de um conteudo. Puro, para poder ser conferido em teste.</summary>
61
+ // Dois niveis de prefixo: um diretorio com centenas de milhares de arquivos degrada a listagem.
117
62
  public static string RelativePathFor(MediaHash hash, string mediaType)
118
63
  {
119
64
  var value = hash.Value;
120
65
  return $"{value[..2]}/{value[2..4]}/{value}{ExtensionFor(mediaType)}";
121
66
  }
122
67
 
123
- /// <summary>MIME para extensao de arquivo. Desconhecido vira <c>.bin</c>, nunca recusa.</summary>
124
68
  public static string ExtensionFor(string mediaType) => mediaType.ToLowerInvariant() switch
125
69
  {
126
70
  "image/png" => ".png",