@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
@@ -4,9 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Evals;
6
6
 
7
- /// <summary>
8
- /// O cliente que RECUSA: a peca de que depende "o eval roda no CI sem credencial".
9
- /// </summary>
10
7
  public sealed class OfflineChatClientTests
11
8
  {
12
9
  private static OfflineChatClient Build(Func<string>? scenario = null)
@@ -19,13 +16,9 @@ public sealed class OfflineChatClientTests
19
16
 
20
17
  var metadata = Assert.IsType<ChatClientMetadata>(client.GetService(typeof(ChatClientMetadata)));
21
18
 
22
- // Estes DOIS campos entram na chave de cache. Se divergissem do run que gravou, todo
23
- // cenario daria miss e a cache quente seria indistinguivel de uma cache vazia.
24
19
  Assert.Equal("morph-fake", metadata.ProviderName);
25
20
  Assert.Equal("modelo-exemplar", metadata.DefaultModelId);
26
21
 
27
- // E o endpoint e NULO de proposito: ele nao entra na chave (medido na v10.9.0), e um
28
- // cliente que nunca liga para lugar nenhum nao deve anunciar um destino.
29
22
  Assert.Null(metadata.ProviderUri);
30
23
  }
31
24
 
@@ -59,9 +52,7 @@ public sealed class OfflineChatClientTests
59
52
  [Fact]
60
53
  public void TheScenarioNameIsReadAtCALLTIME_NotCaptured()
