@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,239 @@
|
|
|
1
|
+
@* ============================================================
|
|
2
|
+
BLAZOR COMPONENT TEMPLATE
|
|
3
|
+
Generated by MORPH Framework
|
|
4
|
+
============================================================ *@
|
|
5
|
+
|
|
6
|
+
@page "/{feature}/{feature}s"
|
|
7
|
+
@attribute [Authorize(Policy = "CanView{Feature}")]
|
|
8
|
+
@inject I{Feature}Service {Feature}Service
|
|
9
|
+
@inject ILogger<{Feature}List> Logger
|
|
10
|
+
@inject NavigationManager Navigation
|
|
11
|
+
|
|
12
|
+
<PageTitle>{Feature}s</PageTitle>
|
|
13
|
+
|
|
14
|
+
<div class="container-fluid">
|
|
15
|
+
<div class="row mb-4">
|
|
16
|
+
<div class="col">
|
|
17
|
+
<h1>{Feature}s</h1>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="col-auto">
|
|
20
|
+
<AuthorizeView Policy="CanManage{Feature}">
|
|
21
|
+
<button class="btn btn-primary" @onclick="ShowCreateModal">
|
|
22
|
+
<i class="bi bi-plus-lg"></i> New {Feature}
|
|
23
|
+
</button>
|
|
24
|
+
</AuthorizeView>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
@if (_isLoading)
|
|
29
|
+
{
|
|
30
|
+
<div class="d-flex justify-content-center py-5">
|
|
31
|
+
<div class="spinner-border text-primary" role="status">
|
|
32
|
+
<span class="visually-hidden">Loading...</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
}
|
|
36
|
+
else if (_error is not null)
|
|
37
|
+
{
|
|
38
|
+
<div class="alert alert-danger" role="alert">
|
|
39
|
+
<i class="bi bi-exclamation-triangle"></i>
|
|
40
|
+
@_error
|
|
41
|
+
<button class="btn btn-link" @onclick="LoadDataAsync">Retry</button>
|
|
42
|
+
</div>
|
|
43
|
+
}
|
|
44
|
+
else if (_items is null || !_items.Any())
|
|
45
|
+
{
|
|
46
|
+
<div class="text-center py-5">
|
|
47
|
+
<i class="bi bi-inbox display-1 text-muted"></i>
|
|
48
|
+
<p class="lead mt-3">No {feature}s found</p>
|
|
49
|
+
<AuthorizeView Policy="CanManage{Feature}">
|
|
50
|
+
<button class="btn btn-primary" @onclick="ShowCreateModal">
|
|
51
|
+
Create your first {feature}
|
|
52
|
+
</button>
|
|
53
|
+
</AuthorizeView>
|
|
54
|
+
</div>
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
{
|
|
58
|
+
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
|
59
|
+
@foreach (var item in _items)
|
|
60
|
+
{
|
|
61
|
+
<div class="col">
|
|
62
|
+
<div class="card h-100">
|
|
63
|
+
<div class="card-body">
|
|
64
|
+
<h5 class="card-title">@item.Name</h5>
|
|
65
|
+
<p class="card-text">
|
|
66
|
+
<span class="badge @GetStatusBadgeClass(item.Status)">
|
|
67
|
+
@item.Status
|
|
68
|
+
</span>
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="card-footer bg-transparent">
|
|
72
|
+
<small class="text-muted">
|
|
73
|
+
Created @item.CreatedAt.ToString("d")
|
|
74
|
+
</small>
|
|
75
|
+
<AuthorizeView Policy="CanManage{Feature}">
|
|
76
|
+
<div class="float-end">
|
|
77
|
+
<button class="btn btn-sm btn-outline-primary"
|
|
78
|
+
@onclick="() => ShowEditModal(item)">
|
|
79
|
+
Edit
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</AuthorizeView>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
@* Modal for Create/Edit *@
|
|
92
|
+
@if (_showModal)
|
|
93
|
+
{
|
|
94
|
+
<div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
|
|
95
|
+
<div class="modal-dialog">
|
|
96
|
+
<div class="modal-content">
|
|
97
|
+
<div class="modal-header">
|
|
98
|
+
<h5 class="modal-title">
|
|
99
|
+
@(_editingItem is null ? "New {Feature}" : "Edit {Feature}")
|
|
100
|
+
</h5>
|
|
101
|
+
<button type="button" class="btn-close" @onclick="CloseModal"></button>
|
|
102
|
+
</div>
|
|
103
|
+
<EditForm Model="_formModel" OnValidSubmit="HandleSubmitAsync">
|
|
104
|
+
<DataAnnotationsValidator />
|
|
105
|
+
<div class="modal-body">
|
|
106
|
+
<div class="mb-3">
|
|
107
|
+
<label class="form-label">Name</label>
|
|
108
|
+
<InputText class="form-control" @bind-Value="_formModel.Name" />
|
|
109
|
+
<ValidationMessage For="() => _formModel.Name" />
|
|
110
|
+
</div>
|
|
111
|
+
@* Add more fields as needed *@
|
|
112
|
+
</div>
|
|
113
|
+
<div class="modal-footer">
|
|
114
|
+
<button type="button" class="btn btn-secondary" @onclick="CloseModal">
|
|
115
|
+
Cancel
|
|
116
|
+
</button>
|
|
117
|
+
<button type="submit" class="btn btn-primary" disabled="@_isSaving">
|
|
118
|
+
@if (_isSaving)
|
|
119
|
+
{
|
|
120
|
+
<span class="spinner-border spinner-border-sm"></span>
|
|
121
|
+
}
|
|
122
|
+
Save
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</EditForm>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@code {
|
|
132
|
+
private List<{Feature}Dto>? _items;
|
|
133
|
+
private bool _isLoading = true;
|
|
134
|
+
private bool _showModal;
|
|
135
|
+
private bool _isSaving;
|
|
136
|
+
private string? _error;
|
|
137
|
+
private {Feature}Dto? _editingItem;
|
|
138
|
+
private {Feature}FormModel _formModel = new();
|
|
139
|
+
|
|
140
|
+
protected override async Task OnInitializedAsync()
|
|
141
|
+
{
|
|
142
|
+
await LoadDataAsync();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async Task LoadDataAsync()
|
|
146
|
+
{
|
|
147
|
+
_isLoading = true;
|
|
148
|
+
_error = null;
|
|
149
|
+
|
|
150
|
+
try
|
|
151
|
+
{
|
|
152
|
+
_items = await {Feature}Service.GetAllAsync();
|
|
153
|
+
}
|
|
154
|
+
catch (Exception ex)
|
|
155
|
+
{
|
|
156
|
+
Logger.LogError(ex, "Failed to load {feature}s");
|
|
157
|
+
_error = "Failed to load data. Please try again.";
|
|
158
|
+
}
|
|
159
|
+
finally
|
|
160
|
+
{
|
|
161
|
+
_isLoading = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private void ShowCreateModal()
|
|
166
|
+
{
|
|
167
|
+
_editingItem = null;
|
|
168
|
+
_formModel = new {Feature}FormModel();
|
|
169
|
+
_showModal = true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private void ShowEditModal({Feature}Dto item)
|
|
173
|
+
{
|
|
174
|
+
_editingItem = item;
|
|
175
|
+
_formModel = new {Feature}FormModel
|
|
176
|
+
{
|
|
177
|
+
Name = item.Name
|
|
178
|
+
// Map other fields
|
|
179
|
+
};
|
|
180
|
+
_showModal = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private void CloseModal()
|
|
184
|
+
{
|
|
185
|
+
_showModal = false;
|
|
186
|
+
_formModel = new();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async Task HandleSubmitAsync()
|
|
190
|
+
{
|
|
191
|
+
_isSaving = true;
|
|
192
|
+
|
|
193
|
+
try
|
|
194
|
+
{
|
|
195
|
+
if (_editingItem is null)
|
|
196
|
+
{
|
|
197
|
+
// Create
|
|
198
|
+
var request = new Create{Feature}Request(_formModel.Name);
|
|
199
|
+
await {Feature}Service.CreateAsync(request);
|
|
200
|
+
}
|
|
201
|
+
else
|
|
202
|
+
{
|
|
203
|
+
// Update
|
|
204
|
+
var request = new Update{Feature}Request(_formModel.Name);
|
|
205
|
+
await {Feature}Service.UpdateAsync(_editingItem.Id, request);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
CloseModal();
|
|
209
|
+
await LoadDataAsync();
|
|
210
|
+
}
|
|
211
|
+
catch (Exception ex)
|
|
212
|
+
{
|
|
213
|
+
Logger.LogError(ex, "Failed to save {feature}");
|
|
214
|
+
// Show error to user
|
|
215
|
+
}
|
|
216
|
+
finally
|
|
217
|
+
{
|
|
218
|
+
_isSaving = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private static string GetStatusBadgeClass({Feature}Status status) => status switch
|
|
223
|
+
{
|
|
224
|
+
{Feature}Status.Active => "bg-success",
|
|
225
|
+
{Feature}Status.Pending => "bg-warning text-dark",
|
|
226
|
+
{Feature}Status.Completed => "bg-info",
|
|
227
|
+
{Feature}Status.Failed => "bg-danger",
|
|
228
|
+
_ => "bg-secondary"
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
private class {Feature}FormModel
|
|
232
|
+
{
|
|
233
|
+
[Required]
|
|
234
|
+
[StringLength(200, MinimumLength = 3)]
|
|
235
|
+
public string Name { get; set; } = string.Empty;
|
|
236
|
+
|
|
237
|
+
// Add more fields as needed
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CONTRACTS: {Feature Name}
|
|
3
|
+
// Generated by MORPH Framework
|
|
4
|
+
// Date: {date}
|
|
5
|
+
// ============================================================
|
|
6
|
+
|
|
7
|
+
#region Usings
|
|
8
|
+
|
|
9
|
+
using System;
|
|
10
|
+
using System.Collections.Generic;
|
|
11
|
+
using System.Threading;
|
|
12
|
+
using System.Threading.Tasks;
|
|
13
|
+
|
|
14
|
+
#endregion
|
|
15
|
+
|
|
16
|
+
namespace MyProject.Application.Features.{Feature};
|
|
17
|
+
|
|
18
|
+
#region Service Interfaces
|
|
19
|
+
|
|
20
|
+
/// <summary>
|
|
21
|
+
/// Service for managing {Feature} operations.
|
|
22
|
+
/// </summary>
|
|
23
|
+
public interface I{Feature}Service
|
|
24
|
+
{
|
|
25
|
+
/// <summary>
|
|
26
|
+
/// Gets a {feature} by its identifier.
|
|
27
|
+
/// </summary>
|
|
28
|
+
Task<{Feature}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
|
29
|
+
|
|
30
|
+
/// <summary>
|
|
31
|
+
/// Gets all {features}.
|
|
32
|
+
/// </summary>
|
|
33
|
+
Task<List<{Feature}Dto>> GetAllAsync(CancellationToken cancellationToken = default);
|
|
34
|
+
|
|
35
|
+
/// <summary>
|
|
36
|
+
/// Creates a new {feature}.
|
|
37
|
+
/// </summary>
|
|
38
|
+
Task<{Feature}Dto> CreateAsync(Create{Feature}Request request, CancellationToken cancellationToken = default);
|
|
39
|
+
|
|
40
|
+
/// <summary>
|
|
41
|
+
/// Updates an existing {feature}.
|
|
42
|
+
/// </summary>
|
|
43
|
+
Task UpdateAsync(int id, Update{Feature}Request request, CancellationToken cancellationToken = default);
|
|
44
|
+
|
|
45
|
+
/// <summary>
|
|
46
|
+
/// Deletes a {feature}.
|
|
47
|
+
/// </summary>
|
|
48
|
+
Task DeleteAsync(int id, CancellationToken cancellationToken = default);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#endregion
|
|
52
|
+
|
|
53
|
+
#region DTOs
|
|
54
|
+
|
|
55
|
+
/// <summary>
|
|
56
|
+
/// Data transfer object for {Feature}.
|
|
57
|
+
/// </summary>
|
|
58
|
+
public record {Feature}Dto(
|
|
59
|
+
int Id,
|
|
60
|
+
string Name,
|
|
61
|
+
{Feature}Status Status,
|
|
62
|
+
DateTime CreatedAt,
|
|
63
|
+
DateTime? UpdatedAt
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/// <summary>
|
|
67
|
+
/// Request to create a new {Feature}.
|
|
68
|
+
/// </summary>
|
|
69
|
+
public record Create{Feature}Request(
|
|
70
|
+
string Name
|
|
71
|
+
// Add other required fields
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/// <summary>
|
|
75
|
+
/// Request to update an existing {Feature}.
|
|
76
|
+
/// </summary>
|
|
77
|
+
public record Update{Feature}Request(
|
|
78
|
+
string Name
|
|
79
|
+
// Add other updatable fields
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
#endregion
|
|
83
|
+
|
|
84
|
+
#region Enums
|
|
85
|
+
|
|
86
|
+
/// <summary>
|
|
87
|
+
/// Status of a {Feature}.
|
|
88
|
+
/// </summary>
|
|
89
|
+
public enum {Feature}Status
|
|
90
|
+
{
|
|
91
|
+
Pending = 0,
|
|
92
|
+
Active = 1,
|
|
93
|
+
Completed = 2,
|
|
94
|
+
Failed = 3
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#endregion
|
|
98
|
+
|
|
99
|
+
#region Repository Interfaces
|
|
100
|
+
|
|
101
|
+
/// <summary>
|
|
102
|
+
/// Repository for {Feature} data access.
|
|
103
|
+
/// </summary>
|
|
104
|
+
public interface I{Feature}Repository
|
|
105
|
+
{
|
|
106
|
+
Task<{Feature}?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
|
107
|
+
Task<List<{Feature}>> GetAllAsync(CancellationToken cancellationToken = default);
|
|
108
|
+
Task AddAsync({Feature} entity, CancellationToken cancellationToken = default);
|
|
109
|
+
void Update({Feature} entity);
|
|
110
|
+
void Remove({Feature} entity);
|
|
111
|
+
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#endregion
|
|
115
|
+
|
|
116
|
+
#region Domain Entity Reference
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
/// <summary>
|
|
120
|
+
/// {Feature} domain entity.
|
|
121
|
+
/// Implement in Domain/Entities/{Feature}.cs
|
|
122
|
+
/// </summary>
|
|
123
|
+
public class {Feature}
|
|
124
|
+
{
|
|
125
|
+
public int Id { get; private set; }
|
|
126
|
+
public string Name { get; private set; } = string.Empty;
|
|
127
|
+
public {Feature}Status Status { get; private set; }
|
|
128
|
+
public DateTime CreatedAt { get; private set; }
|
|
129
|
+
public DateTime? UpdatedAt { get; private set; }
|
|
130
|
+
|
|
131
|
+
private {Feature}() { } // EF Constructor
|
|
132
|
+
|
|
133
|
+
public static {Feature} Create(string name)
|
|
134
|
+
{
|
|
135
|
+
return new {Feature}
|
|
136
|
+
{
|
|
137
|
+
Name = name,
|
|
138
|
+
Status = {Feature}Status.Pending,
|
|
139
|
+
CreatedAt = DateTime.UtcNow
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public void Activate()
|
|
144
|
+
{
|
|
145
|
+
Status = {Feature}Status.Active;
|
|
146
|
+
UpdatedAt = DateTime.UtcNow;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public void Complete()
|
|
150
|
+
{
|
|
151
|
+
Status = {Feature}Status.Completed;
|
|
152
|
+
UpdatedAt = DateTime.UtcNow;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
#endregion
|
|
158
|
+
|
|
159
|
+
#region AI Agent Interfaces (if applicable)
|
|
160
|
+
|
|
161
|
+
/// <summary>
|
|
162
|
+
/// AI Agent for {Feature} analysis.
|
|
163
|
+
/// </summary>
|
|
164
|
+
public interface I{Feature}AnalyzerAgent
|
|
165
|
+
{
|
|
166
|
+
/// <summary>
|
|
167
|
+
/// Analyzes {feature} data using AI.
|
|
168
|
+
/// </summary>
|
|
169
|
+
Task<{Feature}AnalysisResult> AnalyzeAsync(
|
|
170
|
+
{Feature}Data data,
|
|
171
|
+
CancellationToken cancellationToken = default);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public record {Feature}Data(
|
|
175
|
+
// Input data for analysis
|
|
176
|
+
string Content
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
public record {Feature}AnalysisResult(
|
|
180
|
+
string Summary,
|
|
181
|
+
List<string> Insights,
|
|
182
|
+
List<string> Recommendations,
|
|
183
|
+
double ConfidenceScore
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
#endregion
|
|
187
|
+
|
|
188
|
+
#region Hangfire Job Interfaces (if applicable)
|
|
189
|
+
|
|
190
|
+
/// <summary>
|
|
191
|
+
/// Background job for processing {Feature}.
|
|
192
|
+
/// </summary>
|
|
193
|
+
public interface I{Feature}ProcessorJob
|
|
194
|
+
{
|
|
195
|
+
/// <summary>
|
|
196
|
+
/// Executes the {feature} processing job.
|
|
197
|
+
/// </summary>
|
|
198
|
+
Task ExecuteAsync(int id, CancellationToken cancellationToken = default);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#endregion
|
|
202
|
+
|
|
203
|
+
#region Exceptions
|
|
204
|
+
|
|
205
|
+
public class {Feature}NotFoundException : Exception
|
|
206
|
+
{
|
|
207
|
+
public {Feature}NotFoundException(int id)
|
|
208
|
+
: base($"{Feature} with ID {id} was not found.") { }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public class {Feature}ProcessingException : Exception
|
|
212
|
+
{
|
|
213
|
+
public {Feature}ProcessingException(string message, Exception? innerException = null)
|
|
214
|
+
: base(message, innerException) { }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#endregion
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Architecture Decision Records - {Feature Name}
|
|
2
|
+
|
|
3
|
+
## ADR-001: {Decision Title}
|
|
4
|
+
|
|
5
|
+
**Date:** {date}
|
|
6
|
+
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
7
|
+
**Deciders:** MORPH Agents, Developer
|
|
8
|
+
|
|
9
|
+
### Context
|
|
10
|
+
|
|
11
|
+
{Descreva o contexto e o problema que levou a esta decisão}
|
|
12
|
+
|
|
13
|
+
### Decision
|
|
14
|
+
|
|
15
|
+
{Descreva a decisão tomada}
|
|
16
|
+
|
|
17
|
+
### Consequences
|
|
18
|
+
|
|
19
|
+
**Positivas:**
|
|
20
|
+
- {Consequência positiva 1}
|
|
21
|
+
- {Consequência positiva 2}
|
|
22
|
+
|
|
23
|
+
**Negativas:**
|
|
24
|
+
- {Consequência negativa 1}
|
|
25
|
+
|
|
26
|
+
**Riscos:**
|
|
27
|
+
- {Risco identificado}
|
|
28
|
+
|
|
29
|
+
### Alternatives Considered
|
|
30
|
+
|
|
31
|
+
#### Option A: {Nome}
|
|
32
|
+
- Pros: {vantagens}
|
|
33
|
+
- Cons: {desvantagens}
|
|
34
|
+
- **Rejected because:** {motivo}
|
|
35
|
+
|
|
36
|
+
#### Option B: {Nome}
|
|
37
|
+
- Pros: {vantagens}
|
|
38
|
+
- Cons: {desvantagens}
|
|
39
|
+
- **Rejected because:** {motivo}
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ADR-002: Database Model
|
|
44
|
+
|
|
45
|
+
**Date:** {date}
|
|
46
|
+
**Status:** Accepted
|
|
47
|
+
|
|
48
|
+
### Context
|
|
49
|
+
|
|
50
|
+
Need to define how {Feature} data will be stored.
|
|
51
|
+
|
|
52
|
+
### Decision
|
|
53
|
+
|
|
54
|
+
Use Azure SQL with the following model:
|
|
55
|
+
- {Entity} table with {columns}
|
|
56
|
+
- JSON columns for {flexible data}
|
|
57
|
+
|
|
58
|
+
### Consequences
|
|
59
|
+
|
|
60
|
+
**Positivas:**
|
|
61
|
+
- Fits within free tier (32GB)
|
|
62
|
+
- Good query performance
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ADR-003: AI Integration
|
|
67
|
+
|
|
68
|
+
**Date:** {date}
|
|
69
|
+
**Status:** Accepted
|
|
70
|
+
|
|
71
|
+
### Context
|
|
72
|
+
|
|
73
|
+
Feature requires AI capabilities for {purpose}.
|
|
74
|
+
|
|
75
|
+
### Decision
|
|
76
|
+
|
|
77
|
+
Use MS Agent Framework with gpt-4o-mini model.
|
|
78
|
+
|
|
79
|
+
### Consequences
|
|
80
|
+
|
|
81
|
+
**Positivas:**
|
|
82
|
+
- Cost effective (~$0.15/1M input tokens)
|
|
83
|
+
- Good enough quality for this use case
|
|
84
|
+
|
|
85
|
+
**Negativas:**
|
|
86
|
+
- Slightly less capable than gpt-4o
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Template for New ADRs
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
## ADR-XXX: {Title}
|
|
94
|
+
|
|
95
|
+
**Date:**
|
|
96
|
+
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
97
|
+
|
|
98
|
+
### Context
|
|
99
|
+
{Why is this decision needed?}
|
|
100
|
+
|
|
101
|
+
### Decision
|
|
102
|
+
{What was decided?}
|
|
103
|
+
|
|
104
|
+
### Consequences
|
|
105
|
+
{What are the results?}
|
|
106
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Application Insights
|
|
3
|
+
// Azure Application Insights for monitoring and telemetry
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
@description('Application Insights name')
|
|
7
|
+
param name string
|
|
8
|
+
|
|
9
|
+
@description('Location')
|
|
10
|
+
param location string
|
|
11
|
+
|
|
12
|
+
@description('Tags')
|
|
13
|
+
param tags object = {}
|
|
14
|
+
|
|
15
|
+
@description('Log Analytics Workspace ID')
|
|
16
|
+
param logAnalyticsWorkspaceId string
|
|
17
|
+
|
|
18
|
+
@description('Application type')
|
|
19
|
+
@allowed(['web', 'other'])
|
|
20
|
+
param applicationType string = 'web'
|
|
21
|
+
|
|
22
|
+
@description('Retention in days')
|
|
23
|
+
@minValue(30)
|
|
24
|
+
@maxValue(730)
|
|
25
|
+
param retentionInDays int = 90
|
|
26
|
+
|
|
27
|
+
// ==============================================================================
|
|
28
|
+
// APPLICATION INSIGHTS
|
|
29
|
+
// ==============================================================================
|
|
30
|
+
|
|
31
|
+
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
|
|
32
|
+
name: name
|
|
33
|
+
location: location
|
|
34
|
+
tags: tags
|
|
35
|
+
kind: applicationType
|
|
36
|
+
properties: {
|
|
37
|
+
Application_Type: applicationType
|
|
38
|
+
WorkspaceResourceId: logAnalyticsWorkspaceId
|
|
39
|
+
IngestionMode: 'LogAnalytics'
|
|
40
|
+
publicNetworkAccessForIngestion: 'Enabled'
|
|
41
|
+
publicNetworkAccessForQuery: 'Enabled'
|
|
42
|
+
RetentionInDays: retentionInDays
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ==============================================================================
|
|
47
|
+
// OUTPUTS
|
|
48
|
+
// ==============================================================================
|
|
49
|
+
|
|
50
|
+
@description('Application Insights ID')
|
|
51
|
+
output id string = appInsights.id
|
|
52
|
+
|
|
53
|
+
@description('Application Insights name')
|
|
54
|
+
output name string = appInsights.name
|
|
55
|
+
|
|
56
|
+
@description('Instrumentation Key')
|
|
57
|
+
output instrumentationKey string = appInsights.properties.InstrumentationKey
|
|
58
|
+
|
|
59
|
+
@description('Connection String')
|
|
60
|
+
output connectionString string = appInsights.properties.ConnectionString
|
|
61
|
+
|
|
62
|
+
@description('App ID')
|
|
63
|
+
output appId string = appInsights.properties.AppId
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Container Apps Environment
|
|
3
|
+
// Azure Container Apps managed environment
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
@description('Environment name')
|
|
7
|
+
param name string
|
|
8
|
+
|
|
9
|
+
@description('Location')
|
|
10
|
+
param location string
|
|
11
|
+
|
|
12
|
+
@description('Tags')
|
|
13
|
+
param tags object = {}
|
|
14
|
+
|
|
15
|
+
@description('Log Analytics Workspace ID')
|
|
16
|
+
param logAnalyticsWorkspaceId string
|
|
17
|
+
|
|
18
|
+
// ==============================================================================
|
|
19
|
+
// CONTAINER APPS ENVIRONMENT
|
|
20
|
+
// ==============================================================================
|
|
21
|
+
|
|
22
|
+
resource containerAppEnv 'Microsoft.App/managedEnvironments@2023-05-01' = {
|
|
23
|
+
name: name
|
|
24
|
+
location: location
|
|
25
|
+
tags: tags
|
|
26
|
+
properties: {
|
|
27
|
+
appLogsConfiguration: {
|
|
28
|
+
destination: 'log-analytics'
|
|
29
|
+
logAnalyticsConfiguration: {
|
|
30
|
+
customerId: reference(logAnalyticsWorkspaceId, '2022-10-01').customerId
|
|
31
|
+
sharedKey: listKeys(logAnalyticsWorkspaceId, '2022-10-01').primarySharedKey
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
zoneRedundant: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ==============================================================================
|
|
39
|
+
// OUTPUTS
|
|
40
|
+
// ==============================================================================
|
|
41
|
+
|
|
42
|
+
@description('Container Apps Environment ID')
|
|
43
|
+
output id string = containerAppEnv.id
|
|
44
|
+
|
|
45
|
+
@description('Container Apps Environment name')
|
|
46
|
+
output name string = containerAppEnv.name
|
|
47
|
+
|
|
48
|
+
@description('Default domain')
|
|
49
|
+
output defaultDomain string = containerAppEnv.properties.defaultDomain
|