@polymorphism-tech/morph-spec 8.38.0 → 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 (227) hide show
  1. package/CLAUDE.md +19 -8
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  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 +3 -2
  8. package/framework/MORPH.md +1 -1
  9. package/framework/agents.json +36 -25
  10. package/framework/docs/CLI.md +2 -2
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/mcp/registry.json +33 -2
  14. package/framework/rules/infrastructure-standards.md +0 -1
  15. package/framework/rules/morph-workflow.md +9 -4
  16. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  17. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  18. package/framework/skills/core/morph-apply/SKILL.md +5 -1
  19. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  20. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  21. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  22. package/framework/skills/core/morph-implement/SKILL.md +6 -1
  23. package/framework/skills/core/morph-media/SKILL.md +1 -1
  24. package/framework/skills/core/morph-plan/SKILL.md +4 -3
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +1 -1
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/skills/core/morph-uiux/SKILL.md +6 -5
  30. package/framework/standards/STANDARDS.json +3 -3
  31. package/framework/standards/ai-agents/context-providers.md +16 -26
  32. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  33. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  34. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  35. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  36. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  37. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  38. package/framework/standards/architecture/code-comments.md +52 -16
  39. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  40. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  41. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  42. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  43. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  44. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  45. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  46. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  47. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  48. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  49. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  50. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  51. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  52. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  53. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  54. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  55. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  56. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  57. package/framework/templates/code/dotnet/test.cs +2 -87
  58. package/framework/templates/code/typescript/contracts.ts +140 -167
  59. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  60. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  61. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  62. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  63. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  64. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  65. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  66. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  110. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  111. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  112. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  113. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  114. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  115. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  116. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  117. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  142. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  143. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  144. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  145. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  146. package/framework/templates/integrations/asaas-client.cs +332 -387
  147. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  148. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  149. package/framework/templates/project/validate.js +97 -120
  150. package/package.json +2 -2
  151. package/src/commands/dashboard/dashboard.js +80 -53
  152. package/src/commands/mcp/mcp-setup.js +37 -9
  153. package/src/commands/project/doctor.js +3 -5
  154. package/src/commands/project/init.js +6 -3
  155. package/src/commands/project/install-plugin-cmd.js +1 -1
  156. package/src/commands/project/update.js +5 -0
  157. package/src/dashboard/app/App.jsx +80 -16
  158. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  159. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  160. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  161. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  162. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  163. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  164. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  165. package/src/dashboard/app/components/Markdown.jsx +47 -0
  166. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  167. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  168. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  169. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  170. package/src/dashboard/app/hooks/usePref.js +38 -0
  171. package/src/dashboard/app/lib/format.js +69 -0
  172. package/src/dashboard/app/lib/phases.js +56 -0
  173. package/src/dashboard/app/lib/project.js +50 -0
  174. package/src/dashboard/app/pages/Activity.jsx +24 -0
  175. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  176. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  177. package/src/dashboard/app/pages/Features.jsx +400 -0
  178. package/src/dashboard/app/pages/Guide.jsx +146 -0
  179. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  180. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  181. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  182. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  183. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  184. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  185. package/src/dashboard/app/pages/Standards.jsx +407 -332
  186. package/src/dashboard/app/styles/index.css +187 -47
  187. package/src/dashboard/index.html +14 -12
  188. package/src/dashboard/package.json +32 -24
  189. package/src/dashboard/scanner/activity-scanner.js +351 -0
  190. package/src/dashboard/scanner/agents-scanner.js +59 -1
  191. package/src/dashboard/scanner/docs-scanner.js +59 -0
  192. package/src/dashboard/scanner/feature-status.js +10 -0
  193. package/src/dashboard/scanner/features-scanner.js +360 -0
  194. package/src/dashboard/scanner/index.js +14 -7
  195. package/src/dashboard/scanner/phases-scanner.js +14 -15
  196. package/src/dashboard/scanner/portfolio.js +153 -0
  197. package/src/dashboard/scanner/project-api.js +195 -0
  198. package/src/dashboard/scanner/rules-scanner.js +42 -37
  199. package/src/dashboard/scanner/skills-scanner.js +105 -43
  200. package/src/dashboard/scanner/standards-scanner.js +2 -0
  201. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  202. package/src/lib/detectors/claude-config-detector.js +2 -7
  203. package/src/lib/detectors/index.js +5 -1
  204. package/src/lib/evaluator-report.js +22 -0
  205. package/src/lib/generators/settings-generator.js +208 -209
  206. package/src/lib/installers/mcp-installer.js +724 -719
  207. package/src/lib/installers/memory-installer.js +1 -2
  208. package/src/lib/projects/registry.js +154 -0
  209. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  210. package/src/scripts/install-plugin.js +1 -1
  211. package/src/scripts/setup-infra.js +2 -4
  212. package/src/utils/agents-installer.js +57 -12
  213. package/src/utils/banner.js +1 -1
  214. package/src/commands/index.js +0 -13
  215. package/src/commands/project/checkpoint.js +0 -209
  216. package/src/core/state/phase-state-machine.js +0 -197
  217. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  218. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  219. package/src/dashboard/scanner/squads-scanner.js +0 -13
  220. package/src/dashboard/scanner/state-scanner.js +0 -85
  221. package/src/lib/generators/index.js +0 -10
  222. package/src/lib/hooks/hook-executor.js +0 -167
  223. package/src/lib/validators/checkpoint-persistence.js +0 -39
  224. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  225. package/src/lib/validators/shared/index.js +0 -12
  226. package/src/lib/validators/shared/issue-counter.js +0 -14
  227. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -1,8 +1,3 @@
