@polymorphism-tech/morph-spec 8.33.0 → 8.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CLAUDE.md +22 -2
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +43 -5
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +5 -4
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +2 -2
  8. package/framework/MORPH.md +20 -22
  9. package/framework/agents.json +198 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +238 -10
  13. package/framework/evals/proof-rubrics/README.md +76 -0
  14. package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
  15. package/framework/evals/proof-rubrics/fiacao.md +54 -0
  16. package/framework/evals/proof-rubrics/guard-cego.md +52 -0
  17. package/framework/evals/proof-rubrics/media-claims.md +109 -0
  18. package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
  19. package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
  20. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
  21. package/framework/evals/rubrics/architecture.md +30 -0
  22. package/framework/evals/rubrics/code-quality.md +26 -0
  23. package/framework/evals/rubrics/test-coverage.md +20 -0
  24. package/framework/hooks/README.md +11 -1
  25. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
  26. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
  27. package/framework/hooks/claude-code/core/chain.js +27 -7
  28. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  29. package/framework/hooks/claude-code/core/worktree-context.js +83 -14
  30. package/framework/hooks/claude-code/core/worktree-create.js +206 -0
  31. package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
  32. package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
  33. package/framework/hooks/shared/command-tokens.js +23 -0
  34. package/framework/hooks/shared/e2e-check.js +0 -0
  35. package/framework/hooks/shared/git-paths.js +25 -0
  36. package/framework/hooks/shared/hook-response.js +17 -0
  37. package/framework/hooks/shared/morph-cli.js +134 -0
  38. package/framework/rules/ai-agents-standards.md +14 -2
  39. package/framework/rules/infrastructure-standards.md +3 -1
  40. package/framework/schemas/ai-pin.schema.json +97 -0
  41. package/framework/schemas/config.schema.json +107 -0
  42. package/framework/schemas/feature.schema.json +23 -4
  43. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  44. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  45. package/framework/skills/core/morph-apply/SKILL.md +28 -7
  46. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  47. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  48. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  49. package/framework/skills/core/morph-implement/SKILL.md +20 -2
  50. package/framework/skills/core/morph-media/SKILL.md +215 -0
  51. package/framework/skills/core/morph-plan/SKILL.md +38 -4
  52. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  53. package/framework/skills/core/morph-review/SKILL.md +166 -9
  54. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  55. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  56. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  57. package/framework/standards/PROVENANCE.json +81 -3
  58. package/framework/standards/STANDARDS.json +1504 -830
  59. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  60. package/framework/standards/ai-agents/agent-session.md +256 -160
  61. package/framework/standards/ai-agents/agent-spec.md +196 -0
  62. package/framework/standards/ai-agents/batching.md +217 -193
  63. package/framework/standards/ai-agents/context-providers.md +261 -188
  64. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  65. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  66. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  67. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  68. package/framework/standards/ai-agents/guardrails.md +222 -0
  69. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  70. package/framework/standards/ai-agents/mcp-server.md +351 -250
  71. package/framework/standards/ai-agents/mcp-tools.md +250 -197
  72. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  73. package/framework/standards/ai-agents/media-video.md +267 -0
  74. package/framework/standards/ai-agents/middleware-patterns.md +241 -207
  75. package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
  76. package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
  77. package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
  78. package/framework/standards/ai-agents/modalities-vision.md +205 -201
  79. package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
  80. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  81. package/framework/standards/ai-agents/production.md +260 -231
  82. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  83. package/framework/standards/ai-agents/providers/model-registry.md +293 -158
  84. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  85. package/framework/standards/ai-agents/service-tier-flex.md +143 -135
  86. package/framework/standards/ai-agents/setup.md +166 -50
  87. package/framework/standards/ai-agents/structured-output.md +280 -249
  88. package/framework/standards/ai-agents/sweet-spot.md +129 -109
  89. package/framework/standards/ai-agents/testing-ai.md +236 -0
  90. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  91. package/framework/standards/ai-agents/workflows.md +27 -7
  92. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  93. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  94. package/framework/standards/infrastructure/docker/local-compose-isolation.md +90 -0
  95. package/framework/templates/REGISTRY.json +14 -1
  96. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  97. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  98. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  99. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  100. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  101. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  102. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  103. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  203. package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
  204. package/package.json +3 -3
  205. package/src/commands/e2e/e2e.js +91 -40
  206. package/src/commands/project/doctor.js +143 -6
  207. package/src/commands/project/env.js +95 -0
  208. package/src/commands/project/update.js +469 -153
  209. package/src/commands/project/worktree.js +274 -53
  210. package/src/commands/state/advance.js +24 -2
  211. package/src/commands/state/approve.js +33 -1
  212. package/src/commands/state/create.js +72 -11
  213. package/src/commands/state/delete.js +8 -0
  214. package/src/commands/state/finish.js +360 -53
  215. package/src/commands/state/gate-check.js +190 -6
  216. package/src/commands/state/score.js +36 -1
  217. package/src/commands/validation/verify.js +134 -25
  218. package/src/core/state/state-manager.js +9 -1
  219. package/src/dashboard/app/pages/HookInspector.jsx +430 -418
  220. package/src/dashboard/scanner/hooks-scanner.js +5 -1
  221. package/src/lib/ai/ai-checks.js +182 -0
  222. package/src/lib/ai/ai-project-scan.js +347 -0
  223. package/src/lib/ai/code-audit.js +181 -0
  224. package/src/lib/ai/comment-stripper.js +184 -0
  225. package/src/lib/ai/pin-drift.js +138 -0
  226. package/src/lib/ai/registry-audit.js +285 -0
  227. package/src/lib/e2e/compose-env.js +55 -14
  228. package/src/lib/e2e/stack-teardown.js +115 -0
  229. package/src/lib/evaluator-report.js +269 -15
  230. package/src/lib/generators/architecture-scanner.js +2 -1
  231. package/src/lib/git/default-branch.js +92 -4
  232. package/src/lib/graph/graphifyignore.js +2 -1
  233. package/src/lib/installers/effects.js +193 -0
  234. package/src/lib/installers/templates-reconcile.js +3 -3
  235. package/src/lib/risk-detector.js +284 -0
  236. package/src/lib/standards/verified-against.js +276 -0
  237. package/src/lib/tasks/eval-node.js +485 -0
  238. package/src/lib/tasks/linux-runner.js +297 -0
  239. package/src/lib/tasks/test-nodes.js +486 -0
  240. package/src/lib/tasks/test-runner.js +431 -37
  241. package/src/lib/validators/verify-runner.js +590 -28
  242. package/src/lib/worktree/dirs.js +32 -0
  243. package/src/lib/worktree/docker-audit.js +287 -0
  244. package/src/lib/worktree/env-allocator.js +69 -11
  245. package/src/lib/worktree/worktree-audit.js +70 -3
  246. package/src/scripts/setup-infra.js +24 -11
  247. package/src/utils/agents-installer.js +18 -8
  248. package/src/utils/claude-md-injector.js +10 -5
  249. package/src/utils/file-copier.js +28 -16
  250. package/src/utils/hooks-installer.js +75 -29
  251. package/src/utils/skills-installer.js +194 -187
  252. package/src/utils/version-checker.js +68 -3
  253. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -0,0 +1,495 @@
