@polymorphism-tech/morph-spec 1.0.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 (83) hide show
  1. package/README.md +279 -0
  2. package/bin/morph-spec.js +53 -0
  3. package/content/.claude/commands/morph-apply.md +66 -0
  4. package/content/.claude/commands/morph-archive.md +79 -0
  5. package/content/.claude/commands/morph-costs.md +206 -0
  6. package/content/.claude/commands/morph-infra.md +209 -0
  7. package/content/.claude/commands/morph-proposal.md +60 -0
  8. package/content/.claude/commands/morph-status.md +71 -0
  9. package/content/.claude/settings.local.json +15 -0
  10. package/content/.claude/skills/infra/bicep-architect.md +419 -0
  11. package/content/.claude/skills/infra/container-specialist.md +437 -0
  12. package/content/.claude/skills/infra/devops-engineer.md +405 -0
  13. package/content/.claude/skills/integrations/asaas-financial.md +333 -0
  14. package/content/.claude/skills/integrations/azure-identity.md +309 -0
  15. package/content/.claude/skills/integrations/clerk-auth.md +290 -0
  16. package/content/.claude/skills/specialists/azure-architect.md +142 -0
  17. package/content/.claude/skills/specialists/cost-guardian.md +110 -0
  18. package/content/.claude/skills/specialists/ef-modeler.md +200 -0
  19. package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
  20. package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
  21. package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
  22. package/content/.claude/skills/specialists/standards-architect.md +78 -0
  23. package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
  24. package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
  25. package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
  26. package/content/.claude/skills/stacks/shopify.md +445 -0
  27. package/content/.morph/archive/.gitkeep +25 -0
  28. package/content/.morph/config/agents.json +149 -0
  29. package/content/.morph/config/config.template.json +96 -0
  30. package/content/.morph/examples/api-nextjs/README.md +241 -0
  31. package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
  32. package/content/.morph/examples/api-nextjs/spec.md +399 -0
  33. package/content/.morph/examples/api-nextjs/tasks.md +168 -0
  34. package/content/.morph/examples/micro-saas/README.md +125 -0
  35. package/content/.morph/examples/micro-saas/contracts.cs +358 -0
  36. package/content/.morph/examples/micro-saas/decisions.md +246 -0
  37. package/content/.morph/examples/micro-saas/spec.md +236 -0
  38. package/content/.morph/examples/micro-saas/tasks.md +150 -0
  39. package/content/.morph/examples/multi-agent/README.md +309 -0
  40. package/content/.morph/examples/multi-agent/contracts.cs +433 -0
  41. package/content/.morph/examples/multi-agent/spec.md +479 -0
  42. package/content/.morph/examples/multi-agent/tasks.md +185 -0
  43. package/content/.morph/features/.gitkeep +25 -0
  44. package/content/.morph/project.md +159 -0
  45. package/content/.morph/specs/.gitkeep +20 -0
  46. package/content/.morph/standards/architecture.md +190 -0
  47. package/content/.morph/standards/azure.md +184 -0
  48. package/content/.morph/standards/coding.md +342 -0
  49. package/content/.morph/templates/agent.cs +172 -0
  50. package/content/.morph/templates/component.razor +239 -0
  51. package/content/.morph/templates/contracts.cs +217 -0
  52. package/content/.morph/templates/decisions.md +106 -0
  53. package/content/.morph/templates/infra/app-insights.bicep +63 -0
  54. package/content/.morph/templates/infra/container-app-env.bicep +49 -0
  55. package/content/.morph/templates/infra/container-app.bicep +156 -0
  56. package/content/.morph/templates/infra/key-vault.bicep +91 -0
  57. package/content/.morph/templates/infra/main.bicep +155 -0
  58. package/content/.morph/templates/infra/parameters.dev.json +23 -0
  59. package/content/.morph/templates/infra/parameters.prod.json +23 -0
  60. package/content/.morph/templates/infra/sql-database.bicep +103 -0
  61. package/content/.morph/templates/infra/storage.bicep +106 -0
  62. package/content/.morph/templates/integrations/asaas-client.cs +387 -0
  63. package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
  64. package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
  65. package/content/.morph/templates/integrations/clerk-config.cs +258 -0
  66. package/content/.morph/templates/job.cs +171 -0
  67. package/content/.morph/templates/migration.cs +83 -0
  68. package/content/.morph/templates/proposal.md +155 -0
  69. package/content/.morph/templates/recap.md +105 -0
  70. package/content/.morph/templates/repository.cs +141 -0
  71. package/content/.morph/templates/saas/subscription.cs +347 -0
  72. package/content/.morph/templates/saas/tenant.cs +338 -0
  73. package/content/.morph/templates/service.cs +139 -0
  74. package/content/.morph/templates/spec.md +147 -0
  75. package/content/.morph/templates/tasks.md +235 -0
  76. package/content/.morph/templates/test.cs +239 -0
  77. package/content/CLAUDE.md +318 -0
  78. package/package.json +50 -0
  79. package/src/commands/doctor.js +132 -0
  80. package/src/commands/init.js +121 -0
  81. package/src/commands/update.js +84 -0
  82. package/src/utils/file-copier.js +50 -0
  83. package/src/utils/logger.js +32 -0