1
- // ============================================================
2
- // PGVECTOR ENTITY + CONFIGURATION TEMPLATE
3
- // Generated by MORPH Framework
4
- // ============================================================
5
-
6
1
  using System.ComponentModel.DataAnnotations.Schema;
7
2
  using Microsoft.EntityFrameworkCore;
8
3
  using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -12,10 +7,6 @@ using Pgvector;
12
7
 
13
8
  namespace {{NAMESPACE}}.Domain.Entities;
14
9
 
15
- /// <summary>
16
- /// Entity with vector embedding for similarity search via pgvector.
17
- /// Dimensions: 1536 (text-embedding-3-small). Adjust if using a different model.
18
- /// </summary>
19
10
  public class {{pascalCase FEATURE_NAME}}
20
11
  {
21
12
  public Guid Id { get; set; }
@@ -23,6 +14,7 @@ public class {{pascalCase FEATURE_NAME}}
23
14
  public string Content { get; set; } = string.Empty;
24
15
  public string? Metadata { get; set; }
25
16
 
17
+ // 1536 é a dimensão do text-embedding-3-small; troca junto com o modelo, aqui e na configuração
26
18
  [Column(TypeName = "vector(1536)")]
27
19
  public Vector? Embedding { get; set; }
28
20
 
@@ -30,12 +22,9 @@ public class {{pascalCase FEATURE_NAME}}
30
22
  public DateTimeOffset? UpdatedAt { get; set; }
31
23
  }
32
24
 
33
- // ============================================================
34
- // ENTITY TYPE CONFIGURATION
35
- // ============================================================
36
-
37
25
  namespace {{NAMESPACE}}.Infrastructure.Data.Configurations;
38
26
 
27
+ // Exige UseVector() no AddNpgsql e HasPostgresExtension("vector") no OnModelCreating
39
28
  public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration<{{pascalCase FEATURE_NAME}}>
40
29
  {
41
30
  public void Configure(EntityTypeBuilder<{{pascalCase FEATURE_NAME}}> builder)
@@ -49,7 +38,6 @@ public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration
49
38
  builder.Property(e => e.Embedding).HasColumnType("vector(1536)");
50
39
  builder.Property(e => e.CreatedAt).HasDefaultValueSql("now()");
51
40
 
52
- // HNSW index for cosine similarity search
53
41
  builder.HasIndex(e => e.Embedding)
54
42
  .HasMethod("hnsw")
55
43
  .HasOperators("vector_cosine_ops")
@@ -59,35 +47,3 @@ public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration
59
47
  builder.HasIndex(e => e.Title);
60
48
  }
61
49
  }