61
54
  {
62
- // Um fixture roda N cenarios com o MESMO cliente. Se o nome fosse capturado na
63
- // construcao, toda mensagem de miss nomearia o primeiro cenario e mandaria o leitor
64
- // investigar o arquivo errado.
55
+ // O fixture roda N cenarios com o mesmo cliente: o nome do cenario nao pode ser capturado na construcao.
65
56
  var current = "primeiro";
66
57
  using var client = Build(() => current);
67
58
 
@@ -3,30 +3,6 @@ using Microsoft.Extensions.AI;
3
3
 
4
4
  namespace Morph.AiKit.Tests.Fakes;
5
5
 
6
- /// <summary>
7
- /// <see cref="IChatClient"/> roteirizado: a costura que dispensa rede e chave de API na
8
- /// suite inteira do kit.
9
- /// </summary>
10
- /// <remarks>
11
- /// <para>
12
- /// <b>Por que ele e o coracao da historia de teste:</b> o unico ponto em que o kit toca um
13
- /// provider e um <see cref="IChatClient"/>. Trocando esse ponto por uma fila de respostas
14
- /// escritas a mao, TODO o resto — <c>ModelRegistry</c>, <c>AgentFactory</c>,
15
- /// <c>UsageCostMiddleware</c>, <c>ConversationStore</c> — passa a ser testavel sem socket,
16
- /// sem segredo e sem flake. Nenhum tipo desta pasta abre cliente HTTP nem le variavel de
17
- /// ambiente — o canario estrutural disso e um grep sobre <c>tests/</c> (chave de provider,
18
- /// leitura de ambiente, cliente HTTP) que precisa voltar VAZIO, e o canario de
19
- /// comportamento e
20
- /// <c>FakeChatClientTests.TheWholeSeamAnswersFromMemoryAloneWithNoConfigurationAndNoConnection</c>.
21
- /// </para>
22
- /// <para>
23
- /// <b>Chamada nao roteirizada e defeito de teste, nao "comportamento padrao".</b> Esgotada
24
- /// a fila, o fake lanca com a contagem exata de chamadas. Devolver uma resposta vazia
25
- /// silenciosamente faria um teste passar por engano — que e a unica coisa pior do que um
26
- /// teste falhando.
27
- /// </para>
28
- /// <para>Seguro para uso concorrente: fila, contadores e capturas vivem sob um lock unico.</para>
29
- /// </remarks>
30
6
  public sealed class FakeChatClient : IChatClient
31
7
  {
32
8
  private readonly Lock _gate = new();
@@ -34,12 +10,10 @@ public sealed class FakeChatClient : IChatClient
34
10
  private readonly List<ChatOptions?> _receivedOptions = [];
35
11
  private readonly List<IReadOnlyList<ChatMessage>> _receivedMessages = [];
36
12
 
37
- /// <summary>Cria um fake com a fila vazia. Enfileire antes de usar.</summary>
38
13
  public FakeChatClient()
39
14
  {
40
15
  }
41
16
 
42
- /// <summary>Cria um fake ja roteirizado com as respostas dadas, na ordem.</summary>
43
17
  public FakeChatClient(params ChatResponse[] scripted)
44
18
  {
45
19
  ArgumentNullException.ThrowIfNull(scripted);
@@ -50,7 +24,6 @@ public sealed class FakeChatClient : IChatClient
50
24
  }
51
25
  }
52
26
 
53
- /// <summary>Atalho: uma resposta de assistente por texto, na ordem dada.</summary>
54
27
  public static FakeChatClient Scripted(params string[] texts)
55
28
  {
56
29
  ArgumentNullException.ThrowIfNull(texts);
@@ -64,51 +37,35 @@ public sealed class FakeChatClient : IChatClient
64
37
  return client;
65
38
  }
66
39
 
67
- /// <summary>Quantas vezes o cliente foi invocado (streaming incluso).</summary>
68
40
  public int CallCount { get; private set; }
69
41
 
70
- /// <summary>
71
- /// O <c>ProviderName</c> anunciado no <see cref="ChatClientMetadata"/>. Configuravel
72
- /// porque ele ENTRA NA CHAVE da cache de eval: a arvore que este fake grava so e legivel
73
- /// depois pelo <c>OfflineChatClient</c> se os dois se apresentarem com a mesma
74
- /// identidade. Default preservado ("fake"): quem nao usa eval nao muda de comportamento.
75
- /// </summary>
42
+ // ProviderName e DefaultModelId entram na chave da cache de eval: o OfflineChatClient precisa anunciar os mesmos.
76
43
  public string MetadataProviderName { get; init; } = "fake";
77
44
 
78
- /// <summary>
79
- /// O <c>DefaultModelId</c> anunciado no <see cref="ChatClientMetadata"/>. Tambem entra na
80
- /// chave de cache. Nulo por default, como antes.
81
- /// </summary>
82
45
  public string? MetadataDefaultModelId { get; init; }
83
46
 
84
- /// <summary>Se <see cref="Dispose"/> ja foi chamado — usado para provar ownership de cache.</summary>
85
47
  public bool IsDisposed { get; private set; }
86
48
 
87
- /// <summary>As <see cref="ChatOptions"/> recebidas, uma entrada por chamada, na ordem.</summary>
88
49
  public IReadOnlyList<ChatOptions?> ReceivedOptions
89
50
  {
90
51
  get { lock (_gate) { return [.. _receivedOptions]; } }
91
52
  }
92
53
 
93
- /// <summary>As mensagens recebidas, uma entrada por chamada, na ordem.</summary>
94
54
  public IReadOnlyList<IReadOnlyList<ChatMessage>> ReceivedMessages
95
55
  {
96
56
  get { lock (_gate) { return [.. _receivedMessages]; } }
97
57
  }
98
58
 
99
- /// <summary>As opcoes da ultima chamada, ou nulo se nunca foi chamado.</summary>
100
59
  public ChatOptions? LastOptions
101
60
  {
102
61
  get { lock (_gate) { return _receivedOptions.Count == 0 ? null : _receivedOptions[^1]; } }
103
62
  }
104
63
 
105
- /// <summary>As mensagens da ultima chamada, ou nulo se nunca foi chamado.</summary>
106
64
  public IReadOnlyList<ChatMessage>? LastMessages
107
65
  {
108
66
  get { lock (_gate) { return _receivedMessages.Count == 0 ? null : _receivedMessages[^1]; } }
109
67
  }
110
68
 
111
- /// <summary>Enfileira uma resposta ja montada. Devolve o proprio fake, para encadear.</summary>
112
69
  public FakeChatClient Enqueue(ChatResponse response)
113
70
  {
114
71
  ArgumentNullException.ThrowIfNull(response);
@@ -121,18 +78,9 @@ public sealed class FakeChatClient : IChatClient
121
78
  return this;
122
79
  }
123
80
 
124
- /// <summary>
125
- /// Enfileira uma resposta de assistente com o texto dado e, opcionalmente, um
126
- /// <see cref="UsageDetails"/> controlado — e assim que o teste do
127
- /// <c>UsageCostMiddleware</c> (T11) verifica tarifa sem rede.
128
- /// </summary>
129
81
  public FakeChatClient EnqueueText(string text, UsageDetails? usage = null)
130
82
  => Enqueue(new ChatResponse(new ChatMessage(ChatRole.Assistant, text)) { Usage = usage });
131
83
 
132
- /// <summary>
133
- /// Enfileira uma resposta com contagem de tokens conhecida. Atalho para o caso mais
134
- /// comum de teste de custo.
135
- /// </summary>
136
84
  public FakeChatClient EnqueueTextWithUsage(
137
85
  string text,
138
86
  long inputTokens,
@@ -148,7 +96,6 @@ public sealed class FakeChatClient : IChatClient
148
96
  CachedInputTokenCount = cachedInputTokens,
149
97
  });