@@ -0,0 +1,338 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - Multi-Tenant Model Template
3
+ // Modelo de multi-tenancy para SaaS
4
+ // ==============================================================================
5
+
6
+ using System.ComponentModel.DataAnnotations;
7
+ using Microsoft.EntityFrameworkCore;
8
+
9
+ namespace {{Namespace}}.Domain.Entities;
10
+
11
+ // ==============================================================================
12
+ // TENANT ENTITY
13
+ // ==============================================================================
14
+
15
+ public class Tenant : BaseEntity
16
+ {
17
+ [Required]
18
+ [MaxLength(100)]
19
+ public string Name { get; private set; } = string.Empty;
20
+
21
+ [Required]
22
+ [MaxLength(50)]
23
+ public string Slug { get; private set; } = string.Empty; // Unique identifier (URL-friendly)
24
+
25
+ [MaxLength(500)]
26
+ public string? Description { get; private set; }
27
+
28
+ [MaxLength(200)]
29
+ public string? LogoUrl { get; private set; }
30
+
31
+ public TenantStatus Status { get; private set; }
32
+
33
+ // Settings
34
+ public string? CustomDomain { get; private set; }
35
+ public string? TimeZone { get; private set; }
36
+ public string? Locale { get; private set; }
37
+
38
+ // Billing
39
+ public string? AsaasCustomerId { get; private set; }
40
+
41
+ // Navigation
42
+ public ICollection<TenantUser> Users { get; private set; } = new List<TenantUser>();
43
+ public Subscription? CurrentSubscription { get; private set; }
44
+
45
+ // =========================================================================
46
+ // FACTORY
47
+ // =========================================================================
48
+
49
+ public static Tenant Create(string name, string slug)
50
+ {
51
+ return new Tenant
52
+ {
53
+ Name = name,
54
+ Slug = slug.ToLowerInvariant(),
55
+ Status = TenantStatus.Active,
56
+ TimeZone = "E. South America Standard Time",
57
+ Locale = "pt-BR",
58
+ CreatedAt = DateTime.UtcNow
59
+ };
60
+ }
61
+
62
+ // =========================================================================
63
+ // BEHAVIORS
64
+ // =========================================================================
65
+
66
+ public void UpdateInfo(string name, string? description, string? logoUrl)
67
+ {
68
+ Name = name;
69
+ Description = description;
70
+ LogoUrl = logoUrl;
71
+ UpdatedAt = DateTime.UtcNow;
72
+ }
73
+
74
+ public void SetCustomDomain(string? domain)
75
+ {
76
+ CustomDomain = domain;
77
+ UpdatedAt = DateTime.UtcNow;
78
+ }
79
+
80
+ public void SetAsaasCustomerId(string customerId)
81
+ {
82
+ AsaasCustomerId = customerId;
83
+ UpdatedAt = DateTime.UtcNow;
84
+ }
85
+
86
+ public void Suspend()
87
+ {
88
+ Status = TenantStatus.Suspended;
89
+ UpdatedAt = DateTime.UtcNow;
90
+ }
91
+
92
+ public void Activate()
93
+ {
94
+ Status = TenantStatus.Active;
95
+ UpdatedAt = DateTime.UtcNow;
96
+ }
97
+
98
+ public void Delete()
99
+ {
100
+ Status = TenantStatus.Deleted;
101
+ UpdatedAt = DateTime.UtcNow;
102
+ }
103
+
104
+ // =========================================================================
105
+ // QUERIES
106
+ // =========================================================================
107
+
108
+ public bool IsActive => Status == TenantStatus.Active;
109
+ public bool IsSuspended => Status == TenantStatus.Suspended;
110
+ public bool IsDeleted => Status == TenantStatus.Deleted;
111
+ }
112
+
113
+ // ==============================================================================
114
+ // TENANT USER ENTITY (Association between Users and Tenants)
115
+ // ==============================================================================
116
+
117
+ public class TenantUser : BaseEntity
118
+ {
119
+ public int TenantId { get; private set; }
120
+ public Tenant Tenant { get; private set; } = null!;
121
+
122
+ [Required]
123
+ [MaxLength(100)]
124
+ public string ExternalUserId { get; private set; } = string.Empty; // Clerk/Azure AD user ID
125
+
126
+ [Required]
127
+ [MaxLength(200)]
128
+ public string Email { get; private set; } = string.Empty;
129
+
130
+ [MaxLength(100)]
131
+ public string? Name { get; private set; }
132
+
133
+ public TenantUserRole Role { get; private set; }
134
+ public TenantUserStatus Status { get; private set; }
135
+
136
+ public DateTime? LastLoginAt { get; private set; }
137
+
138
+ // =========================================================================
139
+ // FACTORY
140
+ // =========================================================================
141
+
142
+ public static TenantUser Create(
143
+ int tenantId,
144
+ string externalUserId,
145
+ string email,
146
+ string? name,
147
+ TenantUserRole role = TenantUserRole.Member)
148
+ {
149
+ return new TenantUser
150
+ {
151
+ TenantId = tenantId,
152
+ ExternalUserId = externalUserId,
153
+ Email = email,
154
+ Name = name,
155
+ Role = role,
156
+ Status = TenantUserStatus.Active,
157
+ CreatedAt = DateTime.UtcNow
158
+ };
159
+ }
160
+
161
+ // =========================================================================
162
+ // BEHAVIORS
163
+ // =========================================================================
164
+
165
+ public void UpdateRole(TenantUserRole role)
166
+ {
167
+ Role = role;
168
+ UpdatedAt = DateTime.UtcNow;
169
+ }
170
+
171
+ public void RecordLogin()
172
+ {
173
+ LastLoginAt = DateTime.UtcNow;
174
+ }
175
+
176
+ public void Deactivate()
177
+ {
178
+ Status = TenantUserStatus.Inactive;
179
+ UpdatedAt = DateTime.UtcNow;
180
+ }
181
+
182
+ public void Activate()
183
+ {
184
+ Status = TenantUserStatus.Active;
185
+ UpdatedAt = DateTime.UtcNow;
186
+ }
187
+
188
+ // =========================================================================
189
+ // QUERIES
190
+ // =========================================================================
191
+
192
+ public bool IsOwner => Role == TenantUserRole.Owner;
193
+ public bool IsAdmin => Role == TenantUserRole.Owner || Role == TenantUserRole.Admin;
194
+ public bool IsActive => Status == TenantUserStatus.Active;
195
+ }
196
+
197
+ // ==============================================================================
198
+ // ENUMS
199
+ // ==============================================================================
200
+
201
+ public enum TenantStatus
202
+ {
203
+ Active,
204
+ Suspended,
205
+ Deleted
206
+ }
207
+
208
+ public enum TenantUserRole
209
+ {
210
+ Owner,
211
+ Admin,
212
+ Member,
213
+ Viewer
214
+ }
215
+
216
+ public enum TenantUserStatus
217
+ {
218
+ Pending,
219
+ Active,
220
+ Inactive
221
+ }
222
+
223
+ // ==============================================================================
224
+ // TENANT CONTEXT (Current Tenant Resolution)
225
+ // ==============================================================================
226
+
227
+ public interface ITenantContext
228
+ {
229
+ int? TenantId { get; }
230
+ Tenant? CurrentTenant { get; }
231
+ Task<Tenant?> GetCurrentTenantAsync(CancellationToken ct = default);
232
+ }
233
+
234
+ public class TenantContext : ITenantContext
235
+ {
236
+ private readonly IHttpContextAccessor _httpContextAccessor;
237
+ private readonly ITenantRepository _tenantRepository;
238
+ private Tenant? _currentTenant;
239
+ private bool _loaded;
240
+
241
+ public TenantContext(
242
+ IHttpContextAccessor httpContextAccessor,
243
+ ITenantRepository tenantRepository)
244
+ {
245
+ _httpContextAccessor = httpContextAccessor;
246
+ _tenantRepository = tenantRepository;
247
+ }
248
+
249
+ public int? TenantId => _currentTenant?.Id;
250
+ public Tenant? CurrentTenant => _currentTenant;
251
+
252
+ public async Task<Tenant?> GetCurrentTenantAsync(CancellationToken ct = default)
253
+ {
254
+ if (_loaded) return _currentTenant;
255
+
256
+ var tenantSlug = ResolveTenantSlug();
257
+ if (string.IsNullOrEmpty(tenantSlug))
258
+ {
259
+ _loaded = true;
260
+ return null;
261
+ }
262
+
263
+ _currentTenant = await _tenantRepository.GetBySlugAsync(tenantSlug, ct);
264
+ _loaded = true;
265
+
266
+ return _currentTenant;
267
+ }
268
+
269
+ private string? ResolveTenantSlug()
270
+ {
271
+ var httpContext = _httpContextAccessor.HttpContext;
272
+ if (httpContext is null) return null;
273
+
274
+ // Strategy 1: From route (e.g., /tenant/{slug}/...)
275
+ if (httpContext.Request.RouteValues.TryGetValue("tenantSlug", out var routeSlug))
276
+ return routeSlug?.ToString();
277
+
278
+ // Strategy 2: From header (e.g., X-Tenant-ID)
279
+ if (httpContext.Request.Headers.TryGetValue("X-Tenant-ID", out var headerSlug))
280
+ return headerSlug.FirstOrDefault();
281
+
282
+ // Strategy 3: From subdomain (e.g., acme.app.com)
283
+ var host = httpContext.Request.Host.Host;
284
+ var parts = host.Split('.');
285
+ if (parts.Length >= 3)
286
+ return parts[0];
287
+
288
+ // Strategy 4: From claim
289
+ var claimSlug = httpContext.User.FindFirst("tenant_id")?.Value;
290
+ if (!string.IsNullOrEmpty(claimSlug))
291
+ return claimSlug;
292
+
293
+ return null;
294
+ }
295
+ }
296
+
297
+ // ==============================================================================
298
+ // TENANT REPOSITORY INTERFACE
299
+ // ==============================================================================
300
+
301
+ public interface ITenantRepository
302
+ {
303
+ Task<Tenant?> GetByIdAsync(int id, CancellationToken ct = default);
304
+ Task<Tenant?> GetBySlugAsync(string slug, CancellationToken ct = default);
305
+ Task<bool> SlugExistsAsync(string slug, CancellationToken ct = default);
306
+ Task AddAsync(Tenant tenant, CancellationToken ct = default);
307
+ void Update(Tenant tenant);
308
+ }
309
+
310
+ // ==============================================================================
311
+ // TENANT QUERY FILTER (EF Core)
312
+ // ==============================================================================
313
+
314
+ /*
315
+ // In DbContext:
316
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
317
+ {
318
+ // Apply tenant filter to all tenant-scoped entities
319
+ modelBuilder.Entity<Order>().HasQueryFilter(o => o.TenantId == _tenantContext.TenantId);
320
+ modelBuilder.Entity<Customer>().HasQueryFilter(c => c.TenantId == _tenantContext.TenantId);
321
+ // ... other entities
322
+ }
323
+ */
324
+
325
+ // ==============================================================================
326
+ // DEPENDENCY INJECTION
327
+ // ==============================================================================
328
+
329
+ public static class TenantServiceExtensions
330
+ {
331
+ public static IServiceCollection AddMultiTenancy(this IServiceCollection services)
332
+ {
333
+ services.AddScoped<ITenantContext, TenantContext>();
334
+ services.AddScoped<ITenantRepository, TenantRepository>();
335
+
336
+ return services;
337
+ }
338
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,147 @@
1
+ # Feature Specification: {FEATURE_NAME}
2
+
3
+ ## πŸ“‹ Metadata
4
+
5
+ | Field | Value |
6
+ |-------|-------|
7
+ | **ID** | {feature-id} |
8
+ | **Status** | Draft / In Review / Approved / In Progress / Done |
9
+ | **Created** | {date} |
10
+ | **Agents** | Core: All / Specialists: {list} |
11
+ | **Estimated Cost** | ${X}/month |
12
+
13
+ ---
14
+
15
+ ## 🎯 Overview
16
+
17
+ ### Problem Statement
18
+ {Descreva o problema que esta feature resolve}
19
+
20
+ ### Proposed Solution
21
+ {Descreva a soluΓ§Γ£o em alto nΓ­vel}
22
+
23
+ ### Success Criteria
24
+ - [ ] {CritΓ©rio 1}
25
+ - [ ] {CritΓ©rio 2}
26
+ - [ ] {CritΓ©rio 3}
27
+
28
+ ---
29
+
30
+ ## πŸ‘€ User Stories
31
+
32
+ ### US001: {TΓ­tulo}
33
+
34
+ **Como** {tipo de usuΓ‘rio}
35
+ **Quero** {funcionalidade}
36
+ **Para** {benefΓ­cio}
37
+
38
+ **Acceptance Criteria:**
39
+ 1. {CritΓ©rio 1}
40
+ 2. {CritΓ©rio 2}
41
+
42
+ **Edge Cases:**
43
+ - {Edge case 1}: {Como tratar}
44
+
45
+ ---
46
+
47
+ ## πŸ—οΈ Technical Design
48
+
49
+ ### Stack
50
+ | Component | Technology |
51
+ |-----------|------------|
52
+ | Frontend | Blazor Server |
53
+ | Backend | .NET 9 |
54
+ | Database | Azure SQL |
55
+ | AI | MS Agent Framework |
56
+
57
+ ### Architecture
58
+ ```
59
+ User β†’ Blazor UI β†’ Service β†’ Repository β†’ Database
60
+ ↓
61
+ AI Agent β†’ Azure OpenAI
62
+ ```
63
+
64
+ ### Data Model
65
+
66
+ #### {EntityName}
67
+ | Column | Type | Constraints |
68
+ |--------|------|-------------|
69
+ | Id | int | PK, Identity |
70
+ | Name | nvarchar(200) | Required |
71
+ | CreatedAt | datetime2 | Default: GETUTCDATE() |
72
+
73
+ ### Contracts
74
+ ```csharp
75
+ public interface I{Feature}Service
76
+ {
77
+ Task<{Feature}Dto> GetByIdAsync(int id);
78
+ Task<{Feature}Dto> CreateAsync(Create{Feature}Request request);
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 🎨 UI/UX Design
85
+
86
+ ### Wireframes
87
+ ```
88
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
89
+ β”‚ {Page Title} [+] β”‚
90
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
91
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
92
+ β”‚ β”‚ {Item} [Edit] β”‚ β”‚
93
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
94
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
95
+ ```
96
+
97
+ ### User Flow
98
+ 1. UsuΓ‘rio acessa pΓ‘gina
99
+ 2. Sistema carrega lista
100
+ 3. UsuΓ‘rio clica em [+]
101
+ 4. Modal de criaΓ§Γ£o abre
102
+ 5. UsuΓ‘rio salva
103
+
104
+ ---
105
+
106
+ ## πŸ’° Cost Analysis
107
+
108
+ | Resource | Tier | Monthly Cost |
109
+ |----------|------|--------------|
110
+ | Azure SQL | Free | $0 |
111
+ | Container Apps | Consumption | ~$2 |
112
+ | Azure OpenAI | gpt-4o-mini | ~$2 |
113
+ | **Total** | | **~$4** |
114
+
115
+ ---
116
+
117
+ ## πŸ” Security
118
+
119
+ | Action | Required Policy |
120
+ |--------|-----------------|
121
+ | View | CanView{Feature} |
122
+ | Create | CanManage{Feature} |
123
+ | Delete | CanManage{Feature} |
124
+
125
+ ---
126
+
127
+ ## ⚠️ Risks
128
+
129
+ | Risk | Impact | Mitigation |
130
+ |------|--------|------------|
131
+ | {Risk 1} | High | {Mitigation} |
132
+
133
+ ---
134
+
135
+ ## 🚫 Out of Scope
136
+
137
+ - {Item fora do escopo}
138
+
139
+ ---
140
+
141
+ ## βœ… Definition of Done
142
+
143
+ - [ ] CΓ³digo implementado
144
+ - [ ] Testes >80% coverage
145
+ - [ ] Code review aprovado
146
+ - [ ] Deploy em staging
147
+ - [ ] Recap.md gerado