62
-
63
- // ============================================================
64
- // SETUP INSTRUCTIONS
65
- // ============================================================
66
- //
67
- // 1. Install NuGet packages:
68
- //
69
- // dotnet add package Pgvector.EntityFrameworkCore
70
- // dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
71
- //
72
- // 2. Configure NpgsqlDataSource with vector support in Program.cs:
73
- //
74
- // builder.Services.AddNpgsql<AppDbContext>(
75
- // connectionString,
76
- // npgsqlOptions => npgsqlOptions.UseVector());
77
- //
78
- // 3. Register in DbContext:
79
- //
80
- // public DbSet<{{pascalCase FEATURE_NAME}}> {{pascalCase FEATURE_NAME}}s => Set<{{pascalCase FEATURE_NAME}}>();
81
- //
82
- // protected override void OnModelCreating(ModelBuilder modelBuilder)
83
- // {
84
- // modelBuilder.HasPostgresExtension("vector");
85
- // modelBuilder.ApplyConfiguration(new {{pascalCase FEATURE_NAME}}Configuration());
86
- // }
87
- //
88
- // 4. Create and apply migration:
89
- //
90
- // dotnet ef migrations add Add{{pascalCase FEATURE_NAME}}WithVector -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
91
- // dotnet ef database update -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
92
- //
93
- // ============================================================
@@ -5,7 +5,6 @@ namespace {{pascalCase PROJECT_NAME}}.Extensions;
5
5
 
6
6
  public static class ResultExtensions
7
7
  {
8
- // For non-generic Result
9
8
  public static TOut Match<TOut>(
10
9
  this Result result,
11
10
  Func<TOut> onSuccess,
@@ -16,7 +15,6 @@ public static class ResultExtensions
16
15
  : onFailure(result.Error);
17
16
  }
18
17
 
19
- // 🔥 THIS is what your endpoint needs
20
18
  public static TOut Match<TIn, TOut>(
21
19
  this Result<TIn> result,
22
20
  Func<TIn, TOut> onSuccess,
@@ -1,15 +1,11 @@
1
- using FluentValidation;
2
-
3
- namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}Feature.{{pascalCase SLICE_NAME}};
4
-
5
- public class {{pascalCase SLICE_NAME}}Validator : AbstractValidator<{{pascalCase SLICE_NAME}}Request>
6
- {
7
- public {{pascalCase SLICE_NAME}}Validator()
8
- {
9
- // TODO: Add validation rules for {{pascalCase SLICE_NAME}}Request properties
10
- // Example:
11
- // RuleFor(c => c.Name)
12
- // .NotEmpty().WithMessage("Name is required")
13
- // .MaximumLength(200).WithMessage("Name must not exceed 200 characters");
14
- }
15
- }
1
+ using FluentValidation;
2
+
3
+ namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}Feature.{{pascalCase SLICE_NAME}};
4
+
5
+ public class {{pascalCase SLICE_NAME}}Validator : AbstractValidator<{{pascalCase SLICE_NAME}}Request>
6
+ {
7
+ public {{pascalCase SLICE_NAME}}Validator()
8
+ {
9
+ // TODO: Add validation rules for {{pascalCase SLICE_NAME}}Request properties
10
+ }
11
+ }
@@ -1,8 +1,3 @@
1
- // ============================================================
2
- // MICROSOFT AGENT FRAMEWORK TEMPLATE
3
- // Generated by MORPH Framework
4
- // ============================================================
5
-
6
1
  using System.ComponentModel;
7
2
  using System.Text.Json;
8
3
  using Microsoft.Extensions.AI;
@@ -10,15 +5,10 @@ using Microsoft.Extensions.Logging;
10
5
 
11
6
  namespace {{NAMESPACE}}.Agents.{{pascalCase FEATURE_NAME}};
12
7
 
13
- /// <summary>
14
- /// AI Agent for analyzing {{pascalCase FEATURE_NAME}} data.
15
- /// Uses Microsoft Agent Framework with IChatClient (gpt-4o-mini by default).
16
- /// </summary>
17
8
  public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
18
9
  IChatClient chatClient,
19
10
  ILogger<{{pascalCase FEATURE_NAME}}AnalyzerAgent> logger) : I{{pascalCase FEATURE_NAME}}AnalyzerAgent
20
11
  {
21
- /// <inheritdoc />
22
12
  public async Task<{{pascalCase FEATURE_NAME}}AnalysisResult> AnalyzeAsync(
23
13
  {{pascalCase FEATURE_NAME}}Data data,
24
14
  CancellationToken cancellationToken = default)
@@ -65,14 +55,12 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
65
55
  return result;
66
56
  }
67
57
 
68
- #region Tools
69
-
70
58
  [Description("Gets additional context for the analysis")]
