@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.
- package/README.md +279 -0
- package/bin/morph-spec.js +53 -0
- package/content/.claude/commands/morph-apply.md +66 -0
- package/content/.claude/commands/morph-archive.md +79 -0
- package/content/.claude/commands/morph-costs.md +206 -0
- package/content/.claude/commands/morph-infra.md +209 -0
- package/content/.claude/commands/morph-proposal.md +60 -0
- package/content/.claude/commands/morph-status.md +71 -0
- package/content/.claude/settings.local.json +15 -0
- package/content/.claude/skills/infra/bicep-architect.md +419 -0
- package/content/.claude/skills/infra/container-specialist.md +437 -0
- package/content/.claude/skills/infra/devops-engineer.md +405 -0
- package/content/.claude/skills/integrations/asaas-financial.md +333 -0
- package/content/.claude/skills/integrations/azure-identity.md +309 -0
- package/content/.claude/skills/integrations/clerk-auth.md +290 -0
- package/content/.claude/skills/specialists/azure-architect.md +142 -0
- package/content/.claude/skills/specialists/cost-guardian.md +110 -0
- package/content/.claude/skills/specialists/ef-modeler.md +200 -0
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
- package/content/.claude/skills/specialists/standards-architect.md +78 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
- package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
- package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
- package/content/.claude/skills/stacks/shopify.md +445 -0
- package/content/.morph/archive/.gitkeep +25 -0
- package/content/.morph/config/agents.json +149 -0
- package/content/.morph/config/config.template.json +96 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -0
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
- package/content/.morph/examples/api-nextjs/spec.md +399 -0
- package/content/.morph/examples/api-nextjs/tasks.md +168 -0
- package/content/.morph/examples/micro-saas/README.md +125 -0
- package/content/.morph/examples/micro-saas/contracts.cs +358 -0
- package/content/.morph/examples/micro-saas/decisions.md +246 -0
- package/content/.morph/examples/micro-saas/spec.md +236 -0
- package/content/.morph/examples/micro-saas/tasks.md +150 -0
- package/content/.morph/examples/multi-agent/README.md +309 -0
- package/content/.morph/examples/multi-agent/contracts.cs +433 -0
- package/content/.morph/examples/multi-agent/spec.md +479 -0
- package/content/.morph/examples/multi-agent/tasks.md +185 -0
- package/content/.morph/features/.gitkeep +25 -0
- package/content/.morph/project.md +159 -0
- package/content/.morph/specs/.gitkeep +20 -0
- package/content/.morph/standards/architecture.md +190 -0
- package/content/.morph/standards/azure.md +184 -0
- package/content/.morph/standards/coding.md +342 -0
- package/content/.morph/templates/agent.cs +172 -0
- package/content/.morph/templates/component.razor +239 -0
- package/content/.morph/templates/contracts.cs +217 -0
- package/content/.morph/templates/decisions.md +106 -0
- package/content/.morph/templates/infra/app-insights.bicep +63 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -0
- package/content/.morph/templates/infra/container-app.bicep +156 -0
- package/content/.morph/templates/infra/key-vault.bicep +91 -0
- package/content/.morph/templates/infra/main.bicep +155 -0
- package/content/.morph/templates/infra/parameters.dev.json +23 -0
- package/content/.morph/templates/infra/parameters.prod.json +23 -0
- package/content/.morph/templates/infra/sql-database.bicep +103 -0
- package/content/.morph/templates/infra/storage.bicep +106 -0
- package/content/.morph/templates/integrations/asaas-client.cs +387 -0
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/content/.morph/templates/integrations/clerk-config.cs +258 -0
- package/content/.morph/templates/job.cs +171 -0
- package/content/.morph/templates/migration.cs +83 -0
- package/content/.morph/templates/proposal.md +155 -0
- package/content/.morph/templates/recap.md +105 -0
- package/content/.morph/templates/repository.cs +141 -0
- package/content/.morph/templates/saas/subscription.cs +347 -0
- package/content/.morph/templates/saas/tenant.cs +338 -0
- package/content/.morph/templates/service.cs +139 -0
- package/content/.morph/templates/spec.md +147 -0
- package/content/.morph/templates/tasks.md +235 -0
- package/content/.morph/templates/test.cs +239 -0
- package/content/CLAUDE.md +318 -0
- package/package.json +50 -0
- package/src/commands/doctor.js +132 -0
- package/src/commands/init.js +121 -0
- package/src/commands/update.js +84 -0
- package/src/utils/file-copier.js +50 -0
- package/src/utils/logger.js +32 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# EF Modeler
|
|
2
|
+
|
|
3
|
+
Especialista em Entity Framework Core para modelagem de dados e banco de dados.
|
|
4
|
+
|
|
5
|
+
## Responsabilidades
|
|
6
|
+
|
|
7
|
+
1. **Modelar entidades** e relacionamentos
|
|
8
|
+
2. **Criar migrations** seguras e reversíveis
|
|
9
|
+
3. **Otimizar queries** para performance
|
|
10
|
+
4. **Configurar DbContext** corretamente
|
|
11
|
+
|
|
12
|
+
## Triggers
|
|
13
|
+
|
|
14
|
+
Keywords: `entity`, `database`, `migration`, `ef core`, `dbcontext`, `table`, `column`, `relationship`, `query`
|
|
15
|
+
|
|
16
|
+
## Estrutura de Entidades
|
|
17
|
+
|
|
18
|
+
```csharp
|
|
19
|
+
// Domain/Entities/Order.cs
|
|
20
|
+
public class Order : BaseEntity
|
|
21
|
+
{
|
|
22
|
+
public string OrderNumber { get; private set; } = null!;
|
|
23
|
+
public OrderStatus Status { get; private set; }
|
|
24
|
+
public decimal Total { get; private set; }
|
|
25
|
+
public DateTime CreatedAt { get; private set; }
|
|
26
|
+
|
|
27
|
+
// Navigation properties
|
|
28
|
+
public int CustomerId { get; private set; }
|
|
29
|
+
public Customer Customer { get; private set; } = null!;
|
|
30
|
+
|
|
31
|
+
public ICollection<OrderItem> Items { get; private set; } = new List<OrderItem>();
|
|
32
|
+
|
|
33
|
+
// Factory method
|
|
34
|
+
public static Order Create(int customerId, IEnumerable<OrderItem> items)
|
|
35
|
+
{
|
|
36
|
+
var order = new Order
|
|
37
|
+
{
|
|
38
|
+
OrderNumber = GenerateOrderNumber(),
|
|
39
|
+
Status = OrderStatus.Pending,
|
|
40
|
+
CustomerId = customerId,
|
|
41
|
+
CreatedAt = DateTime.UtcNow
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
foreach (var item in items)
|
|
45
|
+
order.Items.Add(item);
|
|
46
|
+
|
|
47
|
+
order.CalculateTotal();
|
|
48
|
+
return order;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuração do DbContext
|
|
54
|
+
|
|
55
|
+
```csharp
|
|
56
|
+
// Infrastructure/Data/AppDbContext.cs
|
|
57
|
+
public class AppDbContext : DbContext
|
|
58
|
+
{
|
|
59
|
+
public AppDbContext(DbContextOptions<AppDbContext> options)
|
|
60
|
+
: base(options) { }
|
|
61
|
+
|
|
62
|
+
public DbSet<Order> Orders => Set<Order>();
|
|
63
|
+
public DbSet<Customer> Customers => Set<Customer>();
|
|
64
|
+
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
|
|
65
|
+
|
|
66
|
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
67
|
+
{
|
|
68
|
+
modelBuilder.ApplyConfigurationsFromAssembly(
|
|
69
|
+
typeof(AppDbContext).Assembly);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Infrastructure/Data/Configurations/OrderConfiguration.cs
|
|
74
|
+
public class OrderConfiguration : IEntityTypeConfiguration<Order>
|
|
75
|
+
{
|
|
76
|
+
public void Configure(EntityTypeBuilder<Order> builder)
|
|
77
|
+
{
|
|
78
|
+
builder.ToTable("Orders");
|
|
79
|
+
|
|
80
|
+
builder.HasKey(o => o.Id);
|
|
81
|
+
|
|
82
|
+
builder.Property(o => o.OrderNumber)
|
|
83
|
+
.IsRequired()
|
|
84
|
+
.HasMaxLength(20);
|
|
85
|
+
|
|
86
|
+
builder.Property(o => o.Total)
|
|
87
|
+
.HasPrecision(18, 2);
|
|
88
|
+
|
|
89
|
+
builder.HasIndex(o => o.OrderNumber)
|
|
90
|
+
.IsUnique();
|
|
91
|
+
|
|
92
|
+
builder.HasOne(o => o.Customer)
|
|
93
|
+
.WithMany(c => c.Orders)
|
|
94
|
+
.HasForeignKey(o => o.CustomerId)
|
|
95
|
+
.OnDelete(DeleteBehavior.Restrict);
|
|
96
|
+
|
|
97
|
+
builder.HasMany(o => o.Items)
|
|
98
|
+
.WithOne(i => i.Order)
|
|
99
|
+
.HasForeignKey(i => i.OrderId)
|
|
100
|
+
.OnDelete(DeleteBehavior.Cascade);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Migrations Seguras
|
|
106
|
+
|
|
107
|
+
```powershell
|
|
108
|
+
# Criar migration
|
|
109
|
+
dotnet ef migrations add {MigrationName} --project src/Infrastructure --startup-project src/Web
|
|
110
|
+
|
|
111
|
+
# Script SQL para revisão
|
|
112
|
+
dotnet ef migrations script --idempotent --output migration.sql
|
|
113
|
+
|
|
114
|
+
# Aplicar migration
|
|
115
|
+
dotnet ef database update
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Convenções de Naming
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
{YYYYMMDD}_{Numero}_{Descricao}
|
|
122
|
+
|
|
123
|
+
Exemplos:
|
|
124
|
+
- 20240301_001_CreateOrdersTable
|
|
125
|
+
- 20240301_002_AddCustomerIdToOrders
|
|
126
|
+
- 20240302_001_CreateIndexOnOrderNumber
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Queries Otimizadas
|
|
130
|
+
|
|
131
|
+
```csharp
|
|
132
|
+
// RUIM: N+1 problem
|
|
133
|
+
var orders = await _context.Orders.ToListAsync();
|
|
134
|
+
foreach (var order in orders)
|
|
135
|
+
{
|
|
136
|
+
var items = order.Items; // N queries adicionais!
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// BOM: Include explicito
|
|
140
|
+
var orders = await _context.Orders
|
|
141
|
+
.Include(o => o.Items)
|
|
142
|
+
.Include(o => o.Customer)
|
|
143
|
+
.ToListAsync();
|
|
144
|
+
|
|
145
|
+
// MELHOR: Projection
|
|
146
|
+
var orderDtos = await _context.Orders
|
|
147
|
+
.Select(o => new OrderDto
|
|
148
|
+
{
|
|
149
|
+
Id = o.Id,
|
|
150
|
+
OrderNumber = o.OrderNumber,
|
|
151
|
+
CustomerName = o.Customer.Name,
|
|
152
|
+
ItemCount = o.Items.Count,
|
|
153
|
+
Total = o.Total
|
|
154
|
+
})
|
|
155
|
+
.ToListAsync();
|
|
156
|
+
|
|
157
|
+
// Paginação
|
|
158
|
+
var pagedOrders = await _context.Orders
|
|
159
|
+
.OrderByDescending(o => o.CreatedAt)
|
|
160
|
+
.Skip((page - 1) * pageSize)
|
|
161
|
+
.Take(pageSize)
|
|
162
|
+
.ToListAsync();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Padrões de Repository (Opcional)
|
|
166
|
+
|
|
167
|
+
```csharp
|
|
168
|
+
// Se usar repository pattern
|
|
169
|
+
public interface IOrderRepository
|
|
170
|
+
{
|
|
171
|
+
Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
|
|
172
|
+
Task<Order?> GetByOrderNumberAsync(string orderNumber, CancellationToken ct = default);
|
|
173
|
+
Task<IReadOnlyList<Order>> GetByCustomerIdAsync(int customerId, CancellationToken ct = default);
|
|
174
|
+
Task AddAsync(Order order, CancellationToken ct = default);
|
|
175
|
+
void Update(Order order);
|
|
176
|
+
void Remove(Order order);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Documentação de Referência
|
|
181
|
+
|
|
182
|
+
- [EF Core Docs](https://learn.microsoft.com/en-us/ef/core/)
|
|
183
|
+
- [Migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/)
|
|
184
|
+
- [Performance](https://learn.microsoft.com/en-us/ef/core/performance/)
|
|
185
|
+
- [Relationships](https://learn.microsoft.com/en-us/ef/core/modeling/relationships/)
|
|
186
|
+
|
|
187
|
+
## Checklist de Modelagem
|
|
188
|
+
|
|
189
|
+
- [ ] Entidades com private setters
|
|
190
|
+
- [ ] Factory methods para criação
|
|
191
|
+
- [ ] Configuração separada (IEntityTypeConfiguration)
|
|
192
|
+
- [ ] Índices em colunas de busca
|
|
193
|
+
- [ ] Precision definida para decimais
|
|
194
|
+
- [ ] Delete behavior explícito
|
|
195
|
+
- [ ] Migration script gerado e revisado
|
|
196
|
+
- [ ] Queries com Include ou Projection
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Hangfire Orchestrator
|
|
2
|
+
|
|
3
|
+
Especialista em background jobs, tarefas agendadas e processamento assíncrono com Hangfire.
|
|
4
|
+
|
|
5
|
+
## Responsabilidades
|
|
6
|
+
|
|
7
|
+
1. **Criar jobs** de background para processamento assíncrono
|
|
8
|
+
2. **Configurar schedules** com expressões cron
|
|
9
|
+
3. **Gerenciar filas** e prioridades
|
|
10
|
+
4. **Monitorar** execução via dashboard
|
|
11
|
+
|
|
12
|
+
## Triggers
|
|
13
|
+
|
|
14
|
+
Keywords: `scheduled`, `job`, `background`, `cron`, `recurring`, `batch`, `queue`, `hangfire`, `task`
|
|
15
|
+
|
|
16
|
+
## Por que Hangfire (não Azure Functions)
|
|
17
|
+
|
|
18
|
+
| Aspecto | Hangfire | Azure Functions |
|
|
19
|
+
|---------|----------|-----------------|
|
|
20
|
+
| Custo | Free (in-process) | Pay per execution |
|
|
21
|
+
| Dashboard | Built-in | App Insights separado |
|
|
22
|
+
| Debugging | Local fácil | Emulator necessário |
|
|
23
|
+
| Estado | Persistido SQL | Storage Account |
|
|
24
|
+
| Complexidade | Baixa | Média-Alta |
|
|
25
|
+
|
|
26
|
+
## Configuração Básica
|
|
27
|
+
|
|
28
|
+
```csharp
|
|
29
|
+
// Program.cs
|
|
30
|
+
builder.Services.AddHangfire(config => config
|
|
31
|
+
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
|
32
|
+
.UseSimpleAssemblyNameTypeSerializer()
|
|
33
|
+
.UseRecommendedSerializerSettings()
|
|
34
|
+
.UseSqlServerStorage(
|
|
35
|
+
builder.Configuration.GetConnectionString("HangfireConnection"),
|
|
36
|
+
new SqlServerStorageOptions
|
|
37
|
+
{
|
|
38
|
+
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
|
39
|
+
QueuePollInterval = TimeSpan.Zero,
|
|
40
|
+
UseRecommendedIsolationLevel = true,
|
|
41
|
+
DisableGlobalLocks = true
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
builder.Services.AddHangfireServer(options =>
|
|
45
|
+
{
|
|
46
|
+
options.WorkerCount = Environment.ProcessorCount * 2;
|
|
47
|
+
options.Queues = new[] { "critical", "default", "low" };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Middleware
|
|
51
|
+
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
|
52
|
+
{
|
|
53
|
+
Authorization = new[] { new HangfireAuthorizationFilter() }
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tipos de Jobs
|
|
58
|
+
|
|
59
|
+
### Fire-and-Forget (Uma vez)
|
|
60
|
+
|
|
61
|
+
```csharp
|
|
62
|
+
// Executa imediatamente em background
|
|
63
|
+
BackgroundJob.Enqueue(() => _emailService.SendWelcomeEmailAsync(userId));
|
|
64
|
+
|
|
65
|
+
// Com injeção de dependência
|
|
66
|
+
BackgroundJob.Enqueue<IEmailService>(service =>
|
|
67
|
+
service.SendWelcomeEmailAsync(userId));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Delayed (Atrasado)
|
|
71
|
+
|
|
72
|
+
```csharp
|
|
73
|
+
// Executa após delay
|
|
74
|
+
BackgroundJob.Schedule(() =>
|
|
75
|
+
_orderService.CheckAbandonedCartAsync(cartId),
|
|
76
|
+
TimeSpan.FromHours(24));
|
|
77
|
+
|
|
78
|
+
// Em data específica
|
|
79
|
+
BackgroundJob.Schedule(() =>
|
|
80
|
+
_reminderService.SendReminderAsync(appointmentId),
|
|
81
|
+
appointmentDate.AddHours(-1));
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Recurring (Recorrente)
|
|
85
|
+
|
|
86
|
+
```csharp
|
|
87
|
+
// Cron expression
|
|
88
|
+
RecurringJob.AddOrUpdate<IReportService>(
|
|
89
|
+
"daily-sales-report",
|
|
90
|
+
service => service.GenerateDailySalesReportAsync(),
|
|
91
|
+
"0 8 * * *", // Todo dia às 8h
|
|
92
|
+
new RecurringJobOptions
|
|
93
|
+
{
|
|
94
|
+
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time")
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Expressões comuns
|
|
98
|
+
// "0 * * * *" - A cada hora
|
|
99
|
+
// "*/5 * * * *" - A cada 5 minutos
|
|
100
|
+
// "0 8 * * 1-5" - 8h em dias úteis
|
|
101
|
+
// "0 0 1 * *" - Primeiro dia do mês
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Continuations (Encadeamento)
|
|
105
|
+
|
|
106
|
+
```csharp
|
|
107
|
+
var jobId = BackgroundJob.Enqueue(() => _importService.ImportDataAsync(fileId));
|
|
108
|
+
|
|
109
|
+
BackgroundJob.ContinueJobWith(jobId, () =>
|
|
110
|
+
_notificationService.NotifyImportCompleteAsync(fileId));
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Batch (Lote)
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
// Requer Hangfire.Pro ou implementação custom
|
|
117
|
+
var batchId = BatchJob.StartNew(batch =>
|
|
118
|
+
{
|
|
119
|
+
foreach (var orderId in orderIds)
|
|
120
|
+
{
|
|
121
|
+
batch.Enqueue(() => _orderService.ProcessOrderAsync(orderId));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
BatchJob.ContinueBatchWith(batchId, batch =>
|
|
126
|
+
{
|
|
127
|
+
batch.Enqueue(() => _reportService.GenerateBatchReportAsync(batchId));
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Estrutura de Job Service
|
|
132
|
+
|
|
133
|
+
```csharp
|
|
134
|
+
// Jobs/DailySalesReportJob.cs
|
|
135
|
+
public interface IDailySalesReportJob
|
|
136
|
+
{
|
|
137
|
+
Task ExecuteAsync(CancellationToken ct = default);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public class DailySalesReportJob : IDailySalesReportJob
|
|
141
|
+
{
|
|
142
|
+
private readonly IOrderRepository _orderRepository;
|
|
143
|
+
private readonly IReportGenerator _reportGenerator;
|
|
144
|
+
private readonly IEmailService _emailService;
|
|
145
|
+
private readonly ILogger<DailySalesReportJob> _logger;
|
|
146
|
+
|
|
147
|
+
public DailySalesReportJob(
|
|
148
|
+
IOrderRepository orderRepository,
|
|
149
|
+
IReportGenerator reportGenerator,
|
|
150
|
+
IEmailService emailService,
|
|
151
|
+
ILogger<DailySalesReportJob> logger)
|
|
152
|
+
{
|
|
153
|
+
_orderRepository = orderRepository;
|
|
154
|
+
_reportGenerator = reportGenerator;
|
|
155
|
+
_emailService = emailService;
|
|
156
|
+
_logger = logger;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
[AutomaticRetry(Attempts = 3)]
|
|
160
|
+
[Queue("default")]
|
|
161
|
+
public async Task ExecuteAsync(CancellationToken ct = default)
|
|
162
|
+
{
|
|
163
|
+
_logger.LogInformation("Starting daily sales report generation");
|
|
164
|
+
|
|
165
|
+
var yesterday = DateTime.Today.AddDays(-1);
|
|
166
|
+
var orders = await _orderRepository.GetByDateRangeAsync(
|
|
167
|
+
yesterday, yesterday.AddDays(1), ct);
|
|
168
|
+
|
|
169
|
+
var report = await _reportGenerator.GenerateAsync(orders, ct);
|
|
170
|
+
|
|
171
|
+
await _emailService.SendReportAsync(report, ct);
|
|
172
|
+
|
|
173
|
+
_logger.LogInformation("Daily sales report completed. Orders: {Count}", orders.Count);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Registro
|
|
178
|
+
RecurringJob.AddOrUpdate<IDailySalesReportJob>(
|
|
179
|
+
"daily-sales-report",
|
|
180
|
+
job => job.ExecuteAsync(default),
|
|
181
|
+
"0 8 * * *");
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Retry e Error Handling
|
|
185
|
+
|
|
186
|
+
```csharp
|
|
187
|
+
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 })]
|
|
188
|
+
[Queue("critical")]
|
|
189
|
+
public async Task ProcessPaymentAsync(int orderId)
|
|
190
|
+
{
|
|
191
|
+
try
|
|
192
|
+
{
|
|
193
|
+
await _paymentService.ProcessAsync(orderId);
|
|
194
|
+
}
|
|
195
|
+
catch (PaymentGatewayException ex)
|
|
196
|
+
{
|
|
197
|
+
_logger.LogWarning(ex, "Payment failed for order {OrderId}, will retry", orderId);
|
|
198
|
+
throw; // Rethrow para Hangfire retry
|
|
199
|
+
}
|
|
200
|
+
catch (Exception ex)
|
|
201
|
+
{
|
|
202
|
+
_logger.LogError(ex, "Unexpected error processing order {OrderId}", orderId);
|
|
203
|
+
throw;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Dashboard Authorization
|
|
209
|
+
|
|
210
|
+
```csharp
|
|
211
|
+
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
|
|
212
|
+
{
|
|
213
|
+
public bool Authorize(DashboardContext context)
|
|
214
|
+
{
|
|
215
|
+
var httpContext = context.GetHttpContext();
|
|
216
|
+
|
|
217
|
+
// Em produção, verificar autenticação/autorização
|
|
218
|
+
if (!httpContext.User.Identity?.IsAuthenticated ?? true)
|
|
219
|
+
return false;
|
|
220
|
+
|
|
221
|
+
return httpContext.User.IsInRole("Admin");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Documentação de Referência
|
|
227
|
+
|
|
228
|
+
- [Hangfire Documentation](https://docs.hangfire.io/en/latest/)
|
|
229
|
+
- [Cron Expressions](https://crontab.guru/)
|
|
230
|
+
- [SQL Server Storage](https://docs.hangfire.io/en/latest/configuration/using-sql-server.html)
|
|
231
|
+
|
|
232
|
+
## Checklist de Jobs
|
|
233
|
+
|
|
234
|
+
- [ ] Job service com interface
|
|
235
|
+
- [ ] Injeção de dependência correta
|
|
236
|
+
- [ ] AutomaticRetry configurado
|
|
237
|
+
- [ ] Queue definida (critical/default/low)
|
|
238
|
+
- [ ] Logging estruturado
|
|
239
|
+
- [ ] CancellationToken propagado
|
|
240
|
+
- [ ] Dashboard protegido
|
|
241
|
+
- [ ] Timezone configurado para recorrentes
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# MS Agent Expert
|
|
2
|
+
|
|
3
|
+
Especialista em Microsoft Agent Framework, Semantic Kernel e integração de IA em aplicações .NET.
|
|
4
|
+
|
|
5
|
+
## Responsabilidades
|
|
6
|
+
|
|
7
|
+
1. **Integrar Microsoft Agent Framework** em projetos
|
|
8
|
+
2. **Configurar Semantic Kernel** para orquestração
|
|
9
|
+
3. **Otimizar prompts** e gerenciar tokens
|
|
10
|
+
4. **Criar agentes** com plugins e functions
|
|
11
|
+
|
|
12
|
+
## Triggers
|
|
13
|
+
|
|
14
|
+
Keywords: `agent`, `ai`, `semantic kernel`, `llm`, `openai`, `chat`, `prompt`, `plugin`, `function calling`
|
|
15
|
+
|
|
16
|
+
## Microsoft Agent Framework
|
|
17
|
+
|
|
18
|
+
> Novo framework oficial da Microsoft que unifica Semantic Kernel e AutoGen.
|
|
19
|
+
|
|
20
|
+
### Estrutura Básica
|
|
21
|
+
|
|
22
|
+
```csharp
|
|
23
|
+
// Agent com Semantic Kernel
|
|
24
|
+
using Microsoft.SemanticKernel;
|
|
25
|
+
using Microsoft.SemanticKernel.Agents;
|
|
26
|
+
|
|
27
|
+
public class OrderAnalysisAgent
|
|
28
|
+
{
|
|
29
|
+
private readonly Kernel _kernel;
|
|
30
|
+
|
|
31
|
+
public OrderAnalysisAgent(Kernel kernel)
|
|
32
|
+
{
|
|
33
|
+
_kernel = kernel;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async Task<string> AnalyzeOrderAsync(Order order)
|
|
37
|
+
{
|
|
38
|
+
var prompt = """
|
|
39
|
+
Analise o pedido abaixo e forneça insights:
|
|
40
|
+
|
|
41
|
+
Número: {{$orderNumber}}
|
|
42
|
+
Total: {{$total}}
|
|
43
|
+
Itens: {{$itemCount}}
|
|
44
|
+
Cliente: {{$customerName}}
|
|
45
|
+
|
|
46
|
+
Forneça:
|
|
47
|
+
1. Resumo do pedido
|
|
48
|
+
2. Sugestões de upsell
|
|
49
|
+
3. Risco de cancelamento (baixo/médio/alto)
|
|
50
|
+
""";
|
|
51
|
+
|
|
52
|
+
var function = _kernel.CreateFunctionFromPrompt(prompt);
|
|
53
|
+
|
|
54
|
+
var result = await _kernel.InvokeAsync(function, new()
|
|
55
|
+
{
|
|
56
|
+
["orderNumber"] = order.OrderNumber,
|
|
57
|
+
["total"] = order.Total.ToString("C"),
|
|
58
|
+
["itemCount"] = order.Items.Count,
|
|
59
|
+
["customerName"] = order.Customer.Name
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return result.GetValue<string>() ?? "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Configuração do Kernel
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
// Program.cs
|
|
71
|
+
builder.Services.AddKernel()
|
|
72
|
+
.AddAzureOpenAIChatCompletion(
|
|
73
|
+
deploymentName: "gpt-4o-mini",
|
|
74
|
+
endpoint: builder.Configuration["AzureOpenAI:Endpoint"]!,
|
|
75
|
+
apiKey: builder.Configuration["AzureOpenAI:ApiKey"]!);
|
|
76
|
+
|
|
77
|
+
// Ou OpenAI direto
|
|
78
|
+
builder.Services.AddKernel()
|
|
79
|
+
.AddOpenAIChatCompletion(
|
|
80
|
+
modelId: "gpt-4o-mini",
|
|
81
|
+
apiKey: builder.Configuration["OpenAI:ApiKey"]!);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Plugins e Functions
|
|
85
|
+
|
|
86
|
+
```csharp
|
|
87
|
+
// Plugins/OrderPlugin.cs
|
|
88
|
+
public class OrderPlugin
|
|
89
|
+
{
|
|
90
|
+
private readonly IOrderService _orderService;
|
|
91
|
+
|
|
92
|
+
public OrderPlugin(IOrderService orderService)
|
|
93
|
+
{
|
|
94
|
+
_orderService = orderService;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
[KernelFunction("get_order")]
|
|
98
|
+
[Description("Busca um pedido pelo número")]
|
|
99
|
+
public async Task<Order?> GetOrderAsync(
|
|
100
|
+
[Description("Número do pedido")] string orderNumber)
|
|
101
|
+
{
|
|
102
|
+
return await _orderService.GetByNumberAsync(orderNumber);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[KernelFunction("calculate_discount")]
|
|
106
|
+
[Description("Calcula desconto baseado no histórico do cliente")]
|
|
107
|
+
public decimal CalculateDiscount(
|
|
108
|
+
[Description("ID do cliente")] int customerId,
|
|
109
|
+
[Description("Valor do pedido")] decimal orderTotal)
|
|
110
|
+
{
|
|
111
|
+
// Lógica de desconto
|
|
112
|
+
return orderTotal * 0.1m;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Registrar plugin
|
|
117
|
+
kernel.Plugins.AddFromObject(new OrderPlugin(orderService));
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Agent Framework (Preview)
|
|
121
|
+
|
|
122
|
+
```csharp
|
|
123
|
+
// Usando Microsoft.SemanticKernel.Agents
|
|
124
|
+
var agent = new ChatCompletionAgent
|
|
125
|
+
{
|
|
126
|
+
Name = "OrderAssistant",
|
|
127
|
+
Instructions = """
|
|
128
|
+
Você é um assistente de pedidos. Ajude os usuários a:
|
|
129
|
+
- Consultar status de pedidos
|
|
130
|
+
- Calcular descontos
|
|
131
|
+
- Sugerir produtos relacionados
|
|
132
|
+
|
|
133
|
+
Seja conciso e profissional.
|
|
134
|
+
""",
|
|
135
|
+
Kernel = kernel
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Conversa
|
|
139
|
+
var history = new ChatHistory();
|
|
140
|
+
history.AddUserMessage("Qual o status do pedido #12345?");
|
|
141
|
+
|
|
142
|
+
await foreach (var message in agent.InvokeAsync(history))
|
|
143
|
+
{
|
|
144
|
+
Console.WriteLine(message.Content);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Padrões de Prompt
|
|
149
|
+
|
|
150
|
+
### System Prompt Estruturado
|
|
151
|
+
|
|
152
|
+
```csharp
|
|
153
|
+
var systemPrompt = """
|
|
154
|
+
# Identidade
|
|
155
|
+
Você é {AgentName}, especialista em {Domain}.
|
|
156
|
+
|
|
157
|
+
# Contexto
|
|
158
|
+
{ContextDescription}
|
|
159
|
+
|
|
160
|
+
# Regras
|
|
161
|
+
- {Rule1}
|
|
162
|
+
- {Rule2}
|
|
163
|
+
|
|
164
|
+
# Formato de Resposta
|
|
165
|
+
{ResponseFormat}
|
|
166
|
+
|
|
167
|
+
# Exemplos
|
|
168
|
+
Input: {ExampleInput}
|
|
169
|
+
Output: {ExampleOutput}
|
|
170
|
+
""";
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Otimização de Tokens
|
|
174
|
+
|
|
175
|
+
1. **Use gpt-4o-mini** como padrão (mais barato)
|
|
176
|
+
2. **Limite contexto** a informações relevantes
|
|
177
|
+
3. **Cache prompts** fixos quando possível
|
|
178
|
+
4. **Meça tokens** com TikToken
|
|
179
|
+
|
|
180
|
+
## Modelo Padrão
|
|
181
|
+
|
|
182
|
+
| Uso | Modelo | Custo |
|
|
183
|
+
|-----|--------|-------|
|
|
184
|
+
| Tarefas simples | gpt-4o-mini | $0.15/1M input |
|
|
185
|
+
| Análise complexa | gpt-4o | $2.50/1M input |
|
|
186
|
+
| Embedding | text-embedding-3-small | $0.02/1M |
|
|
187
|
+
|
|
188
|
+
## Documentação de Referência
|
|
189
|
+
|
|
190
|
+
- [Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview)
|
|
191
|
+
- [Agent Framework GitHub](https://github.com/microsoft/agent-framework)
|
|
192
|
+
- [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/)
|
|
193
|
+
- [Semantic Kernel GitHub](https://github.com/microsoft/semantic-kernel)
|
|
194
|
+
- [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/)
|
|
195
|
+
|
|
196
|
+
## Checklist de Integração AI
|
|
197
|
+
|
|
198
|
+
- [ ] Kernel configurado com DI
|
|
199
|
+
- [ ] Modelo gpt-4o-mini como padrão
|
|
200
|
+
- [ ] API keys no Key Vault (não hardcoded)
|
|
201
|
+
- [ ] Plugins registrados
|
|
202
|
+
- [ ] System prompt estruturado
|
|
203
|
+
- [ ] Error handling para rate limits
|
|
204
|
+
- [ ] Logging de prompts/responses (dev only)
|
|
205
|
+
- [ ] Custos estimados por operação
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
*MORPH-SPEC by Polymorphism Tech*
|