150
98
 
151
- /// <inheritdoc />
152
99
  public Task<ChatResponse> GetResponseAsync(
153
100
  IEnumerable<ChatMessage> messages,
154
101
  ChatOptions? options = null,
@@ -160,7 +107,6 @@ public sealed class FakeChatClient : IChatClient
160
107
  return Task.FromResult(Record(messages, options));
161
108
  }
162
109
 
163
- /// <inheritdoc />
164
110
  public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
165
111
  IEnumerable<ChatMessage> messages,
166
112
  ChatOptions? options = null,
@@ -180,7 +126,6 @@ public sealed class FakeChatClient : IChatClient
180
126
  await Task.CompletedTask.ConfigureAwait(false);
181
127
  }
182
128
 
183
- /// <inheritdoc />
184
129
  public object? GetService(Type serviceType, object? serviceKey = null)
185
130
  {
186
131
  ArgumentNullException.ThrowIfNull(serviceType);
@@ -198,7 +143,6 @@ public sealed class FakeChatClient : IChatClient
198
143
  return serviceType.IsInstanceOfType(this) ? this : null;
199
144
  }
200
145
 
201
- /// <inheritdoc />
202
146
  public void Dispose() => IsDisposed = true;
203
147
 
204
148
  private ChatResponse Record(IEnumerable<ChatMessage> messages, ChatOptions? options)
@@ -4,17 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Fakes;
6
6
 
7
- /// <summary>
8
- /// Canarios da costura de teste (T9). Um fake so vale se ele proprio for testado: um fake
9
- /// que devolve na ordem errada, ou que engole as <see cref="ChatOptions"/> recebidas,
10
- /// transforma todo teste que depende dele num teste que nao mede nada.
11
- /// </summary>
12
- /// <remarks>
13
- /// Todo <c>await</c> passa <c>TestContext.Current.CancellationToken</c> porque o analisador
14
- /// xUnit1051 cobra isso e <c>TreatWarningsAsErrors</c> transforma a cobranca em build
15
- /// vermelho. Nao ha <c>#pragma</c> aqui: a regra e boa (teste cancelavel) e o custo e um
16
- /// argumento.
17
- /// </remarks>
18
7
  public sealed class FakeChatClientTests