71
59
  private async Task<string> GetContextAsync(
72
60
  [Description("What context is needed")] string query,
73
61
  CancellationToken ct = default)
74
62
  {
75
- // TODO: Implement context retrieval (e.g., from database or search)
63
+ // TODO: buscar o contexto (banco ou busca)
76
64
  await Task.CompletedTask;
77
65
  return $"Context for: {query}";
78
66
  }
@@ -82,19 +70,16 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
82
70
  [Description("Metric type to retrieve")] string metricType,
83
71
  CancellationToken ct = default)
84
72
  {
85
- // TODO: Implement metrics retrieval
73
+ // TODO: buscar as métricas
86
74
  await Task.CompletedTask;
87
75
  return $"Metrics for: {metricType}";
88
76
  }
89
77
 
90
- #endregion
91
-
92
- #region Response Parsing
93
-
94
78
  private {{pascalCase FEATURE_NAME}}AnalysisResult ParseResponse(string response)
95
79
  {
96
80
  try
97
81
  {
82
+ // o modelo às vezes cerca o JSON com ```json mesmo instruído a não fazer
98
83
  var json = response
99
84
  .Replace("```json", "")
100
85
  .Replace("```", "")
@@ -126,37 +111,4 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
126
111
  public List<string>? Recommendations { get; set; }
127
112
  public double ConfidenceScore { get; set; }
128
113
  }
129
-
130
- #endregion
131
114
  }
132
-
133
- // ============================================================
134
- // DEPENDENCY INJECTION SETUP
135
- // ============================================================
136
- //
137
- // In Program.cs:
138
- //
139
- // // Register IChatClient (Azure OpenAI with Managed Identity)
140
- // builder.Services.AddSingleton<IChatClient>(sp =>
141
- // {
142
- // var cfg = sp.GetRequiredService<IConfiguration>();
143
- // return new AzureOpenAIClient(
144
- // new Uri(cfg["AzureOpenAI:Endpoint"]!),
145
- // new DefaultAzureCredential())
146
- // .GetChatClient(cfg["AzureOpenAI:DeploymentName"] ?? "gpt-4o-mini")
147
- // .AsIChatClient();
148
- // });
149
- //
150
- // // Option A: Register as scoped service
151
- // builder.Services.AddScoped<I{{pascalCase FEATURE_NAME}}AnalyzerAgent, {{pascalCase FEATURE_NAME}}AnalyzerAgent>();
152
- //
153
- // // Option B: Register as keyed agent (recommended for multiple agents)
154
- // builder.AddAIAgent("{{pascalCase FEATURE_NAME}}Analyzer", (sp, key) =>
155
- // {
156
- // var chatClient = sp.GetRequiredService<IChatClient>();
157
- // return new ChatClientAgent(chatClient,
158
- // name: key,
159
- // instructions: "You are an expert {{pascalCase FEATURE_NAME}} analyst.");
160
- // });
161
- //
162
- // ============================================================
@@ -1,8 +1,3 @@
1
- // ============================================================
2
- // HANGFIRE JOB TEMPLATE (VSA Pattern — ApplicationDbContext direto)
3
- // Generated by MORPH Framework
4
- // ============================================================
5
-
6
1
  using Hangfire;
7
2
  using Microsoft.EntityFrameworkCore;
8
3
  using Microsoft.Extensions.Logging;
@@ -10,21 +5,11 @@ using {{pascalCase PROJECT_NAME}}.Database;
10
5
 
11
6
  namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}.Jobs;
12
7
 
13
- /// <summary>
14
- /// Background job for processing {{pascalCase FEATURE_NAME}}.
15
- /// Uses Hangfire for scheduling and retry handling.
16
- /// Injects ApplicationDbContext directly (no service layer, no generic repository).
17
- /// </summary>
18
8
  public class {{pascalCase FEATURE_NAME}}ProcessorJob(
19
9
  ApplicationDbContext db,
20
10
  I{{pascalCase FEATURE_NAME}}AnalyzerAgent analyzer,
21
11
  ILogger<{{pascalCase FEATURE_NAME}}ProcessorJob> logger) : I{{pascalCase FEATURE_NAME}}ProcessorJob