1
+ using System.Diagnostics;
2
+ using System.Text.Json;
3
+ using Microsoft.Extensions.AI;
4
+ using Morph.AiKit.Agents;
5
+ using Morph.AiKit.Providers;
6
+ using Morph.AiKit.Tests.Fakes;
7
+ using Xunit;
8
+
9
+ namespace Morph.AiKit.Tests;
10
+
11
+ /// <summary>
12
+ /// Canarios da <see cref="AgentFactory"/> (T10). Duas afirmacoes sao o coracao do arquivo:
13
+ /// <b>a precedencia do protocolo</b> (spec vence o alias quando presente, alias vence quando
14
+ /// a spec cala) e <b>a ordem do pipeline</b>, observada na sequencia real de chamadas, nao
15
+ /// na leitura do codigo.
16
+ /// </summary>
17
+ /// <remarks>
18
+ /// Todo <c>await</c> passa <c>TestContext.Current.CancellationToken</c> — exigencia do
19
+ /// analisador xUnit1051, que sob <c>TreatWarningsAsErrors</c> e build vermelho.
20
+ /// </remarks>
21
+ public sealed class AgentFactoryTests
22
+ {
23
+ // ---------------------------------------------------------------------------------
24
+ // Documentos de registry usados pelos casos
25
+ // ---------------------------------------------------------------------------------
26
+
27
+ /// <summary>Um alias Responses e um alias Chat COM o limite do GPT-5.4 declarado.</summary>
28
+ private const string TwoProtocolsDocument = """
29
+ {
30
+ "version": "2.0",
31
+ "defaultAlias": "responses-alias",
32
+ "aliases": {
33
+ "responses-alias": {
34
+ "provider": "openai",
35
+ "model": "gpt-5.4",
36
+ "api": "responses",
37
+ "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 }
38
+ },
39
+ "chat-alias": {
40
+ "provider": "openai",
41
+ "model": "gpt-5.4",
42
+ "api": "chat",
43
+ "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 },
44
+ "constraints": {
45
+ "toolCallingRequiresReasoningEffortNone": true,
46
+ "provenance": "Documentacao OpenAI citada na issue #93."
47
+ }
48
+ }
49
+ }
50
+ }
51
+ """;
52
+
53
+ private static AgentSpec Spec(string alias = "responses-alias", AgentApi? api = null)
54
+ => new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
55
+
56
+ // ---------------------------------------------------------------------------------
57
+ // doneCriteria 1 — a precedencia do api:
58
+ // ---------------------------------------------------------------------------------
59
+
60
+ /// <summary>
61
+ /// Spec calada: vale o <c>api:</c> do alias. O seletor de cliente e o observatorio —
62
+ /// ele recebe o protocolo <b>efetivo</b>, ja resolvido.
63
+ /// </summary>
64
+ [Fact]
65
+ public void TheAliasApiWinsWhenTheSpecDeclaresNone()
66
+ {
67
+ var seen = new List<AgentApi>();
68
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
69
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
70
+ {
71
+ ChatClientSelector = (_, api) => { seen.Add(api); return new FakeChatClient(); },
72
+ });
73
+
74
+ _ = factory.Create(Spec("responses-alias"));
75
+ _ = factory.Create(Spec("chat-alias"));
76
+
77
+ Assert.Equal([AgentApi.Responses, AgentApi.Chat], seen);
78
+ }
79
+
80
+ /// <summary>
81
+ /// Spec preenchida: ela vence, nos dois sentidos. A regra nao e reimplementada aqui —
82
+ /// a fabrica chama <see cref="AgentSpec.ResolveApi"/> (T7), e este teste morre se
83
+ /// alguem inverter a precedencia la ou copia-la para ca.
84
+ /// </summary>
85
+ [Fact]
86
+ public void TheSpecApiWinsOverTheAliasInBothDirections()
87
+ {
88
+ var seen = new List<AgentApi>();
89
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
90
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
91
+ {
92
+ ChatClientSelector = (_, api) => { seen.Add(api); return new FakeChatClient(); },
93
+ });
94
+
95
+ _ = factory.Create(Spec("responses-alias", AgentApi.Chat));
96
+ _ = factory.Create(Spec("chat-alias", AgentApi.Responses));
97
+
98
+ Assert.Equal([AgentApi.Chat, AgentApi.Responses], seen);
99
+ }
100
+
101
+ /// <summary>
102
+ /// Sem seletor, "a spec vence" nao pode ser cumprido: o registry memoiza UM cliente por
103
+ /// alias, construido para o protocolo do alias. A fabrica recusa nomeando os dois
104
+ /// protocolos, em vez de devolver um agente que diz falar um e fala outro.
105
+ /// </summary>
106
+ [Fact]
107
+ public void WithoutASelectorADivergentSpecApiIsRefusedNamingBothProtocols()
108
+ {
109
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
110
+ var factory = new AgentFactory(registry);
111
+
112
+ var error = Assert.Throws<AgentFactoryException>(
113
+ () => factory.Create(Spec("responses-alias", AgentApi.Chat)));
114
+
115
+ Assert.Equal(AgentFactoryError.ProtocolWithoutSelector, error.Reason);
116
+ Assert.Contains("Chat", error.Message, StringComparison.Ordinal);
117
+ Assert.Contains("Responses", error.Message, StringComparison.Ordinal);
118
+ Assert.Contains("responses-alias", error.Message, StringComparison.Ordinal);
119
+ }
120
+
121
+ /// <summary>
122
+ /// Sem divergencia e sem seletor, o caminho normal: o cliente memoizado do alias, uma
123
+ /// unica construcao por alias, por mais agentes que se criem.
124
+ /// </summary>
125
+ [Fact]
126
+ public void WithoutASelectorEveryAgentOfAnAliasSharesTheMemoizedClient()
127
+ {
128
+ var providerFactory = new FakeProviderClientFactory();
129
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, providerFactory);
130
+ var factory = new AgentFactory(registry);
131
+
132
+ _ = factory.Create(Spec("responses-alias"));
133
+ _ = factory.Create(Spec("responses-alias"));
134
+
135
+ Assert.Equal(1, providerFactory.CreateCount);
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------------
139
+ // doneCriteria 2 — a ordem do pipeline, observada nas chamadas
140
+ // ---------------------------------------------------------------------------------
141
+
142
+ /// <summary>
143
+ /// A ordem completa, medida numa invocacao com <b>duas</b> voltas de tool calling.
144
+ /// </summary>
145
+ /// <remarks>
146
+ /// O que a sequencia prova, e que nenhuma leitura de codigo prova: o encaixe de custo
147
+ /// entra e sai <b>uma</b> vez envolvendo as duas voltas (esta ACIMA do
148
+ /// <c>FunctionInvokingChatClient</c>), e o encaixe de telemetria entra e sai <b>duas</b>
149
+ /// vezes, uma por chamada ao modelo (esta ABAIXO). Trocar os dois de lugar muda esta
150
+ /// lista, e o teste morre.
151
+ /// </remarks>
152
+ [Fact]
153
+ public async Task ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt()
154
+ {
155
+ var log = new List<string>();
156
+ var leaf = ScriptedToolCallThenAnswer();
157
+
158
+ var agent = BuildAgentWithProbes(leaf, log, spec: Spec() with
159
+ {
160
+ Tools = [AIFunctionFactory.Create(() => "42", "answer")],
161
+ });
162
+
163
+ var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
164
+ var response = await agent.RunAsync(
165
+ "qual e a resposta?",
166
+ session,
167
+ cancellationToken: TestContext.Current.CancellationToken);
168
+
169
+ Assert.Equal("a resposta e 42", response.Text);
170
+ Assert.Equal(2, leaf.CallCount);
171
+ Assert.Equal(
172
+ [
173
+ "usage-cost:enter",
174
+ "telemetry:enter",
175
+ "telemetry:exit",
176
+ "telemetry:enter",
177
+ "telemetry:exit",
178
+ "usage-cost:exit",
179
+ ],
180
+ log);
181
+ }
182
+
183
+ /// <summary>
184
+ /// A razao de o custo ficar acima do FICC, em numeros: ele ve UMA resposta com o uso
185
+ /// somado das duas voltas (10+30 de entrada, 4+6 de saida). Abaixo do FICC veria duas
186
+ /// respostas parciais — e leria um <c>UsageDetails</c> que o proprio FICC muta para
187
+ /// acumular, o que faria a mesma conta sair diferente conforme a hora de ler.
188
+ /// </summary>
189
+ [Fact]
190
+ public async Task UsageCostAboveFunctionInvocationSeesTheUsageOfEveryToolRoundSummedOnce()
191
+ {
192
+ var log = new List<string>();
193
+ var leaf = ScriptedToolCallThenAnswer();
194
+ var seenUsage = new List<UsageDetails?>();
195
+
196
+ var agent = BuildAgentWithProbes(leaf, log, spec: Spec() with
197
+ {
198
+ Tools = [AIFunctionFactory.Create(() => "42", "answer")],
199
+ }, onUsageCostResponse: r => seenUsage.Add(r.Usage));
200
+
201
+ var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
202
+ _ = await agent.RunAsync(
203
+ "qual e a resposta?",
204
+ session,
205
+ cancellationToken: TestContext.Current.CancellationToken);
206
+
207
+ var usage = Assert.Single(seenUsage);
208
+ Assert.Equal(40, usage!.InputTokenCount);
209
+ Assert.Equal(10, usage.OutputTokenCount);
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------------
213
+ // As opcoes de nivel de agente (T7) chegam ao lugar que a T7 declarou
214
+ // ---------------------------------------------------------------------------------
215
+
216
+ /// <summary>
217
+ /// <c>MaxToolIterations</c> e <c>AllowConcurrentToolInvocation</c> tem destino nomeado
218
+ /// no contrato da T7. Aqui eles sao lidos <b>do objeto real</b> do pipeline, via
219
+ /// <c>GetService</c> — nao de um campo que a fabrica tenha guardado.
220
+ /// </summary>
221
+ [Fact]
222
+ public void TheAgentLevelKnobsReachTheFunctionInvokingChatClient()
223
+ {
224
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
225
+ var factory = new AgentFactory(registry);
226
+
227
+ var agent = factory.Create(Spec() with
228
+ {
229
+ Options = new AgentSpecOptions { MaxToolIterations = 3, AllowConcurrentToolInvocation = true },
230
+ });
231
+
232
+ var functionInvoking = Assert.IsType<FunctionInvokingChatClient>(
233
+ agent.GetService(typeof(FunctionInvokingChatClient)));
234
+
235
+ Assert.Equal(3, functionInvoking.MaximumIterationsPerRequest);
236
+ Assert.True(functionInvoking.AllowConcurrentInvocation);
237
+ }
238
+
239
+ /// <summary>
240
+ /// <c>TimeoutSeconds</c> vira <see cref="CancellationToken"/> de verdade: um cliente que
241
+ /// nunca responde e cancelado, em vez de pendurar a invocacao.
242
+ /// </summary>
243
+ [Fact]
244
+ public async Task TimeoutSecondsBecomesACancellationTokenThatActuallyCancels()
245
+ {
246
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
247
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
248
+ {
249
+ ChatClientSelector = (_, _) => new NeverAnsweringChatClient(),
250
+ });
251
+
252
+ var agent = factory.Create(Spec() with
253
+ {
254
+ Options = new AgentSpecOptions { TimeoutSeconds = 1 },
255
+ });
256
+
257
+ var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
258
+
259
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(
260
+ () => agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken));
261
+ }
262
+
263
+ // ---------------------------------------------------------------------------------
264
+ // A spec inteira chega as ChatOptions do agente
265
+ // ---------------------------------------------------------------------------------
266
+
267
+ /// <summary>
268
+ /// O modelo vem do alias (a spec nunca cita modelo), as instrucoes e o schema vem da
269
+ /// spec, e <see cref="AgentSpecOptions.ApplyTo"/> nao atropela nem as tools nem o
270
+ /// formato de resposta ja definidos.
271
+ /// </summary>
272
+ [Fact]
273
+ public async Task TheSpecReachesTheChatOptionsWithoutClobberingToolsOrSchema()
274
+ {
275
+ using var schemaDocument = JsonDocument.Parse("""{"type":"object"}""");
276
+ var leaf = new FakeChatClient().EnqueueText("ok");
277
+
278
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
279
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
280
+ {
281
+ ChatClientSelector = (_, _) => leaf,
282
+ });
283
+
284
+ var agent = factory.Create(Spec() with
285
+ {
286
+ Name = "agente-de-teste",
287
+ Description = "so para o teste",
288
+ Tools = [AIFunctionFactory.Create(() => "42", "answer")],
289
+ OutputSchema = schemaDocument.RootElement.Clone(),
290
+ Options = new AgentSpecOptions { Temperature = 0.2f, MaxOutputTokens = 128 },
291
+ });
292
+
293
+ var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
294
+ _ = await agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken);
295
+
296
+ var received = Assert.Single(leaf.ReceivedOptions);
297
+ Assert.NotNull(received);
298
+ Assert.Equal("voce e um agente de teste", received.Instructions);
299
+ Assert.Equal("gpt-5.4", received.ModelId);
300
+ Assert.Equal(0.2f, received.Temperature);
301
+ Assert.Equal(128, received.MaxOutputTokens);
302
+ Assert.Equal("answer", Assert.Single(received.Tools!).Name);
303
+ Assert.IsType<ChatResponseFormatJson>(received.ResponseFormat);
304
+ Assert.Equal("agente-de-teste", agent.Name);
305
+ }
306
+
307
+ // ---------------------------------------------------------------------------------
308
+ // Recusas
309
+ // ---------------------------------------------------------------------------------
310
+
311
+ /// <summary>
312
+ /// Limite de alias violado nao vira agente. A recusa carrega a procedencia escrita no
313
+ /// registry — sem ela, o limite nao teria sequer carregado (T8).
314
+ /// </summary>
315
+ [Fact]
316
+ public void AViolatedAliasConstraintRefusesTheAgentAndCarriesItsProvenance()
317
+ {
318
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
319
+ var factory = new AgentFactory(registry);
320
+
321
+ var error = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec("chat-alias") with
322
+ {
323
+ Tools = [AIFunctionFactory.Create(() => "42", "answer")],
324
+ Options = new AgentSpecOptions { ReasoningEffort = ReasoningEffort.High },
325
+ }));
326
+
327
+ Assert.Equal(AgentFactoryError.ConstraintViolated, error.Reason);
328
+ Assert.Contains("issue #93", error.Message, StringComparison.Ordinal);
329
+ }
330
+
331
+ /// <summary>
332
+ /// O MESMO alias, sem tools, nao viola nada: o limite tem quatro fatos, e tres nao
333
+ /// bastam. O canario existe para que a recusa acima nao seja confundida com "modelo de
334
+ /// raciocinio rejeita tool".
335
+ /// </summary>
336
+ [Fact]
337
+ public void TheSameAliasWithoutToolsCreatesTheAgentNormally()
338
+ {
339
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
340
+ var factory = new AgentFactory(registry);
341
+
342
+ var agent = factory.Create(Spec("chat-alias") with
343
+ {
344
+ Options = new AgentSpecOptions { ReasoningEffort = ReasoningEffort.High },
345
+ });
346
+
347
+ Assert.NotNull(agent);
348
+ }
349
+
350
+ /// <summary>Alias desconhecido falha na fabrica com o mesmo erro do registry.</summary>
351
+ [Fact]
352
+ public void AnUnknownAliasFailsAtCreateTime()
353
+ {
354
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
355
+ var factory = new AgentFactory(registry);
356
+
357
+ var error = Assert.Throws<ModelRegistryException>(() => factory.Create(Spec("nao-existe")));
358
+
359
+ Assert.Equal(ModelRegistryError.UnknownAlias, error.Reason);
360
+ }
361
+
362
+ /// <summary>Seletor que devolve nulo e recusa explicita, nunca agente sem cliente.</summary>
363
+ [Fact]
364
+ public void ASelectorThatReturnsNullIsRefusedInsteadOfProducingAClientlessAgent()
365
+ {
366
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
367
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
368
+ {
369
+ ChatClientSelector = (_, _) => null!,
370
+ });
371
+
372
+ var error = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec()));
373
+
374
+ Assert.Equal(AgentFactoryError.NoChatClient, error.Reason);
375
+ }
376
+
377
+ /// <summary>
378
+ /// Sem nenhum encaixe preenchido, o agente e valido e responde. Um agente sem custo e
379
+ /// sem telemetria nao e um agente pela metade.
380
+ /// </summary>
381
+ [Fact]
382
+ public async Task AnAgentWithNoHooksIsStillAValidAgentThatAnswers()
383
+ {
384
+ var leaf = new FakeChatClient().EnqueueText("pronto");
385
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
386
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
387
+ {
388
+ ChatClientSelector = (_, _) => leaf,
389
+ });
390
+
391
+ var agent = factory.Create(Spec());
392
+ var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
393
+ var response = await agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken);
394
+
395
+ Assert.Equal("pronto", response.Text);
396
+ }
397
+
398
+ // ---------------------------------------------------------------------------------
399
+ // Apoio dos testes
400
+ // ---------------------------------------------------------------------------------
401
+
402
+ /// <summary>
403
+ /// Roteiro de duas voltas: o modelo pede a tool, a tool responde, o modelo conclui. Os
404
+ /// numeros de uso sao escolhidos para que a soma (40/10) seja distinguivel de qualquer
405
+ /// uma das parcelas.
406
+ /// </summary>
407
+ private static FakeChatClient ScriptedToolCallThenAnswer()
408
+ => new FakeChatClient()
409
+ .Enqueue(new ChatResponse(new ChatMessage(
410
+ ChatRole.Assistant,
411
+ [new FunctionCallContent("chamada-1", "answer", null)]))
412
+ {
413
+ Usage = new UsageDetails { InputTokenCount = 10, OutputTokenCount = 4, TotalTokenCount = 14 },
414
+ })
415
+ .EnqueueTextWithUsage("a resposta e 42", inputTokens: 30, outputTokens: 6);
416
+
417
+ private static Microsoft.Agents.AI.AIAgent BuildAgentWithProbes(
418
+ FakeChatClient leaf,
419
+ List<string> log,
420
+ AgentSpec spec,
421
+ Action<ChatResponse>? onUsageCostResponse = null)
422
+ {
423
+ using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
424
+ var factory = new AgentFactory(registry, new AgentFactoryOptions
425
+ {
426
+ ChatClientSelector = (_, _) => leaf,
427
+ ConfigureUsageCost = (builder, _, _) =>
428
+ builder.Use(inner => new ProbeChatClient(inner, "usage-cost", log, onUsageCostResponse)),
429
+ ConfigureChatTelemetry = (builder, _, _) =>
430
+ builder.Use(inner => new ProbeChatClient(inner, "telemetry", log, onResponse: null)),
431
+ });
432
+
433
+ return factory.Create(spec);
434
+ }
435
+
436
+ /// <summary>
437
+ /// Marca a entrada e a saida de uma posicao do pipeline. E o unico jeito de observar a
438
+ /// ORDEM: ela nao esta em nenhuma propriedade, so na sequencia das chamadas.
439
+ /// </summary>
440
+ private sealed class ProbeChatClient(
441
+ IChatClient inner,
442
+ string tag,
443
+ List<string> log,
444
+ Action<ChatResponse>? onResponse) : DelegatingChatClient(inner)
445
+ {
446
+ public override async Task<ChatResponse> GetResponseAsync(
447
+ IEnumerable<ChatMessage> messages,
448
+ ChatOptions? options = null,
449
+ CancellationToken cancellationToken = default)
450
+ {
451
+ lock (log)
452
+ {
453
+ log.Add(tag + ":enter");
454
+ }
455
+
456
+ var response = await base.GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
457
+ onResponse?.Invoke(response);
458
+
459
+ lock (log)
460
+ {
461
+ log.Add(tag + ":exit");
462
+ }
463
+
464
+ return response;
465
+ }
466
+ }
467
+
468
+ /// <summary>Cliente que nunca responde — existe so para o timeout ter o que cancelar.</summary>
469
+ private sealed class NeverAnsweringChatClient : IChatClient
470
+ {
471
+ public async Task<ChatResponse> GetResponseAsync(
472
+ IEnumerable<ChatMessage> messages,
473
+ ChatOptions? options = null,
474
+ CancellationToken cancellationToken = default)
475
+ {
476
+ await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false);
477
+ throw new UnreachableException();
478
+ }
479
+
480
+ public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
481
+ IEnumerable<ChatMessage> messages,
482
+ ChatOptions? options = null,
483
+ CancellationToken cancellationToken = default)
484
+ => throw new NotSupportedException();
485
+
486
+ public object? GetService(Type serviceType, object? serviceKey = null)
487
+ => serviceType == typeof(ChatClientMetadata)
488
+ ? new ChatClientMetadata(providerName: "never")
489
+ : null;
490
+
491
+ public void Dispose()
492
+ {
493
+ }
494
+ }
495
+ }