19
8
  {
20
9
  [Fact]
@@ -114,8 +103,6 @@ public sealed class FakeChatClientTests
114
103
  [Fact]
115
104
  public async Task UsageIsControlledByTheTestAndSurvivesTheRoundTrip()
116
105
  {
117
- // E assim que a T11 vai verificar tarifa sem rede: Usage conhecido entra,
118
- // Usage conhecido sai.
119
106
  using var client = new FakeChatClient();
120
107
  client.EnqueueTextWithUsage("ok", inputTokens: 1_000, outputTokens: 250, cachedInputTokens: 400);
121
108
 
@@ -155,17 +142,6 @@ public sealed class FakeChatClientTests
155
142
  [Fact]
156
143
  public async Task TheWholeSeamAnswersFromMemoryAloneWithNoConfigurationAndNoConnection()
157
144
  {
158
- // O caminho inteiro que os consumidores usam — documento JSON, registry, fabrica,
159
- // cliente — exercitado com nada alem de objetos em memoria: nenhuma chave, nenhum
160
- // arquivo, nenhum socket. E este o teste a apontar quando alguem perguntar "como o
161
- // kit testa IA sem provider".
162
- //
163
- // A propriedade "passa sem chave de provider no ambiente" NAO e afirmada aqui por
164
- // codigo: afirma-la exigiria ler variavel de ambiente, e um teste que faz isso
165
- // derruba o canario estrutural da T9 — o grep sobre tests/ (chave de provider,
166
- // leitura de ambiente, cliente HTTP) tem de voltar VAZIO. A propriedade e
167
- // DEMONSTRADA rodando a suite com o ambiente esvaziado, o que cobre os 50 testes
168
- // em vez de um.
169
145
  var document = """
170
146
  {
171
147
  "version": "2.0",
@@ -195,8 +171,6 @@ public sealed class FakeChatClientTests
195
171
  [Fact]
196
172
  public async Task ConcurrentCallersNeverLoseACallOrACapture()
197
173
  {
198
- // ConversationStore (T13) exercita duas sessoes sobre a MESMA instancia. Se o fake
199
- // perdesse chamada sob concorrencia, aquele teste mediria o fake, nao o store.
200
174
  using var client = new FakeChatClient();
201
175
  for (var i = 0; i < 50; i++)
202
176
  {
@@ -1,30 +1,12 @@
1
1
  namespace Morph.AiKit.Tests.Fakes;
2
2
 
3
- /// <summary>
4
- /// <see cref="TimeProvider"/> controlado pelo teste: o relogio so anda quando o teste manda.
5
- /// </summary>
6
- /// <remarks>
7
- /// <para>
8
- /// Existe <b>escrito a mao, em 20 linhas</b>, e nao via
9
- /// <c>Microsoft.Extensions.TimeProvider.Testing</c>, por uma razao de contrato desta
10
- /// feature: toda versao de pacote vem de <c>framework/ai-pin.json</c>, e aquele pacote nao
11
- /// esta no pin. Acrescenta-lo seria uma versao decidida fora do pin — exatamente a segunda
12
- /// verdade que o kit existe para impedir. O achado esta registrado no relatorio da T11.
13
- /// </para>
14
- /// <para>
15
- /// So o necessario esta implementado: <see cref="GetUtcNow"/> e o avanco manual. Timers nao
16
- /// sao usados por nenhum tipo do kit; herdar o comportamento base e mais honesto que
17
- /// simular um agendador que ninguem exercita.
18
- /// </para>
19
- /// </remarks>
3
+ // Escrito a mao: Microsoft.Extensions.TimeProvider.Testing nao esta no pin (framework/ai-pin.json).
20
4
  public sealed class FakeClock(DateTimeOffset start) : TimeProvider
21
5
  {
22
6
  private DateTimeOffset _now = start;
23
7
 
24
- /// <inheritdoc />
25
8
  public override DateTimeOffset GetUtcNow() => _now;
26
9
 
27
- /// <summary>Avanca o relogio. Rejeita andar para tras: o tempo do teste e monotono.</summary>
28
10
  public void Advance(TimeSpan delta)
29
11
  {
30
12
  ArgumentOutOfRangeException.ThrowIfLessThan(delta, TimeSpan.Zero);
@@ -2,30 +2,6 @@ using Morph.AiKit.Media;
2
2
 
3
3
  namespace Morph.AiKit.Tests.Fakes;
4
4
 
5
- /// <summary>
6
- /// <see cref="IMediaProvider"/> roteirizado: a costura que dispensa rede, chave de API e
7
- /// <b>fatura</b> na suite inteira de midia.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// <b>Por que ele existe:</b> o unico ponto do subsistema que gasta dinheiro e um
12
- /// <c>IMediaProvider</c>. Trocando-o por uma fila de respostas escritas a mao, todo o resto
13
- /// — <c>MediaGenerator</c>, <c>MediaWorker</c>, <c>MediaJob</c>, <c>MediaStore</c> — passa a
14
- /// ser testavel sem socket, sem segredo e sem cobranca. Mesmo desenho do
15
- /// <see cref="FakeChatClient"/>, pela mesma razao.
16
- /// </para>
17
- /// <para>
18
- /// Ele so e construivel porque <c>InternalsVisibleTo("Morph.AiKit.Tests")</c> existe no
19
- /// <c>.csproj</c> da biblioteca. Um projeto consumidor <b>nao</b> consegue escrever esta
20
- /// classe — e essa impossibilidade e a regra dura de <c>ai-agents/media-pipeline.md</c>,
21
- /// asserida em <c>MediaAccessibilityTests</c>.
22
- /// </para>
23
- /// <para>
24
- /// <b>Chamada nao roteirizada e defeito de teste.</b> Esgotada a fila, o fake devolve uma
25
- /// falha nomeada com a contagem de chamadas, em vez de inventar bytes — um artefato
26
- /// fabricado pelo proprio fake faria um teste passar por engano.
27
- /// </para>
28
- /// </remarks>
29
5
  internal sealed class FakeMediaProvider : IMediaProvider
30
6
  {
31
7
  private readonly Queue<MediaResult<MediaArtifact>> _scripted = new();
@@ -37,19 +13,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
37
13
  Supported = supports;
38
14
  }
39
15
 
40
- /// <inheritdoc />
41
16
  public string Provider { get; }
42
17
 
43
- /// <summary>Que tipo de midia este fake diz atender.</summary>
44
18
  public MediaKind Supported { get; }
45
19
 
46
- /// <summary>Quantas vezes foi chamado. E como um teste prova que NAO houve segunda chamada paga.</summary>
47
20
  public int Calls { get; private set; }
48
21
 
49
- /// <summary>Os pedidos vistos, na ordem.</summary>
50
22
  public List<MediaRequest> Seen { get; } = [];
51
23
 
52
- /// <summary>Enfileira bytes de sucesso.</summary>
53
24
  public FakeMediaProvider Returns(byte[] bytes, string mediaType = "image/png", string? revisedPrompt = null)
54
25
  {
55
26
  _scripted.Enqueue(MediaResult<MediaArtifact>.Ok(new MediaArtifact(
@@ -57,17 +28,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
57
28
  return this;
58
29
  }
59
30
 
60
- /// <summary>Enfileira uma recusa com o codigo dado — inclusive <c>PolicyBlocked</c>.</summary>
61
31
  public FakeMediaProvider Fails(MediaErrorCode code, string message = "roteirizado")
62
32
  {
63
33
  _scripted.Enqueue(MediaResult<MediaArtifact>.Fail(code, message));
64
34
  return this;
65
35
  }
66
36
 
67
- /// <inheritdoc />
68
37
  public bool Supports(MediaKind kind) => kind == Supported;
69
38
 
70
- /// <inheritdoc />
71
39
  public Task<MediaResult<MediaArtifact>> ProduceAsync(
72
40
  MediaRequest request,
73
41
  MediaAliasBinding binding,
@@ -3,40 +3,26 @@ using Morph.AiKit.Providers;
3
3
 
4
4
  namespace Morph.AiKit.Tests.Fakes;
5
5
 
6
- /// <summary>
7
- /// <see cref="IProviderClientFactory"/> de teste: devolve <see cref="FakeChatClient"/> e
8
- /// <b>conta quantas vezes foi chamada</b>. E o contador que torna a memoizacao do
9
- /// <c>ModelRegistry</c> uma afirmacao verificavel em vez de uma promessa.
10
- /// </summary>
11
6
  public sealed class FakeProviderClientFactory : IProviderClientFactory
12
7
  {
13
8
  private readonly Func<ModelAlias, IChatClient?> _create;
14
9
  private readonly Dictionary<string, IChatClient> _created = new(StringComparer.Ordinal);
15
10
  private readonly List<ModelAlias> _requested = [];
16
11
 
17
- /// <summary>Fabrica que devolve um <see cref="FakeChatClient"/> novo por alias.</summary>
18
12
  public FakeProviderClientFactory()
19
13
  : this(_ => new FakeChatClient())
20
14
  {
21
15
  }
22
16
 
23
- /// <summary>
24
- /// Fabrica com construcao sob medida — inclusive devolvendo nulo, para exercitar a
25
- /// recusa do registry a um cliente inexistente.
26
- /// </summary>
27
17
  public FakeProviderClientFactory(Func<ModelAlias, IChatClient?> create)
28
18
  => _create = create;
29
19
 
30
- /// <summary>Quantas vezes <see cref="CreateChatClient"/> foi realmente executada.</summary>
31
20
  public int CreateCount { get; private set; }
32
21
 
33
- /// <summary>Os aliases para os quais um cliente foi criado, na ordem.</summary>
34
22
  public IReadOnlyList<ModelAlias> Requested => _requested;
35
23
 
36
- /// <summary>Os clientes criados, por nome de alias.</summary>
37
24
  public IReadOnlyDictionary<string, IChatClient> Created => _created;
38
25
 
39
- /// <inheritdoc />
40
26
  public IChatClient CreateChatClient(ModelAlias alias)
41
27
  {
42
28
  ArgumentNullException.ThrowIfNull(alias);
@@ -2,55 +2,31 @@ using Morph.AiKit.Middleware;
2
2
 
3
3
  namespace Morph.AiKit.Tests.Fakes;
4
4
 
5
- /// <summary>
6
- /// Sink de teste: acumula os registros de custo publicados pelo
7
- /// <see cref="UsageCostMiddleware"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// Estava nos <c>outputs</c> da T9, mas depende de <see cref="IUsageCostSink"/> e
12
- /// <see cref="UsageCostRecord"/>, que sao <c>outputs</c> da T11. A T9 recusou-se a criar os
13
- /// dois tipos por fora (seriam arquivos de outra task, e duplica-los criaria a segunda
14
- /// verdade do registro de custo) e deixou este codigo pronto no seu relatorio; a errata E8
15
- /// do <c>spec.md</c> moveu formalmente o arquivo para ca. Nasce junto dos tipos que
16
- /// implementa.
17
- /// </para>
18
- /// <para>
19
- /// Seguro sob concorrencia pelo mesmo motivo do <see cref="FakeChatClient"/>: um mesmo
20
- /// pipeline atende varias conversas, e um sink que perdesse linha sob concorrencia faria o
21
- /// teste medir o sink em vez do middleware.
22
- /// </para>
23
- /// </remarks>
24
5
  public sealed class FakeUsageCostSink : IUsageCostSink
25
6
  {
26
7
  private readonly Lock _gate = new();
27
8
  private readonly List<UsageCostRecord> _records = [];
28
9
 
29
- /// <summary>Tudo o que foi publicado, na ordem. Snapshot — iterar e seguro.</summary>
30
10
  public IReadOnlyList<UsageCostRecord> Records
31
11
  {
32
12
  get { lock (_gate) { return [.. _records]; } }
33
13
  }
34
14
 
35
- /// <summary>O ultimo registro publicado, ou nulo se nenhum foi.</summary>
36
15
  public UsageCostRecord? Last
37
16
  {
38
17
  get { lock (_gate) { return _records.Count == 0 ? null : _records[^1]; } }
39
18
  }
40
19
 
41
- /// <summary>Quantas linhas de custo foram publicadas.</summary>
42
20
  public int Count
43
21
  {
44
22
  get { lock (_gate) { return _records.Count; } }
45
23
  }
46
24
 
47
- /// <summary>A soma dos custos <b>medidos</b> — os demais status nao entram na conta.</summary>
48
25
  public decimal MeasuredTotalUsd
49
26
  {
50
27
  get { lock (_gate) { return _records.Where(r => r.IsMeasuredCost).Sum(r => r.CostUsd); } }
51
28
  }
52
29
 
53
- /// <inheritdoc />
54
30
  public void Record(UsageCostRecord record)
55
31
  {
56
32
  ArgumentNullException.ThrowIfNull(record);
@@ -3,29 +3,17 @@ using Morph.AiKit.Media;
3
3
 
4
4
  namespace Morph.AiKit.Tests.Fakes;
5
5
 
6
- /// <summary>
7
- /// Blob em memoria. <b>Conta as gravacoes</b> — e a contagem e o que prova que o
8
- /// <see cref="MediaStore"/> deduplicou em vez de regravar.
9
- /// </summary>
10
- /// <remarks>
11
- /// Um teste que so confira "o hash e o mesmo" nao distingue dedupe de regravacao: os dois
12
- /// produzem o mesmo hash. Por isso <see cref="Writes"/> existe.
13
- /// </remarks>
14
6
  public sealed class InMemoryMediaBlobs : IMediaBlobs
15
7
  {
16
8
  private readonly ConcurrentDictionary<string, byte[]> _files = new(StringComparer.Ordinal);
17
9
 
18
- /// <summary>Quantas gravacoes de verdade aconteceram.</summary>
19
10
  public int Writes { get; private set; }
20
11
 
21
- /// <summary>Os caminhos gravados.</summary>
22
12
  public IReadOnlyCollection<string> Paths => _files.Keys.ToArray();
23
13
 
24
- /// <inheritdoc />
25
14
  public ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken)
26
15
  => ValueTask.FromResult(_files.ContainsKey(relativePath));
27
16
 
28
- /// <inheritdoc />
29
17
  public ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken)
30
18
  {
31
19
  Writes++;
@@ -34,25 +22,18 @@ public sealed class InMemoryMediaBlobs : IMediaBlobs
34
22
  }
35
23
  }
36
24
 
37
- /// <summary>
38
- /// <see cref="IMediaStore"/> em memoria, para os testes que precisam de UM storage e nao
39
- /// estao medindo o storage.
40
- /// </summary>
41
25
  public sealed class InMemoryMediaStore : IMediaStore
42
26
  {
43
27
  private readonly MediaStore _inner;
44
28
 
45
- /// <summary>Cria o store sobre um blob em memoria proprio.</summary>
46
29
  public InMemoryMediaStore()
47
30
  {
48
31
  Blobs = new InMemoryMediaBlobs();
49
32
  _inner = new MediaStore(Blobs);
50
33
  }
51
34
 
52
- /// <summary>O blob por baixo, para conferir gravacoes.</summary>
53
35
  public InMemoryMediaBlobs Blobs { get; }
54
36
 
55
- /// <inheritdoc />
56
37
  public ValueTask<MediaStoreEntry> PutAsync(
57
38
  ReadOnlyMemory<byte> bytes,
58
39
  string mediaType,
@@ -60,25 +41,20 @@ public sealed class InMemoryMediaStore : IMediaStore
60
41
  => _inner.PutAsync(bytes, mediaType, cancellationToken);
61
42
  }
62
43
 
63
- /// <summary>Acervo de jobs em memoria — a costura com o banco do projeto, em teste.</summary>
64
44
  public sealed class InMemoryMediaJobs : IMediaJobRepository
65
45
  {
66
46
  private readonly ConcurrentDictionary<Guid, MediaJob> _byId = new();
67
47
 
68
- /// <summary>Quantas gravacoes aconteceram. Prova que o worker persistiu cada passo.</summary>
69
48
  public int Saves { get; private set; }
70
49
 
71
- /// <inheritdoc />
72
50
  public ValueTask<MediaJob?> FindByIdempotencyKeyAsync(
73
51
  MediaIdempotencyKey key,
74
52
  CancellationToken cancellationToken)
75
53
  => ValueTask.FromResult(_byId.Values.FirstOrDefault(j => j.IdempotencyKey == key));
76
54
 
77
- /// <inheritdoc />
78
55
  public ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken)
79
56
  => ValueTask.FromResult(_byId.TryGetValue(id.Value, out var job) ? job : null);
80
57
 
81
- /// <inheritdoc />
82
58
  public ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken)
83
59
  {
84
60
  Saves++;
@@ -87,32 +63,26 @@ public sealed class InMemoryMediaJobs : IMediaJobRepository
87
63
  }
88
64
  }
89
65
 
90
- /// <summary>Resolvedor de alias de teste: um alias, um binding.</summary>
91
66
  public sealed class StubAliasResolver : IMediaAliasResolver
92
67
  {
93
68
  private readonly Dictionary<string, MediaAliasBinding> _bindings = new(StringComparer.Ordinal);
94
69
 
95
- /// <summary>Declara um alias.</summary>
96
70
  public StubAliasResolver With(MediaAliasBinding binding)
97
71
  {
98
72
  _bindings[binding.Alias] = binding;
99
73
  return this;
100
74
  }
101
75
 
102
- /// <inheritdoc />
103
76
  public MediaResult<MediaAliasBinding> Resolve(string alias)
104
77
  => _bindings.TryGetValue(alias, out var binding)
105
78
  ? MediaResult<MediaAliasBinding>.Ok(binding)
106
79
  : MediaResult<MediaAliasBinding>.Fail(MediaErrorCode.InvalidRequest, $"alias '{alias}' nao existe.");
107
80
  }
108
81
 
109
- /// <summary>Teto de custo de teste: aceita ate um limite, e registra o que foi pedido.</summary>
110
82
  public sealed class CappedBudget(decimal cap) : IMediaBudget
111
83
  {
112
- /// <summary>Os custos estimados que chegaram, na ordem.</summary>
113
84
  public List<decimal> Seen { get; } = [];
114
85
 
115
- /// <inheritdoc />
116
86
  public ValueTask<bool> TryReserveAsync(
117
87
  MediaRequest request,
118
88
  decimal estimatedCostUsd,
@@ -123,9 +93,7 @@ public sealed class CappedBudget(decimal cap) : IMediaBudget
123
93
  }
124
94
  }
125
95
 
126
- /// <summary>Relogio parado, para que o carimbo de tempo seja um fato do teste.</summary>
127
96
  public sealed class FixedMediaClock(DateTimeOffset now) : IMediaClock
128
97
  {
129
- /// <inheritdoc />
130
98
  public DateTimeOffset UtcNow { get; } = now;
131
99
  }
@@ -4,33 +4,10 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests;
6
6
 
7
- /// <summary>
8
- /// O canario da REGRA DURA de <c>ai-agents/media-pipeline.md</c>:
9
- /// <b>nunca gere midia dentro de um turno de conversa</b>.
10
- /// </summary>
11
- /// <remarks>
12
- /// <para>
13
- /// A regra nao e presa por prosa — e presa por ACESSIBILIDADE. O tipo que produz bytes e
14
- /// <c>internal</c> ao assembly; um handler de mensagem no projeto consumidor nao consegue
15
- /// nomea-lo, e o codigo nao compila. Este arquivo e o que mantem essa afirmacao verdadeira
16
- /// depois de qualquer refatoracao.
17
- /// </para>
18
- /// <para>
19
- /// <b>A mutacao foi rodada</b> (T10): tornar <c>IMediaProvider</c> publico derruba
20
- /// <see cref="TheTypeThatProducesBytesIsNotPublic"/> nomeando o defeito. Um canario que
21
- /// ninguem viu morder pode ser o canario errado.
22
- /// </para>
23
- /// <para>
24
- /// A leitura e por <b>reflexao sobre a assembly</b>, e nao por <c>grep</c> no fonte: o
25
- /// modificador que importa e o que sobreviveu a compilacao. Um <c>grep</c> por
26
- /// <c>"internal interface"</c> mediria o texto, e o texto e uma copia do objeto.
27
- /// </para>
28
- /// </remarks>
29
7
  public sealed class MediaAccessibilityTests
30
8
  {
31
9
  private static readonly Assembly Kit = typeof(MediaJob).Assembly;
32
10
 
33
- /// <summary>Os nomes que produzem, ou orquestram a producao de, bytes pagos.</summary>
34
11
  public static TheoryData<string> ByteProducingTypes() =>
35
12
  [
36
13
  "Morph.AiKit.Media.IMediaProvider",
@@ -44,10 +21,6 @@ public sealed class MediaAccessibilityTests
44
21
  "Morph.AiKit.Media.MediaArtifact",
45
22
  ];
46
23
 
47
- /// <summary>
48
- /// A asserção literal do plano:
49
- /// <c>typeof(MediaJob).Assembly.GetType("Morph.AiKit.Media.IMediaProvider")!.IsPublic == false</c>.
50
- /// </summary>
51
24
  [Fact]
52
25
  public void TheTypeThatProducesBytesIsNotPublic()
53
26
  {
@@ -61,7 +34,6 @@ public sealed class MediaAccessibilityTests
61
34
  "compilador. E prosa outra vez.");
62
35
  }
63
36
 
64
- /// <summary>Uma rodada por tipo: nenhum deles pode vazar para fora do assembly.</summary>
65
37
  [Theory]
66
38
  [MemberData(nameof(ByteProducingTypes))]
67
39
  public void NoByteProducingTypeIsVisibleOutsideTheAssembly(string fullName)
@@ -73,11 +45,6 @@ public sealed class MediaAccessibilityTests
73
45
  Assert.False(type.IsNestedPublic, $"{fullName} nao pode ser aninhado publico.");
74
46
  }
75
47
 
76
- /// <summary>
77
- /// O outro lado da mesma afirmacao: a superficie publica EXISTE, e ela nunca devolve
78
- /// bytes. Sem esta metade, tornar tudo internal passaria — e um subsistema inalcancavel
79
- /// tambem esta errado.
80
- /// </summary>
81
48
  [Fact]
82
49
  public void ThePublicEntryPointExistsAndNeverReturnsBytes()
83
50
  {
@@ -101,10 +68,6 @@ public sealed class MediaAccessibilityTests
101
68
  }
102
69
  }
103
70
 
104
- /// <summary>
105
- /// <see cref="MediaArtifact"/> — o registro que CARREGA os bytes — nao pode aparecer em
106
- /// nenhuma assinatura publica do subsistema. Ele existe entre dois tipos internal.
107
- /// </summary>
108
71
  [Fact]
109
72
  public void TheArtifactNeverAppearsInAPublicSignature()
110
73
  {