22
12
  {
23
- /// <summary>
24
- /// Executes the {{pascalCase FEATURE_NAME}} processing job.
25
- /// </summary>
26
- /// <param name="id">The {{pascalCase FEATURE_NAME}} ID to process</param>
27
- /// <param name="cancellationToken">Cancellation token</param>
28
13
  [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
29
14
  [Queue("default")]
30
15
  [JobDisplayName("{{pascalCase FEATURE_NAME}} Processing - ID: {0}")]
@@ -34,7 +19,6 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
34
19
 
35
20
  try
36
21
  {
37
- // Get the entity directly from the DbSet
38
22
  var entity = await db.Set<{{pascalCase FEATURE_NAME}}>().FindAsync(new object[] { id }, cancellationToken);
39
23
  if (entity is null)
40
24
  {
@@ -42,14 +26,12 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
42
26
  return;
43
27
  }
44
28
 
45
- // Check if already processed
46
29
  if (entity.Status == {{pascalCase FEATURE_NAME}}Status.Completed)
47
30
  {
48
31
  logger.LogInformation("{{pascalCase FEATURE_NAME}} {Id} already completed, skipping", id);
49
32
  return;
50
33
  }
51
34
 
52
- // Perform MAF agent analysis (if applicable)
53
35
  var analysisData = new {{pascalCase FEATURE_NAME}}Data(entity.Name);
54
36
  var analysis = await analyzer.AnalyzeAsync(analysisData, cancellationToken);
55
37
 
@@ -57,7 +39,6 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
57
39
  "{{pascalCase FEATURE_NAME}} {Id} analyzed. Confidence: {Confidence:P0}",
58
40
  id, analysis.ConfidenceScore);
59
41
 
60
- // Update the tracked entity and persist
61
42
  entity.MarkAsCompleted(analysis);
62
43
  await db.SaveChangesAsync(cancellationToken);
63
44
 
@@ -66,54 +47,17 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
66
47
  catch (Exception ex)
67
48
  {
68
49
  logger.LogError(ex, "Failed to process {{pascalCase FEATURE_NAME}} {Id}", id);
69
- throw; // Re-throw to trigger Hangfire retry
50
+ throw; // sem a exceção o Hangfire não aplica o AutomaticRetry
70
51
  }
71
52
  }
72
53
  }
73
54
 
74
- // ============================================================
75
- // RECURRING JOB CONFIGURATION
76
- // ============================================================
77
- //
78
- // For scheduled/recurring jobs, configure in Program.cs:
79
- //
80
- // // Run every hour
81
- // RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
82
- // "{{kebabCase FEATURE_NAME}}-processor",
83
- // job => job.ExecuteAsync(0, CancellationToken.None),
84
- // Cron.Hourly);
85
- //
86
- // // Run daily at midnight
87
- // RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
88
- // "{{kebabCase FEATURE_NAME}}-daily-processor",
89
- // job => job.ExecuteAsync(0, CancellationToken.None),
90
- // Cron.Daily);
91
- //
92
- // // Custom cron expression (every 15 minutes)
93
- // RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
94
- // "{{kebabCase FEATURE_NAME}}-frequent-processor",
95
- // job => job.ExecuteAsync(0, CancellationToken.None),
96
- // "*/15 * * * *");
97
- //
98
- // ============================================================
99
-
100
- // ============================================================
101
- // BATCH JOB TEMPLATE
102
- // ============================================================
103
-
104
- /// <summary>
105
- /// Batch job for processing multiple {{pascalCase FEATURE_NAME}}s.
106
- /// Queries pending items via LINQ on the DbSet and enqueues individual jobs.
107
- /// </summary>
108
55
  public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
109
56
  ApplicationDbContext db,
110
57
  ILogger<{{pascalCase FEATURE_NAME}}BatchProcessorJob> logger)
111
58
  {
112
- /// <summary>
113
- /// Processes all pending {{pascalCase FEATURE_NAME}}s.
114
- /// </summary>
115
59
  [AutomaticRetry(Attempts = 1)]
116
- [Queue("batch")]
60
+ [Queue("batch")] // a fila precisa constar em AddHangfireServer(o => o.Queues), senão ninguém a consome
117
61
  [JobDisplayName("{{pascalCase FEATURE_NAME}} Batch Processing")]
118
62
  public async Task ProcessAllPendingAsync(CancellationToken cancellationToken)
119
63
  {
@@ -127,7 +71,6 @@ public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
127
71
 
128
72
  foreach (var item in pending)
129
73
  {
130
- // Enqueue individual processing jobs
131
74
  BackgroundJob.Enqueue<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
132
75
  job => job.ExecuteAsync(item.Id, CancellationToken.None));
133
76
  }
@@ -135,41 +78,3 @@ public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
135
78
  logger.LogInformation("Enqueued {Count} processing jobs", pending.Count);
136
79
  }
