@polymorphism-tech/morph-spec 2.2.0 → 2.4.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 (251) hide show
  1. package/CLAUDE.md +314 -1673
  2. package/LICENSE +72 -72
  3. package/README.md +515 -516
  4. package/bin/detect-agents.js +225 -225
  5. package/bin/morph-spec.js +358 -173
  6. package/bin/render-template.js +302 -302
  7. package/bin/semantic-detect-agents.js +246 -246
  8. package/bin/task-manager.js +429 -0
  9. package/bin/validate-agents-skills.js +251 -251
  10. package/bin/validate-agents.js +69 -69
  11. package/bin/validate-phase.js +263 -263
  12. package/bin/validate.js +369 -0
  13. package/content/.azure/README.md +293 -293
  14. package/content/.azure/docs/azure-devops-setup.md +454 -454
  15. package/content/.azure/docs/branch-strategy.md +398 -398
  16. package/content/.azure/docs/local-development.md +515 -515
  17. package/content/.azure/pipelines/pipeline-variables.yml +34 -34
  18. package/content/.azure/pipelines/prod-pipeline.yml +319 -319
  19. package/content/.azure/pipelines/staging-pipeline.yml +234 -234
  20. package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
  21. package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
  22. package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
  23. package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
  24. package/content/.claude/commands/morph-apply.md +221 -158
  25. package/content/.claude/commands/morph-archive.md +79 -79
  26. package/content/.claude/commands/morph-infra.md +209 -209
  27. package/content/.claude/commands/morph-preflight.md +227 -0
  28. package/content/.claude/commands/morph-proposal.md +122 -101
  29. package/content/.claude/commands/morph-status.md +86 -86
  30. package/content/.claude/commands/morph-troubleshoot.md +122 -0
  31. package/content/.claude/settings.local.json +15 -15
  32. package/content/.claude/skills/checklists/code-review.md +226 -0
  33. package/content/.claude/skills/checklists/morph-checklist.md +117 -0
  34. package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
  35. package/content/.claude/skills/infra/bicep-architect.md +126 -419
  36. package/content/.claude/skills/infra/container-specialist.md +131 -437
  37. package/content/.claude/skills/infra/devops-engineer.md +119 -405
  38. package/content/.claude/skills/integrations/asaas-financial.md +130 -333
  39. package/content/.claude/skills/integrations/azure-identity.md +142 -309
  40. package/content/.claude/skills/integrations/clerk-auth.md +108 -290
  41. package/content/.claude/skills/integrations/resend-email.md +119 -0
  42. package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
  43. package/content/.claude/skills/specialists/azure-architect.md +142 -142
  44. package/content/.claude/skills/specialists/code-analyzer.md +235 -0
  45. package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
  46. package/content/.claude/skills/specialists/ef-modeler.md +113 -200
  47. package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
  48. package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
  49. package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
  50. package/content/.claude/skills/specialists/standards-architect.md +156 -78
  51. package/content/.claude/skills/specialists/testing-specialist.md +126 -0
  52. package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
  53. package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
  54. package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
  55. package/content/.claude/skills/workflows/morph-replicate.md +213 -0
  56. package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
  57. package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
  58. package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
  59. package/content/.claude/skills/workflows/phase-tasks.md +164 -0
  60. package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
  61. package/content/.morph/.morphversion +5 -5
  62. package/content/.morph/archive/.gitkeep +25 -25
  63. package/content/.morph/config/agents.json +378 -242
  64. package/content/.morph/config/config.template.json +89 -108
  65. package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
  66. package/content/.morph/docs/workflows/design-impl.md +37 -0
  67. package/content/.morph/docs/workflows/fast-track.md +29 -0
  68. package/content/.morph/docs/workflows/full-morph.md +76 -0
  69. package/content/.morph/docs/workflows/standard.md +44 -0
  70. package/content/.morph/docs/workflows/ui-refresh.md +39 -0
  71. package/content/.morph/examples/api-nextjs/README.md +241 -241
  72. package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
  73. package/content/.morph/examples/api-nextjs/spec.md +399 -399
  74. package/content/.morph/examples/api-nextjs/tasks.md +168 -168
  75. package/content/.morph/examples/micro-saas/README.md +125 -125
  76. package/content/.morph/examples/micro-saas/contracts.cs +358 -358
  77. package/content/.morph/examples/micro-saas/decisions.md +246 -246
  78. package/content/.morph/examples/micro-saas/spec.md +236 -236
  79. package/content/.morph/examples/micro-saas/tasks.md +150 -150
  80. package/content/.morph/examples/multi-agent/README.md +309 -309
  81. package/content/.morph/examples/multi-agent/contracts.cs +433 -433
  82. package/content/.morph/examples/multi-agent/spec.md +479 -479
  83. package/content/.morph/examples/multi-agent/tasks.md +185 -185
  84. package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
  85. package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
  86. package/content/.morph/examples/scheduled-reports/spec.md +267 -0
  87. package/content/.morph/examples/state-v3.json +188 -0
  88. package/content/.morph/features/.gitkeep +25 -25
  89. package/content/.morph/hooks/README.md +190 -239
  90. package/content/.morph/hooks/pre-commit-agents.sh +24 -24
  91. package/content/.morph/hooks/pre-commit-all.sh +48 -48
  92. package/content/.morph/hooks/pre-commit-specs.sh +49 -49
  93. package/content/.morph/hooks/pre-commit-tests.sh +60 -60
  94. package/content/.morph/project.md +160 -160
  95. package/content/.morph/schemas/agent.schema.json +296 -296
  96. package/content/.morph/schemas/tasks.schema.json +220 -0
  97. package/content/.morph/specs/.gitkeep +20 -20
  98. package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
  99. package/content/.morph/standards/agent-framework-production.md +410 -0
  100. package/content/.morph/standards/agent-framework-setup.md +413 -453
  101. package/content/.morph/standards/agent-framework-workflows.md +349 -0
  102. package/content/.morph/standards/architecture.md +325 -325
  103. package/content/.morph/standards/azure.md +605 -379
  104. package/content/.morph/standards/coding.md +377 -377
  105. package/content/.morph/standards/dotnet10-migration.md +520 -494
  106. package/content/.morph/standards/fluent-ui-setup.md +590 -590
  107. package/content/.morph/standards/migration-guide.md +514 -514
  108. package/content/.morph/standards/passkeys-auth.md +423 -423
  109. package/content/.morph/standards/vector-search-rag.md +536 -536
  110. package/content/.morph/state.json +17 -17
  111. package/content/.morph/templates/FluentDesignTheme.cs +149 -149
  112. package/content/.morph/templates/MudTheme.cs +281 -281
  113. package/content/.morph/templates/agent.cs +163 -172
  114. package/content/.morph/templates/clarify-questions.md +159 -0
  115. package/content/.morph/templates/component.razor +239 -239
  116. package/content/.morph/templates/contracts/Commands.cs +74 -0
  117. package/content/.morph/templates/contracts/Entities.cs +25 -0
  118. package/content/.morph/templates/contracts/Queries.cs +74 -0
  119. package/content/.morph/templates/contracts/README.md +74 -0
  120. package/content/.morph/templates/contracts.cs +217 -217
  121. package/content/.morph/templates/decisions.md +123 -106
  122. package/content/.morph/templates/design-system.css +226 -226
  123. package/content/.morph/templates/infra/.dockerignore.example +89 -89
  124. package/content/.morph/templates/infra/Dockerfile.example +82 -82
  125. package/content/.morph/templates/infra/README.md +286 -286
  126. package/content/.morph/templates/infra/app-insights.bicep +63 -63
  127. package/content/.morph/templates/infra/app-service.bicep +164 -164
  128. package/content/.morph/templates/infra/container-app-env.bicep +49 -49
  129. package/content/.morph/templates/infra/container-app.bicep +156 -156
  130. package/content/.morph/templates/infra/deploy-checklist.md +426 -0
  131. package/content/.morph/templates/infra/deploy.ps1 +229 -229
  132. package/content/.morph/templates/infra/deploy.sh +208 -208
  133. package/content/.morph/templates/infra/key-vault.bicep +91 -91
  134. package/content/.morph/templates/infra/main.bicep +189 -189
  135. package/content/.morph/templates/infra/parameters.dev.json +29 -29
  136. package/content/.morph/templates/infra/parameters.prod.json +29 -29
  137. package/content/.morph/templates/infra/parameters.staging.json +29 -29
  138. package/content/.morph/templates/infra/sql-database.bicep +103 -103
  139. package/content/.morph/templates/infra/storage.bicep +106 -106
  140. package/content/.morph/templates/integrations/asaas-client.cs +387 -387
  141. package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
  142. package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
  143. package/content/.morph/templates/integrations/clerk-config.cs +258 -258
  144. package/content/.morph/templates/job.cs +171 -171
  145. package/content/.morph/templates/migration.cs +83 -83
  146. package/content/.morph/templates/proposal.md +141 -155
  147. package/content/.morph/templates/recap.md +94 -105
  148. package/content/.morph/templates/repository.cs +141 -141
  149. package/content/.morph/templates/saas/subscription.cs +347 -347
  150. package/content/.morph/templates/saas/tenant.cs +338 -338
  151. package/content/.morph/templates/service.cs +139 -139
  152. package/content/.morph/templates/simulation.md +353 -0
  153. package/content/.morph/templates/spec.md +149 -148
  154. package/content/.morph/templates/sprint-status.yaml +68 -68
  155. package/content/.morph/templates/state.template.json +222 -222
  156. package/content/.morph/templates/story.md +143 -143
  157. package/content/.morph/templates/tasks.md +257 -235
  158. package/content/.morph/templates/test.cs +239 -239
  159. package/content/.morph/templates/ui-components.md +362 -276
  160. package/content/.morph/templates/ui-design-system.md +286 -286
  161. package/content/.morph/templates/ui-flows.md +336 -336
  162. package/content/.morph/templates/ui-mockups.md +133 -133
  163. package/content/.morph/test-infra/example.bicep +59 -59
  164. package/content/CLAUDE.md +150 -442
  165. package/content/README.md +79 -79
  166. package/detectors/config-detector.js +223 -223
  167. package/detectors/conversation-analyzer.js +163 -163
  168. package/detectors/index.js +84 -84
  169. package/detectors/standards-generator.js +275 -275
  170. package/detectors/structure-detector.js +245 -250
  171. package/docs/README.md +144 -149
  172. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
  173. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
  174. package/docs/api/scripts/collapse.js +38 -38
  175. package/docs/api/scripts/commonNav.js +28 -28
  176. package/docs/api/scripts/linenumber.js +25 -25
  177. package/docs/api/scripts/nav.js +12 -12
  178. package/docs/api/scripts/polyfill.js +3 -3
  179. package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
  180. package/docs/api/scripts/prettify/lang-css.js +2 -2
  181. package/docs/api/scripts/prettify/prettify.js +28 -28
  182. package/docs/api/scripts/search.js +98 -98
  183. package/docs/api/styles/jsdoc.css +776 -776
  184. package/docs/api/styles/prettify.css +80 -80
  185. package/docs/examples.md +328 -328
  186. package/docs/getting-started.md +301 -302
  187. package/docs/installation.md +361 -361
  188. package/docs/templates.md +418 -418
  189. package/docs/validation-checklist.md +265 -266
  190. package/package.json +80 -80
  191. package/scripts/postinstall.js +132 -132
  192. package/src/commands/advance-phase.js +183 -0
  193. package/src/commands/analyze-blazor-concurrency.js +193 -0
  194. package/src/commands/create-story.js +351 -351
  195. package/src/commands/detect-agents.js +139 -0
  196. package/src/commands/detect.js +104 -104
  197. package/src/commands/doctor.js +356 -280
  198. package/src/commands/generate.js +149 -149
  199. package/src/commands/init.js +258 -245
  200. package/src/commands/lint-fluent.js +352 -0
  201. package/src/commands/rollback-phase.js +185 -0
  202. package/src/commands/session-summary.js +291 -0
  203. package/src/commands/shard-spec.js +224 -224
  204. package/src/commands/sprint-status.js +250 -250
  205. package/src/commands/state.js +333 -333
  206. package/src/commands/sync.js +167 -167
  207. package/src/commands/task.js +78 -0
  208. package/src/commands/troubleshoot.js +222 -0
  209. package/src/commands/update.js +192 -159
  210. package/src/commands/validate-blazor-state.js +210 -0
  211. package/src/commands/validate-blazor.js +156 -0
  212. package/src/commands/validate-css.js +84 -0
  213. package/src/commands/validate-phase.js +221 -0
  214. package/src/lib/blazor-concurrency-analyzer.js +288 -0
  215. package/src/lib/blazor-state-validator.js +291 -0
  216. package/src/lib/blazor-validator.js +374 -0
  217. package/src/lib/complexity-analyzer.js +441 -292
  218. package/src/lib/continuous-validator.js +421 -0
  219. package/src/lib/css-validator.js +352 -0
  220. package/src/lib/decision-constraint-loader.js +109 -0
  221. package/src/lib/design-system-generator.js +298 -298
  222. package/src/lib/learning-system.js +520 -0
  223. package/src/lib/mockup-generator.js +366 -0
  224. package/src/lib/recap-generator.js +205 -0
  225. package/src/lib/state-manager.js +397 -340
  226. package/src/lib/troubleshoot-grep.js +194 -0
  227. package/src/lib/troubleshoot-index.js +144 -0
  228. package/src/lib/ui-detector.js +350 -0
  229. package/src/lib/validation-runner.js +231 -0
  230. package/src/lib/validators/architecture-validator.js +387 -0
  231. package/src/lib/validators/contract-compliance-validator.js +273 -0
  232. package/src/lib/validators/package-validator.js +360 -0
  233. package/src/lib/validators/ui-contrast-validator.js +422 -0
  234. package/src/utils/file-copier.js +179 -139
  235. package/src/utils/logger.js +32 -32
  236. package/src/utils/version-checker.js +175 -175
  237. package/content/.claude/commands/morph-costs.md +0 -206
  238. package/content/.claude/commands/morph-tasks.md +0 -319
  239. package/content/.claude/skills/specialists/cost-guardian.md +0 -110
  240. package/content/.claude/skills/stacks/shopify.md +0 -445
  241. package/content/.morph/config/azure-pricing.json +0 -70
  242. package/content/.morph/config/azure-pricing.schema.json +0 -50
  243. package/content/.morph/hooks/pre-commit-costs.sh +0 -91
  244. package/docs/api/cost-calculator.js.html +0 -513
  245. package/docs/api/design-system-generator.js.html +0 -382
  246. package/docs/api/global.html +0 -5263
  247. package/docs/api/index.html +0 -96
  248. package/docs/api/state-manager.js.html +0 -423
  249. package/src/commands/cost.js +0 -181
  250. package/src/commands/update-pricing.js +0 -206
  251. package/src/lib/cost-calculator.js +0 -429
