@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.
- package/CLAUDE.md +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- 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*
|