137
80
  }
138
-
139
- // ============================================================
140
- // HANGFIRE CONFIGURATION
141
- // ============================================================
142
- //
143
- // In Program.cs:
144
- //
145
- // // Add Hangfire services
146
- // builder.Services.AddHangfire(config => config
147
- // .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
148
- // .UseSimpleAssemblyNameTypeSerializer()
149
- // .UseRecommendedSerializerSettings()
150
- // .UseSqlServerStorage(connectionString, new SqlServerStorageOptions
151
- // {
152
- // CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
153
- // SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
154
- // QueuePollInterval = TimeSpan.Zero,
155
- // UseRecommendedIsolationLevel = true,
156
- // DisableGlobalLocks = true
157
- // }));
158
- //
159
- // builder.Services.AddHangfireServer(options =>
160
- // {
161
- // options.Queues = new[] { "default", "batch" };
162
- // options.WorkerCount = Environment.ProcessorCount * 2;
163
- // });
164
- //
165
- // // Register jobs (ApplicationDbContext already registered via AddDbContext)
166
- // builder.Services.AddScoped<I{{pascalCase FEATURE_NAME}}ProcessorJob, {{pascalCase FEATURE_NAME}}ProcessorJob>();
167
- // builder.Services.AddScoped<{{pascalCase FEATURE_NAME}}BatchProcessorJob>();
168
- //
169
- // // In app pipeline
170
- // app.UseHangfireDashboard("/hangfire", new DashboardOptions
171
- // {
172
- // Authorization = new[] { new HangfireAuthorizationFilter() }
173
- // });
174
- //
175
- // ============================================================
@@ -1,8 +1,3 @@
1
- // ============================================================
2
- // TEST TEMPLATE (VSA Pattern — Handler + Validator)
3
- // Generated by MORPH Framework
4
- // ============================================================
5
-
6
1
  using FluentAssertions;
7
2
  using FluentValidation;
8
3
  using FluentValidation.Results;
@@ -14,15 +9,6 @@ using Xunit;
14
9
 
15
10
  namespace {{pascalCase PROJECT_NAME}}.Tests.Unit.Features.{{pascalCase FEATURE_NAME}};
16
11
 
17
- // ============================================================
18
- // Handler Tests
19
- // ============================================================
20
-
21
- /// <summary>
22
- /// Unit tests for {{pascalCase FEATURE_NAME}} handlers.
23
- /// Tests handlers directly against a real ApplicationDbContext backed by
24
- /// in-memory SQLite (no mocks — DbSet is the repository, DbContext is the unit of work).
25
- /// </summary>
26
12
  public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