@@ -1,139 +1,139 @@
1
- // ============================================================
2
- // SERVICE TEMPLATE
3
- // Generated by MORPH Framework
4
- // ============================================================
5
-
6
- using Microsoft.Extensions.Logging;
7
-
8
- namespace MyProject.Application.Features.{Feature}.Services;
9
-
10
- /// <summary>
11
- /// Service for managing {Feature} operations.
12
- /// </summary>
13
- public class {Feature}Service(
14
- I{Feature}Repository repository,
15
- ILogger<{Feature}Service> logger) : I{Feature}Service
16
- {
17
- /// <inheritdoc />
18
- public async Task<{Feature}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
19
- {
20
- logger.LogDebug("Getting {Feature} with ID {Id}", id);
21
-
22
- var entity = await repository.GetByIdAsync(id, cancellationToken);
23
- if (entity is null)
24
- {
25
- logger.LogWarning("{Feature} with ID {Id} not found", id);
26
- return null;
27
- }
28
-
29
- return MapToDto(entity);
30
- }
31
-
32
- /// <inheritdoc />
33
- public async Task<List<{Feature}Dto>> GetAllAsync(CancellationToken cancellationToken = default)
34
- {
35
- logger.LogDebug("Getting all {Feature}s");
36
-
37
- var entities = await repository.GetAllAsync(cancellationToken);
38
- return entities.Select(MapToDto).ToList();
39
- }
40
-
41
- /// <inheritdoc />
42
- public async Task<{Feature}Dto> CreateAsync(
43
- Create{Feature}Request request,
44
- CancellationToken cancellationToken = default)
45
- {
46
- logger.LogInformation("Creating new {Feature}: {Name}", request.Name);
47
-
48
- // Validate
49
- await ValidateCreateAsync(request, cancellationToken);
50
-
51
- // Create entity
52
- var entity = Domain.Entities.{Feature}.Create(request.Name);
53
-
54
- // Save
55
- await repository.AddAsync(entity, cancellationToken);
56
- await repository.SaveChangesAsync(cancellationToken);
57
-
58
- logger.LogInformation("Created {Feature} with ID {Id}", entity.Id);
59
-
60
- return MapToDto(entity);
61
- }
62
-
63
- /// <inheritdoc />
64
- public async Task UpdateAsync(
65
- int id,
66
- Update{Feature}Request request,
67
- CancellationToken cancellationToken = default)
68
- {
69
- logger.LogInformation("Updating {Feature} {Id}", id);
70
-
71
- var entity = await repository.GetByIdAsync(id, cancellationToken)
72
- ?? throw new {Feature}NotFoundException(id);
73
-
74
- // Validate
75
- await ValidateUpdateAsync(entity, request, cancellationToken);
76
-
77
- // Update entity
78
- entity.UpdateName(request.Name);
79
-
80
- // Save
81
- repository.Update(entity);
82
- await repository.SaveChangesAsync(cancellationToken);
83
-
84
- logger.LogInformation("Updated {Feature} {Id}", id);
85
- }
86
-
87
- /// <inheritdoc />
88
- public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
89
- {
90
- logger.LogInformation("Deleting {Feature} {Id}", id);
91
-
92
- var entity = await repository.GetByIdAsync(id, cancellationToken)
93
- ?? throw new {Feature}NotFoundException(id);
94
-
95
- // Soft delete or remove
96
- repository.Remove(entity);
97
- await repository.SaveChangesAsync(cancellationToken);
98
-
99
- logger.LogInformation("Deleted {Feature} {Id}", id);
100
- }
101
-
102
- #region Private Methods
103
-
104
- private static {Feature}Dto MapToDto(Domain.Entities.{Feature} entity)
105
- {
106
- return new {Feature}Dto(
107
- entity.Id,
108
- entity.Name,
109
- entity.Status,
110
- entity.CreatedAt,
111
- entity.UpdatedAt
112
- );
113
- }
114
-
115
- private async Task ValidateCreateAsync(
116
- Create{Feature}Request request,
117
- CancellationToken cancellationToken)
118
- {
119
- // Add validation logic here
120
- // Example: Check for duplicates
121
- // var existing = await repository.FindByNameAsync(request.Name, cancellationToken);
122
- // if (existing is not null)
123
- // throw new ValidationException("Name already exists");
124
-
125
- await Task.CompletedTask;
126
- }
127
-
128
- private async Task ValidateUpdateAsync(
129
- Domain.Entities.{Feature} entity,
130
- Update{Feature}Request request,
131
- CancellationToken cancellationToken)
132
- {
133
- // Add validation logic here
134
-
135
- await Task.CompletedTask;
136
- }
137
-
138
- #endregion
139
- }
1
+ // ============================================================
2
+ // SERVICE TEMPLATE
3
+ // Generated by MORPH Framework
4
+ // ============================================================
5
+
6
+ using Microsoft.Extensions.Logging;
7
+
8
+ namespace MyProject.Application.Features.{Feature}.Services;
9
+
10
+ /// <summary>
11
+ /// Service for managing {Feature} operations.
12
+ /// </summary>
13
+ public class {Feature}Service(
14
+ I{Feature}Repository repository,
15
+ ILogger<{Feature}Service> logger) : I{Feature}Service
16
+ {
17
+ /// <inheritdoc />
18
+ public async Task<{Feature}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
19
+ {
20
+ logger.LogDebug("Getting {Feature} with ID {Id}", id);
21
+
22
+ var entity = await repository.GetByIdAsync(id, cancellationToken);
23
+ if (entity is null)
24
+ {
25
+ logger.LogWarning("{Feature} with ID {Id} not found", id);
26
+ return null;
27
+ }
28
+
29
+ return MapToDto(entity);
30
+ }
31
+
32
+ /// <inheritdoc />
33
+ public async Task<List<{Feature}Dto>> GetAllAsync(CancellationToken cancellationToken = default)
34
+ {
35
+ logger.LogDebug("Getting all {Feature}s");
36
+
37
+ var entities = await repository.GetAllAsync(cancellationToken);
38
+ return entities.Select(MapToDto).ToList();
39
+ }
40
+
41
+ /// <inheritdoc />
42
+ public async Task<{Feature}Dto> CreateAsync(
43
+ Create{Feature}Request request,
44
+ CancellationToken cancellationToken = default)
45
+ {
46
+ logger.LogInformation("Creating new {Feature}: {Name}", request.Name);
47
+
48
+ // Validate
49
+ await ValidateCreateAsync(request, cancellationToken);
50
+
51
+ // Create entity
52
+ var entity = Domain.Entities.{Feature}.Create(request.Name);
53
+
54
+ // Save
55
+ await repository.AddAsync(entity, cancellationToken);
56
+ await repository.SaveChangesAsync(cancellationToken);
57
+
58
+ logger.LogInformation("Created {Feature} with ID {Id}", entity.Id);
59
+
60
+ return MapToDto(entity);
61
+ }
62
+
63
+ /// <inheritdoc />
64
+ public async Task UpdateAsync(
65
+ int id,
66
+ Update{Feature}Request request,
67
+ CancellationToken cancellationToken = default)
68
+ {
69
+ logger.LogInformation("Updating {Feature} {Id}", id);
70
+
71
+ var entity = await repository.GetByIdAsync(id, cancellationToken)
72
+ ?? throw new {Feature}NotFoundException(id);
73
+
74
+ // Validate
75
+ await ValidateUpdateAsync(entity, request, cancellationToken);
76
+
77
+ // Update entity
78
+ entity.UpdateName(request.Name);
79
+
80
+ // Save
81
+ repository.Update(entity);
82
+ await repository.SaveChangesAsync(cancellationToken);
83
+
84
+ logger.LogInformation("Updated {Feature} {Id}", id);
85
+ }
86
+
87
+ /// <inheritdoc />
88
+ public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
89
+ {
90
+ logger.LogInformation("Deleting {Feature} {Id}", id);
91
+
92
+ var entity = await repository.GetByIdAsync(id, cancellationToken)
93
+ ?? throw new {Feature}NotFoundException(id);
94
+
95
+ // Soft delete or remove
96
+ repository.Remove(entity);
97
+ await repository.SaveChangesAsync(cancellationToken);
98
+
99
+ logger.LogInformation("Deleted {Feature} {Id}", id);
100
+ }
101
+
102
+ #region Private Methods
103
+
104
+ private static {Feature}Dto MapToDto(Domain.Entities.{Feature} entity)
105
+ {
106
+ return new {Feature}Dto(
107
+ entity.Id,
108
+ entity.Name,
109
+ entity.Status,
110
+ entity.CreatedAt,
111
+ entity.UpdatedAt
112
+ );
113
+ }
114
+
115
+ private async Task ValidateCreateAsync(
116
+ Create{Feature}Request request,
117
+ CancellationToken cancellationToken)
118
+ {
119
+ // Add validation logic here
120
+ // Example: Check for duplicates
121
+ // var existing = await repository.FindByNameAsync(request.Name, cancellationToken);
122
+ // if (existing is not null)
123
+ // throw new ValidationException("Name already exists");
124
+
125
+ await Task.CompletedTask;
126
+ }
127
+
128
+ private async Task ValidateUpdateAsync(
129
+ Domain.Entities.{Feature} entity,
130
+ Update{Feature}Request request,
131
+ CancellationToken cancellationToken)
132
+ {
133
+ // Add validation logic here
134
+
135
+ await Task.CompletedTask;
136
+ }
137
+
138
+ #endregion
139
+ }
@@ -0,0 +1,353 @@
1
+ # Template: Simulacao de Servicos Externos
2
+
3
+ > Template para configurar modo de simulacao em projetos .NET
4
+
5
+ ## Configuracao
6
+
7
+ ### appsettings.Development.json
8
+
9
+ ```json
10
+ {
11
+ "Simulation": {
12
+ "Enabled": true,
13
+ "ImageDelayMs": 500,
14
+ "EmailDelayMs": 100,
15
+ "PaymentDelayMs": 200,
16
+ "PlaceholderImageUrl": "https://picsum.photos/1024/1024"
17
+ }
18
+ }
19
+ ```
20
+
21
+ ### SimulationOptions.cs
22
+
23
+ ```csharp
24
+ namespace {Namespace}.Infrastructure.Options;
25
+
26
+ public class SimulationOptions
27
+ {
28
+ public bool Enabled { get; set; }
29
+ public int ImageDelayMs { get; set; } = 500;
30
+ public int EmailDelayMs { get; set; } = 100;
31
+ public int PaymentDelayMs { get; set; } = 200;
32
+ public string PlaceholderImageUrl { get; set; } = "https://picsum.photos/1024/1024";
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ## DI Condicional
39
+
40
+ ### Program.cs
41
+
42
+ ```csharp
43
+ // Configurar options
44
+ builder.Services.Configure<SimulationOptions>(
45
+ builder.Configuration.GetSection("Simulation"));
46
+
47
+ // Registrar clientes baseado em modo
48
+ var simulationEnabled = builder.Configuration.GetValue<bool>("Simulation:Enabled");
49
+
50
+ if (simulationEnabled)
51
+ {
52
+ builder.Services.AddSimulationClients();
53
+ builder.Services.AddSingleton<IStartupFilter, SimulationWarningStartupFilter>();
54
+ }
55
+ else
56
+ {
57
+ builder.Services.AddProductionClients(builder.Configuration);
58
+ }
59
+ ```
60
+
61
+ ### ServiceCollectionExtensions.cs
62
+
63
+ ```csharp
64
+ namespace {Namespace}.Infrastructure.Extensions;
65
+
66
+ public static class ServiceCollectionExtensions
67
+ {
68
+ public static IServiceCollection AddSimulationClients(this IServiceCollection services)
69
+ {
70
+ // ========================================
71
+ // STATEFUL (mantem dados) → SINGLETON
72
+ // ========================================
73
+ services.AddSingleton<IReplicateClient, FakeReplicateClient>();
74
+ services.AddSingleton<IPaymentClient, FakePaymentClient>();
75
+
76
+ // ========================================
77
+ // STATELESS → SCOPED
78
+ // ========================================
79
+ services.AddScoped<IEmailClient, FakeEmailClient>();
80
+
81
+ // ========================================
82
+ // DEPENDENCIAS TRANSITIVAS - NAO ESQUECER!
83
+ // ========================================
84
+ services.AddHttpClient("FakeImageDownloader");
85
+ services.AddScoped<IImageDownloader>(sp =>
86
+ {
87
+ var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
88
+ var httpClient = httpClientFactory.CreateClient("FakeImageDownloader");
89
+ var logger = sp.GetRequiredService<ILogger<FakeImageDownloader>>();
90
+ var options = sp.GetRequiredService<IOptions<SimulationOptions>>();
91
+ return new FakeImageDownloader(httpClient, logger, options);
92
+ });
93
+
94
+ return services;
95
+ }
96
+
97
+ public static IServiceCollection AddProductionClients(
98
+ this IServiceCollection services,
99
+ IConfiguration configuration)
100
+ {
101
+ // Clientes reais com HttpClient tipado
102
+ services.AddHttpClient<IReplicateClient, ReplicateClient>();
103
+ services.AddHttpClient<IPaymentClient, AsaasClient>();
104
+ services.AddHttpClient<IEmailClient, ResendClient>();
105
+ services.AddHttpClient<IImageDownloader, HttpImageDownloader>();
106
+
107
+ return services;
108
+ }
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Exemplos de Fake Clients
115
+
116
+ ### FakeReplicateClient (Stateful → Singleton)
117
+
118
+ ```csharp
119
+ public class FakeReplicateClient : IReplicateClient
120
+ {
121
+ // Stateful - REQUER Singleton
122
+ private readonly Dictionary<string, FakePrediction> _predictions = new();
123
+ private readonly ILogger<FakeReplicateClient> _logger;
124
+ private readonly SimulationOptions _options;
125
+
126
+ public FakeReplicateClient(
127
+ ILogger<FakeReplicateClient> logger,
128
+ IOptions<SimulationOptions> options)
129
+ {
130
+ _logger = logger;
131
+ _options = options.Value;
132
+ }
133
+
134
+ public Task<string> CreatePredictionAsync(CreatePredictionRequest request, CancellationToken ct)
135
+ {
136
+ var id = Guid.NewGuid().ToString();
137
+ _predictions[id] = new FakePrediction
138
+ {
139
+ Id = id,
140
+ Status = "processing",
141
+ Prompt = request.Input.Prompt,
142
+ CreatedAt = DateTime.UtcNow
143
+ };
144
+
145
+ _logger.LogInformation(
146
+ "[SIMULATED] Created prediction {Id} with prompt: {Prompt}",
147
+ id, request.Input.Prompt?.Substring(0, Math.Min(50, request.Input.Prompt.Length)));
148
+
149
+ return Task.FromResult(id);
150
+ }
151
+
152
+ public async Task<PredictionResult> WaitForPredictionAsync(string id, CancellationToken ct)
153
+ {
154
+ if (!_predictions.TryGetValue(id, out var prediction))
155
+ throw new InvalidOperationException($"Prediction {id} not found");
156
+
157
+ // Simular delay de processamento
158
+ await Task.Delay(_options.ImageDelayMs, ct);
159
+
160
+ var style = ExtractStyleFromPrompt(prediction.Prompt);
161
+
162
+ _logger.LogInformation(
163
+ "[SIMULATED] Prediction {Id} completed. Style: {Style}",
164
+ id, style);
165
+
166
+ return new PredictionResult
167
+ {
168
+ Id = id,
169
+ Status = "succeeded",
170
+ Output = _options.PlaceholderImageUrl
171
+ };
172
+ }
173
+
174
+ private static string ExtractStyleFromPrompt(string? prompt)
175
+ {
176
+ if (string.IsNullOrEmpty(prompt)) return "Default";
177
+
178
+ var lowerPrompt = prompt.ToLowerInvariant();
179
+
180
+ // IMPORTANTE: Atualizar conforme prompts REAIS do projeto
181
+ if (lowerPrompt.Contains("movie poster")) return "MoviePoster";
182
+ if (lowerPrompt.Contains("impressionist")) return "Impressionist";
183
+ if (lowerPrompt.Contains("baroque")) return "Baroque";
184
+ if (lowerPrompt.Contains("watercolor")) return "Watercolor";
185
+
186
+ return "Default";
187
+ }
188
+
189
+ private class FakePrediction
190
+ {
191
+ public string Id { get; init; } = "";
192
+ public string Status { get; set; } = "";
193
+ public string? Prompt { get; init; }
194
+ public DateTime CreatedAt { get; init; }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### FakeEmailClient (Stateless → Scoped)
200
+
201
+ ```csharp
202
+ public class FakeEmailClient : IEmailClient
203
+ {
204
+ private readonly ILogger<FakeEmailClient> _logger;
205
+ private readonly SimulationOptions _options;
206
+
207
+ public FakeEmailClient(
208
+ ILogger<FakeEmailClient> logger,
209
+ IOptions<SimulationOptions> options)
210
+ {
211
+ _logger = logger;
212
+ _options = options.Value;
213
+ }
214
+
215
+ public async Task<string?> SendEmailAsync(
216
+ string to,
217
+ string subject,
218
+ string htmlBody,
219
+ IReadOnlyList<EmailAttachment>? attachments = null,
220
+ CancellationToken ct = default)
221
+ {
222
+ await Task.Delay(_options.EmailDelayMs, ct);
223
+
224
+ var attachmentCount = attachments?.Count ?? 0;
225
+
226
+ _logger.LogInformation(
227
+ "[SIMULATED] Email sent to {To}. Subject: {Subject}. Attachments: {Count}",
228
+ to, subject, attachmentCount);
229
+
230
+ return $"fake-message-{Guid.NewGuid():N}";
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### FakeImageDownloader (Stateless → Scoped)
236
+
237
+ ```csharp
238
+ public class FakeImageDownloader : IImageDownloader
239
+ {
240
+ private readonly HttpClient _httpClient;
241
+ private readonly ILogger<FakeImageDownloader> _logger;
242
+ private readonly SimulationOptions _options;
243
+
244
+ public FakeImageDownloader(
245
+ HttpClient httpClient,
246
+ ILogger<FakeImageDownloader> logger,
247
+ IOptions<SimulationOptions> options)
248
+ {
249
+ _httpClient = httpClient;
250
+ _logger = logger;
251
+ _options = options.Value;
252
+ }
253
+
254
+ public async Task<byte[]> DownloadAsync(string url, CancellationToken ct)
255
+ {
256
+ _logger.LogInformation(
257
+ "[SIMULATED] Downloading image from placeholder instead of {Url}",
258
+ url);
259
+
260
+ // Baixa imagem real do placeholder
261
+ var response = await _httpClient.GetAsync(_options.PlaceholderImageUrl, ct);
262
+ response.EnsureSuccessStatusCode();
263
+
264
+ return await response.Content.ReadAsByteArrayAsync(ct);
265
+ }
266
+ }
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Startup Warning
272
+
273
+ ```csharp
274
+ public class SimulationWarningStartupFilter : IStartupFilter
275
+ {
276
+ private readonly ILogger<SimulationWarningStartupFilter> _logger;
277
+
278
+ public SimulationWarningStartupFilter(ILogger<SimulationWarningStartupFilter> logger)
279
+ {
280
+ _logger = logger;
281
+ }
282
+
283
+ public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
284
+ {
285
+ _logger.LogWarning(
286
+ "⚠️ SIMULATION MODE ENABLED - External services are being simulated!");
287
+
288
+ return next;
289
+ }
290
+ }
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Dependency Trace Template
296
+
297
+ Antes de implementar, preencher este trace:
298
+
299
+ ```markdown
300
+ ## Dependency Trace para Simulacao
301
+
302
+ ### Feature: {Nome da Feature}
303
+
304
+ ### Fluxo Principal:
305
+ 1. Controller/Page chama {ServiceA}
306
+ 2. {ServiceA} chama {IExternalClient}
307
+ 3. {IExternalClient} retorna dados
308
+ 4. {ServiceA} chama {IDependenciaTransitiva} ← NAO ESQUECER!
309
+
310
+ ### Servicos a Mockar:
311
+ | Interface | Fake | Lifetime | Motivo |
312
+ |-----------|------|----------|--------|
313
+ | IReplicateClient | FakeReplicateClient | Singleton | Stateful (Dictionary) |
314
+ | IEmailClient | FakeEmailClient | Scoped | Stateless |
315
+ | IImageDownloader | FakeImageDownloader | Scoped | Stateless |
316
+
317
+ ### Checklist:
318
+ - [ ] Todas as interfaces mapeadas
319
+ - [ ] Lifetimes definidos corretamente
320
+ - [ ] Dependencias transitivas identificadas
321
+ - [ ] Assinaturas de metodos verificadas
322
+ - [ ] Prompts/payloads atuais lidos (se IA)
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Checklist Rapido
328
+
329
+ ```markdown
330
+ ### Pre-Implementacao:
331
+ - [ ] Listar TODAS as interfaces envolvidas no fluxo
332
+ - [ ] Verificar dependencias transitivas (DI graph)
333
+ - [ ] Ler assinaturas COMPLETAS das interfaces
334
+ - [ ] Verificar lifetimes necessarios (Singleton vs Scoped)
335
+
336
+ ### Para mocks de IA:
337
+ - [ ] Ler prompts/payloads ATUAIS do codigo
338
+ - [ ] Verificar formato de response da API real
339
+ - [ ] Testar com dados reais primeiro (1 chamada) antes de mockar
340
+
341
+ ### Para mocks de Email:
342
+ - [ ] Verificar se ha attachments/inline images
343
+ - [ ] Testar renderizacao do template separadamente
344
+
345
+ ### Pos-Implementacao:
346
+ - [ ] Build passa: `dotnet build`
347
+ - [ ] DI valido: `dotnet run` sem erros
348
+ - [ ] Fluxo funciona em modo simulacao
349
+ ```
350
+
351
+ ---
352
+
353
+ *MORPH-SPEC Simulation Template*