27
13
  {
28
14
  private readonly SqliteConnection _connection;
@@ -30,6 +16,7 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
30
16
 
31
17
  public {{pascalCase FEATURE_NAME}}HandlerTests()
32
18
  {
19
+ // o banco :memory: some quando a conexão fecha, então ela vive o teste inteiro
33
20
  _connection = new SqliteConnection("DataSource=:memory:");
34
21
  _connection.Open();
35
22
 
@@ -47,19 +34,14 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
47
34
  _connection.Dispose();
48
35
  }
49
36
 
50
- #region Create Handler Tests
51
-
52
37
  [Fact]
53
38
  public async Task Create_HandleAsync_WithValidCommand_ReturnsSuccessResult()
54
39
  {
55
- // Arrange
56
40
  var command = new Create{{pascalCase FEATURE_NAME}}Request("Test {{pascalCase FEATURE_NAME}}");
57
41
  var handler = new Create{{pascalCase FEATURE_NAME}}Handler(_db);
58
42
 
59
- // Act
60
43
  var result = await handler.HandleAsync(command, CancellationToken.None);
61
44
 
62
- // Assert
63
45
  result.IsSuccess.Should().BeTrue();
64
46
  result.Value.Should().NotBeNull();
65
47
 
@@ -67,14 +49,9 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
67
49
  persisted.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
68
50
  }
69
51
 
70
- #endregion
71
-
72
- #region GetById Handler Tests
73
-
74
52
  [Fact]
75
53
  public async Task GetById_HandleAsync_WithValidId_ReturnsSuccessResult()
76
54
  {
77
- // Arrange
78
55
  var entity = CreateTestEntity("Test {{pascalCase FEATURE_NAME}}");
79
56
  _db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
80
57
  await _db.SaveChangesAsync();
@@ -82,10 +59,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
82
59
  var query = new Get{{pascalCase FEATURE_NAME}}ByIdRequest(entity.Id);
83
60
  var handler = new Get{{pascalCase FEATURE_NAME}}ByIdHandler(_db);
84
61
 
85
- // Act
86
62
  var result = await handler.HandleAsync(query, CancellationToken.None);
87
63
 
88
- // Assert
89
64
  result.IsSuccess.Should().BeTrue();
90
65
  result.Value.Should().NotBeNull();
91
66
  result.Value!.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
@@ -94,25 +69,17 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
94
69
  [Fact]
95
70
  public async Task GetById_HandleAsync_WithInvalidId_ReturnsFailureResult()
96
71
  {
97
- // Arrange
98
72
  var query = new Get{{pascalCase FEATURE_NAME}}ByIdRequest(Guid.CreateVersion7());
99
73
  var handler = new Get{{pascalCase FEATURE_NAME}}ByIdHandler(_db);
100
74
 
101
- // Act
102
75
  var result = await handler.HandleAsync(query, CancellationToken.None);
103
76
 
104
- // Assert
105
77
  result.IsSuccess.Should().BeFalse();
106
78
  }
107
79
 
108
- #endregion
109
-
110
- #region Update Handler Tests
111
-
112
80
  [Fact]
113
81
  public async Task Update_HandleAsync_WithValidCommand_ReturnsSuccessResult()
114
82
  {
115
- // Arrange
116
83
  var entity = CreateTestEntity("Original Name");
117
84
  _db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
118
85
  await _db.SaveChangesAsync();
@@ -120,10 +87,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
120
87
  var command = new Update{{pascalCase FEATURE_NAME}}Request(entity.Id, "Updated Name");
121
88
  var handler = new Update{{pascalCase FEATURE_NAME}}Handler(_db);
122
89
 
123
- // Act
124
90
  var result = await handler.HandleAsync(command, CancellationToken.None);
125
91
 
126
- // Assert
127
92
  result.IsSuccess.Should().BeTrue();
128
93
 
129
94
  var persisted = await _db.Set<{{pascalCase FEATURE_NAME}}>().SingleAsync(x => x.Id == entity.Id);
@@ -133,25 +98,17 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
133
98
  [Fact]
134
99
  public async Task Update_HandleAsync_WithNonExistentId_ReturnsFailureResult()
135
100
  {
136
- // Arrange
137
101
  var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7(), "Updated");
138
102
  var handler = new Update{{pascalCase FEATURE_NAME}}Handler(_db);
139
103
 
140
- // Act
141
104
  var result = await handler.HandleAsync(command, CancellationToken.None);
142
105
 
143
- // Assert
144
106
  result.IsSuccess.Should().BeFalse();
145
107
  }
146
108
 
147
- #endregion
148
-
149
- #region Delete Handler Tests
150
-
151
109
  [Fact]
152
110
  public async Task Delete_HandleAsync_WithValidId_ReturnsSuccessResult()
153
111
  {
154
- // Arrange
155
112
  var entity = CreateTestEntity("To Delete");
156
113
  _db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
157
114
  await _db.SaveChangesAsync();
@@ -159,10 +116,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
159
116
  var command = new Delete{{pascalCase FEATURE_NAME}}Request(entity.Id);
160
117
  var handler = new Delete{{pascalCase FEATURE_NAME}}Handler(_db);
161
118
 
162
- // Act
163
119
  var result = await handler.HandleAsync(command, CancellationToken.None);
164
120
 
165
- // Assert
166
121
  result.IsSuccess.Should().BeTrue();
167
122
 
168
123
  (await _db.Set<{{pascalCase FEATURE_NAME}}>().AnyAsync(x => x.Id == entity.Id)).Should().BeFalse();
@@ -171,57 +126,35 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
171
126
  [Fact]
172
127
  public async Task Delete_HandleAsync_WithNonExistentId_ReturnsFailureResult()
173
128
  {
174
- // Arrange
175
129
  var command = new Delete{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7());
176
130
  var handler = new Delete{{pascalCase FEATURE_NAME}}Handler(_db);
177
131
 
178
- // Act
179
132
  var result = await handler.HandleAsync(command, CancellationToken.None);
180
133
 
181
- // Assert
182
134
  result.IsSuccess.Should().BeFalse();
183
135
  }
184
136
 
185
- #endregion
186
-
187
- #region Helper Methods
188
-
189
137
  private static {{pascalCase FEATURE_NAME}} CreateTestEntity(string name)
190
138
  {
191
139
  return new {{pascalCase FEATURE_NAME}}
192
140
  {
193
141
  Id = Guid.CreateVersion7(),
194
142
  Name = name,
195
- // TODO: preencher demais campos obrigatórios da entidade
143
+ // TODO: demais campos obrigatórios da entidade
196
144
  };
197
145
  }
198
-
199
- #endregion
200
146
  }
201
147
 
202
- // ============================================================
203
- // Validator Tests
204
- // ============================================================
205
-
206
- /// <summary>
207
- /// Unit tests for {{pascalCase FEATURE_NAME}} validators.
208
- /// Tests FluentValidation rules using ValidateAsync.
209
- /// </summary>
210
148
  public class {{pascalCase FEATURE_NAME}}ValidatorTests
211
149
  {
212
- #region Create Validator Tests
213
-
214
150
  [Fact]
215
151
  public async Task CreateValidator_WithValidCommand_PassesValidation()
216
152
  {
217
- // Arrange
218
153
  var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
219
154
  var command = new Create{{pascalCase FEATURE_NAME}}Request("Valid Name");
220
155
 
221
- // Act
222
156
  ValidationResult result = await validator.ValidateAsync(command);
223
157
 
224
- // Assert
225
158
  result.IsValid.Should().BeTrue();
226
159
  result.Errors.Should().BeEmpty();
227
160
  }
@@ -232,14 +165,11 @@ public class {{pascalCase FEATURE_NAME}}ValidatorTests
232
165
  [InlineData(null)]
233
166
  public async Task CreateValidator_WithInvalidName_FailsValidation(string? name)
234
167
  {
235
- // Arrange
236
168
  var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
237
169
  var command = new Create{{pascalCase FEATURE_NAME}}Request(name!);
238
170
 
239
- // Act
240
171
  ValidationResult result = await validator.ValidateAsync(command);
241
172
 
242
- // Assert
243
173
  result.IsValid.Should().BeFalse();
244
174
  result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}Request.Name));
245
175
  }
@@ -247,50 +177,35 @@ public class {{pascalCase FEATURE_NAME}}ValidatorTests
247
177
  [Fact]
248
178
  public async Task CreateValidator_WithNameExceedingMaxLength_FailsValidation()
249
179
  {
250
- // Arrange
251
180
  var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
252
181
  var command = new Create{{pascalCase FEATURE_NAME}}Request(new string('A', 201));
253
182
 
254
- // Act
255
183
  ValidationResult result = await validator.ValidateAsync(command);
256
184
 
257
- // Assert
258
185
  result.IsValid.Should().BeFalse();
259
186
  result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}Request.Name));
260
187
  }
261
188
 
262
- #endregion
263
-
264
- #region Update Validator Tests
265
-
266
189
  [Fact]
267
190
  public async Task UpdateValidator_WithValidCommand_PassesValidation()
268
191
  {
269
- // Arrange
270
192
  var validator = new Update{{pascalCase FEATURE_NAME}}Validator();
271
193
  var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7(), "Updated Name");
272
194
 
273
- // Act
274
195
  ValidationResult result = await validator.ValidateAsync(command);
275
196
 
276
- // Assert
277
197
  result.IsValid.Should().BeTrue();
278
198
  }
279
199
 
280
200
  [Fact]
281
201
  public async Task UpdateValidator_WithInvalidId_FailsValidation()
282
202
  {
283
- // Arrange
284
203
  var validator = new Update{{pascalCase FEATURE_NAME}}Validator();
285
204
  var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.Empty, "Updated Name");
286
205
 
287
- // Act
288
206
  ValidationResult result = await validator.ValidateAsync(command);
289
207
 
290
- // Assert
291
208
  result.IsValid.Should().BeFalse();
292
209
  result.Errors.Should().Contain(e => e.PropertyName == nameof(Update{{pascalCase FEATURE_NAME}}Request.Id));
293
210
  }
294
-
295
- #endregion
296
211
  }