@atlashub/smartstack-cli 3.5.0 → 3.6.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/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/skills/business-analyse/SKILL.md +26 -15
- package/templates/skills/business-analyse/_architecture.md +1 -1
- package/templates/skills/business-analyse/_elicitation.md +1 -1
- package/templates/skills/business-analyse/_module-loop.md +4 -4
- package/templates/skills/business-analyse/html/ba-interactive.html +39 -10
- package/templates/skills/business-analyse/questionnaire/06-security.md +1 -1
- package/templates/skills/business-analyse/questionnaire.md +2 -2
- package/templates/skills/business-analyse/react/components.md +1 -1
- package/templates/skills/business-analyse/react/schema.md +1 -1
- package/templates/skills/business-analyse/references/html-data-mapping.md +4 -3
- package/templates/skills/business-analyse/schemas/feature-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +1 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +29 -0
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +166 -6
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -4
- package/templates/skills/business-analyse/steps/{step-03a-specify.md → step-03a-data.md} +10 -359
- package/templates/skills/business-analyse/steps/step-03b-ui.md +414 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +343 -0
- package/templates/skills/business-analyse/steps/{step-03b-compile.md → step-03d-validate.md} +26 -308
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +2 -2
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +49 -292
- package/templates/skills/business-analyse/steps/step-05b-mapping.md +302 -0
- package/templates/skills/business-analyse/steps/step-05c-deploy.md +296 -0
- package/templates/skills/business-analyse/steps/step-05d-html.md +326 -0
- package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +1 -1
- package/templates/skills/business-analyse/templates/tpl-progress.md +1 -1
- package/templates/skills/controller/steps/step-03-generate.md +2 -1
- package/templates/skills/ralph-loop/SKILL.md +17 -2
- package/templates/skills/ralph-loop/references/core-seed-data.md +538 -0
- package/templates/skills/ralph-loop/steps/step-00-init.md +2 -0
- package/templates/skills/ralph-loop/steps/step-01-task.md +25 -2
- package/templates/skills/ralph-loop/steps/step-02-execute.md +39 -15
- package/templates/skills/ralph-loop/steps/step-04-check.md +87 -4
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +0 -475
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# Core Seed Data - Execution Reference
|
|
2
|
+
|
|
3
|
+
> **Loaded by:** step-02-execute and step-04 compact loop
|
|
4
|
+
> **Condition:** Infrastructure task involves core seed data or IClientSeedDataProvider
|
|
5
|
+
> **Applies to:** Client projects only (seeding_strategy = "provider", ExtensionsDbContext)
|
|
6
|
+
>
|
|
7
|
+
> **Source of truth:** `/application` skill `templates-seed.md` (lines 608-916)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Parameter Extraction
|
|
12
|
+
|
|
13
|
+
Extract navigation hierarchy from the task's `_seedDataMeta` (populated by guardrail 1e):
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const task = currentTask;
|
|
17
|
+
const meta = task._seedDataMeta || task._providerMeta || {};
|
|
18
|
+
const coreSeedData = meta.coreSeedData || {};
|
|
19
|
+
|
|
20
|
+
// Navigation hierarchy
|
|
21
|
+
const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
|
|
22
|
+
const permissions = coreSeedData.permissions || [];
|
|
23
|
+
const rolePerms = coreSeedData.rolePermissions || [];
|
|
24
|
+
|
|
25
|
+
// Derived context (from guardrail or PRD)
|
|
26
|
+
const navRoute = meta.navRoute; // e.g. "business.humanresources.projects"
|
|
27
|
+
const contextCode = meta.contextCode; // e.g. "business"
|
|
28
|
+
const appCode = meta.appCode; // e.g. "humanresources"
|
|
29
|
+
const moduleCode = task.module; // e.g. "projects"
|
|
30
|
+
|
|
31
|
+
// If _seedDataMeta is absent, fallback to PRD source
|
|
32
|
+
if (!navRoute) {
|
|
33
|
+
const prd = readJSON('.ralph/prd.json');
|
|
34
|
+
const navRoute = `${prd.source?.context || 'business'}.${prd.source?.application || prd.metadata?.module}.${task.module}`;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**State variables after extraction:**
|
|
39
|
+
|
|
40
|
+
| Variable | Example | Source |
|
|
41
|
+
|----------|---------|--------|
|
|
42
|
+
| `navRoute` | `business.humanresources.projects` | `_seedDataMeta.navRoute` |
|
|
43
|
+
| `contextCode` | `business` | `_seedDataMeta.contextCode` |
|
|
44
|
+
| `appCode` | `humanresources` | `_seedDataMeta.appCode` |
|
|
45
|
+
| `moduleCode` | `projects` | `task.module` |
|
|
46
|
+
| `navModules[]` | `[{code, label, icon, route, translations}]` | `coreSeedData.navigationModules` |
|
|
47
|
+
| `permissions[]` | `[{path, action, description}]` | `coreSeedData.permissions` |
|
|
48
|
+
| `rolePerms[]` | `[{role, permissions[]}]` | `coreSeedData.rolePermissions` |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. NavigationModuleSeedData.cs
|
|
53
|
+
|
|
54
|
+
**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/NavigationModuleSeedData.cs`
|
|
55
|
+
|
|
56
|
+
### GUID Generation Rule
|
|
57
|
+
|
|
58
|
+
```csharp
|
|
59
|
+
// NEVER use Guid.NewGuid() — ALWAYS deterministic
|
|
60
|
+
private static Guid GenerateDeterministicGuid(string seed)
|
|
61
|
+
{
|
|
62
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
63
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
|
|
64
|
+
return new Guid(hash.Take(16).ToArray());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Usage: GUIDs are derived from the navigation path
|
|
68
|
+
public static readonly Guid ModuleId = GenerateDeterministicGuid("navigation-module-{navRoute}");
|
|
69
|
+
// Example: GenerateDeterministicGuid("navigation-module-business.humanresources.projects")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Template
|
|
73
|
+
|
|
74
|
+
```csharp
|
|
75
|
+
using SmartStack.Domain.Navigation;
|
|
76
|
+
|
|
77
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
|
|
78
|
+
|
|
79
|
+
/// <summary>
|
|
80
|
+
/// Navigation seed data for {ModuleLabel} module.
|
|
81
|
+
/// Consumed by IClientSeedDataProvider at application startup.
|
|
82
|
+
/// </summary>
|
|
83
|
+
public static class {ModulePascal}NavigationSeedData
|
|
84
|
+
{
|
|
85
|
+
// Deterministic GUID for this module
|
|
86
|
+
public static readonly Guid {ModulePascal}ModuleId =
|
|
87
|
+
GenerateDeterministicGuid("navigation-module-{navRoute}");
|
|
88
|
+
|
|
89
|
+
/// <summary>
|
|
90
|
+
/// Returns navigation module entry for seeding into core.nav_Modules.
|
|
91
|
+
/// </summary>
|
|
92
|
+
public static NavigationModuleSeedEntry GetModuleEntry(Guid applicationId)
|
|
93
|
+
{
|
|
94
|
+
return new NavigationModuleSeedEntry
|
|
95
|
+
{
|
|
96
|
+
Id = {ModulePascal}ModuleId,
|
|
97
|
+
ApplicationId = applicationId,
|
|
98
|
+
Code = "{moduleCode}",
|
|
99
|
+
Label = "{label_en}",
|
|
100
|
+
Description = "{desc_en}",
|
|
101
|
+
Icon = "{icon}", // Lucide React icon name
|
|
102
|
+
IconType = IconType.Lucide,
|
|
103
|
+
Route = "/{contextCode}/{appCode}/{moduleCode}",
|
|
104
|
+
DisplayOrder = {displayOrder},
|
|
105
|
+
IsActive = true
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// <summary>
|
|
110
|
+
/// Returns 4-language translations for this module.
|
|
111
|
+
/// </summary>
|
|
112
|
+
public static IEnumerable<NavigationTranslationSeedEntry> GetTranslationEntries()
|
|
113
|
+
{
|
|
114
|
+
var moduleId = {ModulePascal}ModuleId;
|
|
115
|
+
return new[]
|
|
116
|
+
{
|
|
117
|
+
new NavigationTranslationSeedEntry
|
|
118
|
+
{
|
|
119
|
+
EntityType = NavigationEntityType.Module,
|
|
120
|
+
EntityId = moduleId,
|
|
121
|
+
LanguageCode = "fr",
|
|
122
|
+
Label = "{label_fr}",
|
|
123
|
+
Description = "{desc_fr}"
|
|
124
|
+
},
|
|
125
|
+
new NavigationTranslationSeedEntry
|
|
126
|
+
{
|
|
127
|
+
EntityType = NavigationEntityType.Module,
|
|
128
|
+
EntityId = moduleId,
|
|
129
|
+
LanguageCode = "en",
|
|
130
|
+
Label = "{label_en}",
|
|
131
|
+
Description = "{desc_en}"
|
|
132
|
+
},
|
|
133
|
+
new NavigationTranslationSeedEntry
|
|
134
|
+
{
|
|
135
|
+
EntityType = NavigationEntityType.Module,
|
|
136
|
+
EntityId = moduleId,
|
|
137
|
+
LanguageCode = "it",
|
|
138
|
+
Label = "{label_it}",
|
|
139
|
+
Description = "{desc_it}"
|
|
140
|
+
},
|
|
141
|
+
new NavigationTranslationSeedEntry
|
|
142
|
+
{
|
|
143
|
+
EntityType = NavigationEntityType.Module,
|
|
144
|
+
EntityId = moduleId,
|
|
145
|
+
LanguageCode = "de",
|
|
146
|
+
Label = "{label_de}",
|
|
147
|
+
Description = "{desc_de}"
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private static Guid GenerateDeterministicGuid(string seed)
|
|
153
|
+
{
|
|
154
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
155
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
|
|
156
|
+
return new Guid(hash.Take(16).ToArray());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// <summary>Seed entry DTO for navigation module.</summary>
|
|
161
|
+
public class NavigationModuleSeedEntry
|
|
162
|
+
{
|
|
163
|
+
public Guid Id { get; init; }
|
|
164
|
+
public Guid ApplicationId { get; init; }
|
|
165
|
+
public string Code { get; init; } = null!;
|
|
166
|
+
public string Label { get; init; } = null!;
|
|
167
|
+
public string Description { get; init; } = null!;
|
|
168
|
+
public string Icon { get; init; } = null!;
|
|
169
|
+
public IconType IconType { get; init; }
|
|
170
|
+
public string Route { get; init; } = null!;
|
|
171
|
+
public int DisplayOrder { get; init; }
|
|
172
|
+
public bool IsActive { get; init; }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// <summary>Seed entry DTO for navigation translation.</summary>
|
|
176
|
+
public class NavigationTranslationSeedEntry
|
|
177
|
+
{
|
|
178
|
+
public NavigationEntityType EntityType { get; init; }
|
|
179
|
+
public Guid EntityId { get; init; }
|
|
180
|
+
public string LanguageCode { get; init; } = null!;
|
|
181
|
+
public string Label { get; init; } = null!;
|
|
182
|
+
public string Description { get; init; } = null!;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Replace placeholders** with values from `navModules[]` and PRD `project` metadata.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 3. PermissionsSeedData.cs — MCP-First
|
|
191
|
+
|
|
192
|
+
### Step A: Call MCP (PRIMARY)
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Tool: mcp__smartstack__generate_permissions
|
|
196
|
+
Args:
|
|
197
|
+
navRoute: "{navRoute}"
|
|
198
|
+
includeStandardActions: true
|
|
199
|
+
includeWildcard: true
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
MCP returns:
|
|
203
|
+
- `Permissions.cs` nested class (Application layer constants)
|
|
204
|
+
- Permission seed entries with deterministic GUIDs
|
|
205
|
+
|
|
206
|
+
### Step B: Write Permissions.cs (Application layer)
|
|
207
|
+
|
|
208
|
+
```csharp
|
|
209
|
+
// Add to Application/Common/Authorization/Permissions.cs
|
|
210
|
+
public static class {ContextPascal}
|
|
211
|
+
{
|
|
212
|
+
public const string Access = "{contextCode}.{appCode}";
|
|
213
|
+
|
|
214
|
+
public static class {ModulePascal}
|
|
215
|
+
{
|
|
216
|
+
public const string View = "{navRoute}.read";
|
|
217
|
+
public const string Create = "{navRoute}.create";
|
|
218
|
+
public const string Update = "{navRoute}.update";
|
|
219
|
+
public const string Delete = "{navRoute}.delete";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Step C: Write PermissionsSeedData.cs (Infrastructure layer)
|
|
225
|
+
|
|
226
|
+
**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/PermissionsSeedData.cs`
|
|
227
|
+
|
|
228
|
+
```csharp
|
|
229
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
|
|
230
|
+
|
|
231
|
+
/// <summary>
|
|
232
|
+
/// Permission seed data for {ModuleLabel} module.
|
|
233
|
+
/// Consumed by IClientSeedDataProvider at application startup.
|
|
234
|
+
/// </summary>
|
|
235
|
+
public static class {ModulePascal}PermissionsSeedData
|
|
236
|
+
{
|
|
237
|
+
// Deterministic GUIDs for permissions
|
|
238
|
+
public static readonly Guid WildcardPermId = GenerateGuid("{navRoute}.*");
|
|
239
|
+
public static readonly Guid ReadPermId = GenerateGuid("{navRoute}.read");
|
|
240
|
+
public static readonly Guid CreatePermId = GenerateGuid("{navRoute}.create");
|
|
241
|
+
public static readonly Guid UpdatePermId = GenerateGuid("{navRoute}.update");
|
|
242
|
+
public static readonly Guid DeletePermId = GenerateGuid("{navRoute}.delete");
|
|
243
|
+
|
|
244
|
+
public static IEnumerable<PermissionSeedEntry> GetPermissionEntries(Guid moduleId)
|
|
245
|
+
{
|
|
246
|
+
return new[]
|
|
247
|
+
{
|
|
248
|
+
new PermissionSeedEntry { Id = WildcardPermId, Path = "{navRoute}.*", Level = PermissionLevel.Module, Action = PermissionAction.Access, IsWildcard = true, ModuleId = moduleId, Description = "Full {moduleLabel} access" },
|
|
249
|
+
new PermissionSeedEntry { Id = ReadPermId, Path = "{navRoute}.read", Level = PermissionLevel.Module, Action = PermissionAction.Read, IsWildcard = false, ModuleId = moduleId, Description = "View {moduleLabel}" },
|
|
250
|
+
new PermissionSeedEntry { Id = CreatePermId, Path = "{navRoute}.create", Level = PermissionLevel.Module, Action = PermissionAction.Create, IsWildcard = false, ModuleId = moduleId, Description = "Create {moduleLabel}" },
|
|
251
|
+
new PermissionSeedEntry { Id = UpdatePermId, Path = "{navRoute}.update", Level = PermissionLevel.Module, Action = PermissionAction.Update, IsWildcard = false, ModuleId = moduleId, Description = "Update {moduleLabel}" },
|
|
252
|
+
new PermissionSeedEntry { Id = DeletePermId, Path = "{navRoute}.delete", Level = PermissionLevel.Module, Action = PermissionAction.Delete, IsWildcard = false, ModuleId = moduleId, Description = "Delete {moduleLabel}" }
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private static Guid GenerateGuid(string path)
|
|
257
|
+
{
|
|
258
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
259
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"permission-{path}"));
|
|
260
|
+
return new Guid(hash.Take(16).ToArray());
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public class PermissionSeedEntry
|
|
265
|
+
{
|
|
266
|
+
public Guid Id { get; init; }
|
|
267
|
+
public string Path { get; init; } = null!;
|
|
268
|
+
public PermissionLevel Level { get; init; }
|
|
269
|
+
public PermissionAction Action { get; init; }
|
|
270
|
+
public bool IsWildcard { get; init; }
|
|
271
|
+
public Guid ModuleId { get; init; }
|
|
272
|
+
public string Description { get; init; } = null!;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Step D: MCP Fallback
|
|
277
|
+
|
|
278
|
+
If MCP `generate_permissions` fails, use the template above directly with values derived from the PRD `coreSeedData.permissions[]`.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 4. RolesSeedData.cs
|
|
283
|
+
|
|
284
|
+
**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/RolesSeedData.cs`
|
|
285
|
+
|
|
286
|
+
### Context-Based Role Mapping
|
|
287
|
+
|
|
288
|
+
| Context | Admin | Manager | Contributor | Viewer |
|
|
289
|
+
|---------|-------|---------|-------------|--------|
|
|
290
|
+
| `platform.*` | CRUD | CRU | CR | R |
|
|
291
|
+
| `business.*` | CRUD | CRU | CR | R |
|
|
292
|
+
| `personal.*` | CRUD | CRU | CR | R |
|
|
293
|
+
|
|
294
|
+
### Template
|
|
295
|
+
|
|
296
|
+
```csharp
|
|
297
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
|
|
298
|
+
|
|
299
|
+
/// <summary>
|
|
300
|
+
/// Role-permission mapping seed data for {ModuleLabel} module.
|
|
301
|
+
/// Maps permissions to application-scoped roles (Admin, Manager, Contributor, Viewer).
|
|
302
|
+
/// Consumed by IClientSeedDataProvider at application startup.
|
|
303
|
+
/// </summary>
|
|
304
|
+
public static class {ModulePascal}RolesSeedData
|
|
305
|
+
{
|
|
306
|
+
/// <summary>
|
|
307
|
+
/// Returns role-permission mappings for this module.
|
|
308
|
+
/// Roles are resolved at runtime by Code (not hardcoded GUIDs).
|
|
309
|
+
/// </summary>
|
|
310
|
+
public static IEnumerable<RolePermissionSeedEntry> GetRolePermissionEntries()
|
|
311
|
+
{
|
|
312
|
+
// Admin: wildcard access
|
|
313
|
+
yield return new RolePermissionSeedEntry { RoleCode = "admin", PermissionPath = "{navRoute}.*" };
|
|
314
|
+
|
|
315
|
+
// Manager: CRUD
|
|
316
|
+
yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.read" };
|
|
317
|
+
yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.create" };
|
|
318
|
+
yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.update" };
|
|
319
|
+
|
|
320
|
+
// Contributor: CR
|
|
321
|
+
yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.read" };
|
|
322
|
+
yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.create" };
|
|
323
|
+
|
|
324
|
+
// Viewer: R
|
|
325
|
+
yield return new RolePermissionSeedEntry { RoleCode = "viewer", PermissionPath = "{navRoute}.read" };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
public class RolePermissionSeedEntry
|
|
330
|
+
{
|
|
331
|
+
public string RoleCode { get; init; } = null!;
|
|
332
|
+
public string PermissionPath { get; init; } = null!;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 5. IClientSeedDataProvider Implementation
|
|
339
|
+
|
|
340
|
+
**File:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
|
|
341
|
+
|
|
342
|
+
### Critical Rules
|
|
343
|
+
|
|
344
|
+
| Rule | Description |
|
|
345
|
+
|------|-------------|
|
|
346
|
+
| Factory methods | `NavigationModule.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()` |
|
|
347
|
+
| Idempotence | Each Seed method checks existence before inserting |
|
|
348
|
+
| SaveChanges per group | Navigation -> save -> Permissions -> save -> RolePermissions -> save |
|
|
349
|
+
| FK resolution by Code | Parent entities found by `Code`, not hardcoded GUID |
|
|
350
|
+
| DI registration | `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()` |
|
|
351
|
+
|
|
352
|
+
### Template
|
|
353
|
+
|
|
354
|
+
```csharp
|
|
355
|
+
using Microsoft.EntityFrameworkCore;
|
|
356
|
+
using SmartStack.Application.Common.Interfaces;
|
|
357
|
+
using SmartStack.Application.Common.Interfaces.Seeding;
|
|
358
|
+
using SmartStack.Domain.Navigation;
|
|
359
|
+
using SmartStack.Domain.Platform.Administration.Roles;
|
|
360
|
+
using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module1Pascal};
|
|
361
|
+
// using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module2Pascal}; // Add per module
|
|
362
|
+
|
|
363
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
|
|
364
|
+
|
|
365
|
+
/// <summary>
|
|
366
|
+
/// Seeds {AppLabel} navigation, permissions, and role-permission data
|
|
367
|
+
/// into the SmartStack Core schema at application startup.
|
|
368
|
+
/// </summary>
|
|
369
|
+
public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
370
|
+
{
|
|
371
|
+
public int Order => 100;
|
|
372
|
+
|
|
373
|
+
public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
|
|
374
|
+
{
|
|
375
|
+
// --- Application ---
|
|
376
|
+
var exists = await context.NavigationApplications
|
|
377
|
+
.AnyAsync(a => a.Code == "{appCode}", ct);
|
|
378
|
+
if (exists) return;
|
|
379
|
+
|
|
380
|
+
var parentContext = await context.NavigationContexts
|
|
381
|
+
.FirstAsync(c => c.Code == "{contextCode}", ct);
|
|
382
|
+
|
|
383
|
+
var app = NavigationApplication.Create(
|
|
384
|
+
parentContext.Id, "{appCode}", "{appLabel_en}",
|
|
385
|
+
"{appDesc_en}", "{appIcon}", IconType.Lucide,
|
|
386
|
+
"/{contextCode}/{appCode}", 1);
|
|
387
|
+
context.NavigationApplications.Add(app);
|
|
388
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
389
|
+
|
|
390
|
+
// --- Application translations (4 languages) ---
|
|
391
|
+
var appTranslations = new[]
|
|
392
|
+
{
|
|
393
|
+
NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "fr", "{appLabel_fr}", "{appDesc_fr}"),
|
|
394
|
+
NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "en", "{appLabel_en}", "{appDesc_en}"),
|
|
395
|
+
NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "it", "{appLabel_it}", "{appDesc_it}"),
|
|
396
|
+
NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "de", "{appLabel_de}", "{appDesc_de}")
|
|
397
|
+
};
|
|
398
|
+
foreach (var t in appTranslations) context.NavigationTranslations.Add(t);
|
|
399
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
400
|
+
|
|
401
|
+
// --- Modules ---
|
|
402
|
+
// Module: {Module1}
|
|
403
|
+
var mod1Entry = {Module1Pascal}NavigationSeedData.GetModuleEntry(app.Id);
|
|
404
|
+
var mod1 = NavigationModule.Create(
|
|
405
|
+
mod1Entry.ApplicationId, mod1Entry.Code, mod1Entry.Label,
|
|
406
|
+
mod1Entry.Description, mod1Entry.Icon, mod1Entry.IconType,
|
|
407
|
+
mod1Entry.Route, mod1Entry.DisplayOrder);
|
|
408
|
+
context.NavigationModules.Add(mod1);
|
|
409
|
+
|
|
410
|
+
// Repeat for each module...
|
|
411
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
412
|
+
|
|
413
|
+
// --- Module translations ---
|
|
414
|
+
foreach (var t in {Module1Pascal}NavigationSeedData.GetTranslationEntries())
|
|
415
|
+
{
|
|
416
|
+
context.NavigationTranslations.Add(
|
|
417
|
+
NavigationTranslation.Create(t.EntityType, t.EntityId, t.LanguageCode, t.Label, t.Description));
|
|
418
|
+
}
|
|
419
|
+
// Repeat for each module...
|
|
420
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
424
|
+
{
|
|
425
|
+
var exists = await context.Permissions
|
|
426
|
+
.AnyAsync(p => p.Path == "{contextCode}.{appCode}.*", ct);
|
|
427
|
+
if (exists) return;
|
|
428
|
+
|
|
429
|
+
// Application-level wildcard
|
|
430
|
+
var appWildcard = Permission.CreateWildcard(
|
|
431
|
+
"{contextCode}.{appCode}.*", PermissionLevel.Application,
|
|
432
|
+
"Full {appLabel_en} access");
|
|
433
|
+
context.Permissions.Add(appWildcard);
|
|
434
|
+
|
|
435
|
+
// Module permissions
|
|
436
|
+
var mod1 = await context.NavigationModules
|
|
437
|
+
.FirstAsync(m => m.Code == "{module1Code}", ct);
|
|
438
|
+
foreach (var entry in {Module1Pascal}PermissionsSeedData.GetPermissionEntries(mod1.Id))
|
|
439
|
+
{
|
|
440
|
+
var perm = entry.IsWildcard
|
|
441
|
+
? Permission.CreateWildcard(entry.Path, entry.Level, entry.Description)
|
|
442
|
+
: Permission.CreateForModule(entry.Path, entry.Level, entry.Action, false, entry.ModuleId, entry.Description);
|
|
443
|
+
context.Permissions.Add(perm);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Repeat for each module...
|
|
447
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
451
|
+
{
|
|
452
|
+
var exists = await context.RolePermissions
|
|
453
|
+
.AnyAsync(rp => rp.Permission!.Path.StartsWith("{contextCode}.{appCode}."), ct);
|
|
454
|
+
if (exists) return;
|
|
455
|
+
|
|
456
|
+
// Resolve roles by Code
|
|
457
|
+
var roles = await context.Roles
|
|
458
|
+
.Where(r => r.ApplicationId != null || r.IsSystem)
|
|
459
|
+
.ToListAsync(ct);
|
|
460
|
+
|
|
461
|
+
// Resolve permissions
|
|
462
|
+
var permissions = await context.Permissions
|
|
463
|
+
.Where(p => p.Path.StartsWith("{contextCode}.{appCode}."))
|
|
464
|
+
.ToListAsync(ct);
|
|
465
|
+
|
|
466
|
+
// Apply role-permission mappings from all modules
|
|
467
|
+
var allMappings = new List<RolePermissionSeedEntry>();
|
|
468
|
+
allMappings.AddRange({Module1Pascal}RolesSeedData.GetRolePermissionEntries());
|
|
469
|
+
// allMappings.AddRange({Module2Pascal}RolesSeedData.GetRolePermissionEntries()); // per module
|
|
470
|
+
|
|
471
|
+
foreach (var mapping in allMappings)
|
|
472
|
+
{
|
|
473
|
+
var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode);
|
|
474
|
+
var perm = permissions.FirstOrDefault(p => p.Path == mapping.PermissionPath);
|
|
475
|
+
if (role != null && perm != null)
|
|
476
|
+
{
|
|
477
|
+
context.RolePermissions.Add(RolePermission.Create(role.Id, perm.Id, "system"));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### DI Registration
|
|
487
|
+
|
|
488
|
+
```csharp
|
|
489
|
+
// In Infrastructure/DependencyInjection.cs — add:
|
|
490
|
+
using SmartStack.Application.Common.Interfaces.Seeding;
|
|
491
|
+
|
|
492
|
+
// In the registration method:
|
|
493
|
+
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## 6. Multi-Module Handling
|
|
499
|
+
|
|
500
|
+
When processing multiple modules in the same ralph-loop run:
|
|
501
|
+
|
|
502
|
+
### Module 1 (first): Creates everything from scratch
|
|
503
|
+
|
|
504
|
+
1. `{Module1}NavigationSeedData.cs`
|
|
505
|
+
2. `{Module1}PermissionsSeedData.cs`
|
|
506
|
+
3. `{Module1}RolesSeedData.cs`
|
|
507
|
+
4. `{AppPascalName}SeedDataProvider.cs` (new)
|
|
508
|
+
5. DI registration (new)
|
|
509
|
+
|
|
510
|
+
### Module 2+ (subsequent): Append to existing provider
|
|
511
|
+
|
|
512
|
+
1. `{Module2}NavigationSeedData.cs` (new file)
|
|
513
|
+
2. `{Module2}PermissionsSeedData.cs` (new file)
|
|
514
|
+
3. `{Module2}RolesSeedData.cs` (new file)
|
|
515
|
+
4. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in each Seed method)
|
|
516
|
+
5. DI registration (already exists — skip)
|
|
517
|
+
|
|
518
|
+
**Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to each of the 3 methods.
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 7. Verification Checklist (BLOCKING)
|
|
523
|
+
|
|
524
|
+
Before marking the task as completed, verify ALL:
|
|
525
|
+
|
|
526
|
+
- [ ] Deterministic GUIDs (NEVER `Guid.NewGuid()`) — SHA256 of path
|
|
527
|
+
- [ ] 4 languages for each navigation entity (fr, en, it, de)
|
|
528
|
+
- [ ] `Permissions.cs` constants match seed data paths
|
|
529
|
+
- [ ] MCP `generate_permissions` called (or fallback used)
|
|
530
|
+
- [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
|
|
531
|
+
- [ ] `IClientSeedDataProvider` generated with 3 methods
|
|
532
|
+
- [ ] Each Seed method is idempotent (checks existence before inserting)
|
|
533
|
+
- [ ] Factory methods used throughout (NEVER `new Entity()`)
|
|
534
|
+
- [ ] `SaveChangesAsync` called per group (Navigation → Permissions → RolePermissions)
|
|
535
|
+
- [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
|
|
536
|
+
- [ ] `dotnet build` passes after generation
|
|
537
|
+
|
|
538
|
+
**If ANY check fails, the task status = 'failed'.**
|
|
@@ -14,6 +14,8 @@ next_step: steps/step-01-task.md
|
|
|
14
14
|
- YOU ARE AN INITIALIZER, not an executor
|
|
15
15
|
- FORBIDDEN to load step-01 until init is complete
|
|
16
16
|
- ALWAYS check prd.json version is v2
|
|
17
|
+
- **CONTEXT BUDGET**: Keep init output COMPACT. Do NOT dump verbose MCP responses or long file listings. Every token saved here = more tokens for actual code generation in the COMPACT LOOP.
|
|
18
|
+
- **NEVER DELEGATE**: Do NOT use the Task tool to delegate the Ralph loop to a sub-agent. The loop MUST run in the main agent context. Sub-agents lose skill context and stop prematurely.
|
|
17
19
|
|
|
18
20
|
## YOUR TASK:
|
|
19
21
|
|
|
@@ -499,7 +499,19 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
|
499
499
|
"src/Infrastructure/Persistence/Seeding/Data/SeedConstants.cs"
|
|
500
500
|
], modified: [] },
|
|
501
501
|
validation: null, error: null, module: moduleCode,
|
|
502
|
-
_seedDataMeta: {
|
|
502
|
+
_seedDataMeta: {
|
|
503
|
+
source: "guardrail-derived",
|
|
504
|
+
coreSeedData,
|
|
505
|
+
navRoute: prdJson.project?.navRoute
|
|
506
|
+
|| (permissions?.[0]?.path?.split('.').slice(0, -1).join('.'))
|
|
507
|
+
|| `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
|
|
508
|
+
contextCode: prdJson.project?.context
|
|
509
|
+
|| coreSeedData.navigationModules?.[0]?.route?.split('/')?.[1]
|
|
510
|
+
|| 'business',
|
|
511
|
+
appCode: prdJson.project?.application
|
|
512
|
+
|| coreSeedData.navigationModules?.[0]?.route?.split('/')?.[2],
|
|
513
|
+
appLabels: prdJson.project?.labels || null
|
|
514
|
+
}
|
|
503
515
|
});
|
|
504
516
|
lastIdByCategory["infrastructure"] = taskId;
|
|
505
517
|
taskId++;
|
|
@@ -530,7 +542,18 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
|
530
542
|
].join("; "),
|
|
531
543
|
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
532
544
|
files_changed: { created: ["src/Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs"], modified: ["src/Infrastructure/DependencyInjection.cs"] },
|
|
533
|
-
validation: null, error: null, module: moduleCode
|
|
545
|
+
validation: null, error: null, module: moduleCode,
|
|
546
|
+
_providerMeta: {
|
|
547
|
+
consumesSeedDataFiles: [
|
|
548
|
+
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/NavigationModuleSeedData.cs`,
|
|
549
|
+
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/PermissionsSeedData.cs`,
|
|
550
|
+
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/RolesSeedData.cs`
|
|
551
|
+
],
|
|
552
|
+
navRoute: prdJson.project?.navRoute
|
|
553
|
+
|| `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
|
|
554
|
+
contextCode: prdJson.project?.context || 'business',
|
|
555
|
+
appCode: prdJson.project?.application
|
|
556
|
+
}
|
|
534
557
|
});
|
|
535
558
|
lastIdByCategory["infrastructure"] = taskId;
|
|
536
559
|
taskId++;
|
|
@@ -435,15 +435,30 @@ A validation task is ONLY complete when:
|
|
|
435
435
|
|
|
436
436
|
When executing `infrastructure` category tasks, follow these rules strictly:
|
|
437
437
|
|
|
438
|
-
1. **
|
|
438
|
+
1. **Core seed data (navigation, permissions, roles) — CONDITIONAL REFERENCE LOADING:**
|
|
439
|
+
|
|
440
|
+
> **IF** the current task description contains "seed data", "SeedData",
|
|
441
|
+
> "NavigationModule", "PermissionsSeedData", "RolesSeedData", or "IClientSeedDataProvider":
|
|
442
|
+
> **THEN read `references/core-seed-data.md`** for COMPLETE execution guidance including:
|
|
443
|
+
> - Parameter extraction from PRD seedDataCore / task `_seedDataMeta`
|
|
444
|
+
> - MCP `generate_permissions` call sequence (primary) with fallback templates
|
|
445
|
+
> - `NavigationModuleSeedData.cs` template (deterministic GUIDs, 4 languages)
|
|
446
|
+
> - `PermissionsSeedData.cs` template (wraps MCP output)
|
|
447
|
+
> - `RolesSeedData.cs` template (context-based role mapping)
|
|
448
|
+
> - `IClientSeedDataProvider` complete implementation template
|
|
449
|
+
> - DI registration pattern
|
|
450
|
+
> - Verification checklist (BLOCKING)
|
|
451
|
+
>
|
|
452
|
+
> **This reference is MANDATORY for seed data tasks. Do NOT improvise.**
|
|
453
|
+
|
|
454
|
+
**Directory convention:** All seed data files MUST go under `Infrastructure/Persistence/Seeding/Data/{Module}/`
|
|
439
455
|
- NEVER use `Infrastructure/Data/SeedData/` or `Data/SeedData/`
|
|
440
|
-
- Each module gets 5 core files: NavigationModuleSeedData.cs, PermissionsSeedData.cs, RolesSeedData.cs, TenantSeedData.cs, UserSeedData.cs
|
|
441
456
|
|
|
442
457
|
2. **IClientSeedDataProvider** (client projects with `extensions` DbContext):
|
|
443
458
|
- Generate at `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
|
|
444
459
|
- Register in DI: `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()`
|
|
445
460
|
- Must implement: SeedNavigationAsync, SeedPermissionsAsync, SeedRolePermissionsAsync
|
|
446
|
-
-
|
|
461
|
+
- **Complete template and rules in `references/core-seed-data.md`**
|
|
447
462
|
|
|
448
463
|
3. **DevDataSeeder** for domain entity seed data:
|
|
449
464
|
- Located at `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`
|
|
@@ -514,15 +529,29 @@ Only fr/en translations → MUST have 4 languages (fr, en,
|
|
|
514
529
|
| `business.*` | `BusinessLayout` | `/business` |
|
|
515
530
|
| `personal.*` | `UserLayout` | `/personal/myspace` |
|
|
516
531
|
|
|
517
|
-
**
|
|
532
|
+
**Backend folder hierarchy (MANDATORY for ALL layers):**
|
|
518
533
|
|
|
519
|
-
|
|
534
|
+
> **CRITICAL:** ALL backend files MUST be organized by `{ContextPascal}/{ApplicationName}/{ModuleName}/` hierarchy.
|
|
535
|
+
> Derive from PRD `project.application` and feature.json `metadata.context`.
|
|
536
|
+
> The file paths in prd.json already include this hierarchy — follow them exactly.
|
|
520
537
|
|
|
521
|
-
|
|
522
|
-
- Path: `Api/Controllers/{ContextShort}/{Application}/{EntityName}Controller.cs`
|
|
523
|
-
- If no application sub-level: `Api/Controllers/{ContextShort}/{EntityName}Controller.cs`
|
|
538
|
+
When executing `domain`, `application`, `infrastructure`, `api`, or `test` category tasks:
|
|
524
539
|
|
|
525
|
-
|
|
540
|
+
1. **ALL layers use context/application/module folder hierarchy:**
|
|
541
|
+
|
|
542
|
+
| Layer | Path Pattern |
|
|
543
|
+
|-------|-------------|
|
|
544
|
+
| Domain | `Domain/Entities/{ContextPascal}/{App}/{Module}/{Entity}.cs` |
|
|
545
|
+
| Domain Enums | `Domain/Enums/{ContextPascal}/{App}/{Module}/{Enum}.cs` |
|
|
546
|
+
| Domain Exceptions | `Domain/Exceptions/{ContextPascal}/{App}/{Module}/{Exception}.cs` |
|
|
547
|
+
| Application Services | `Application/Services/{ContextPascal}/{App}/{Module}/{Service}.cs` |
|
|
548
|
+
| Application DTOs | `Application/DTOs/{ContextPascal}/{App}/{Module}/{Dto}.cs` |
|
|
549
|
+
| Application Validators | `Application/Validators/{ContextPascal}/{App}/{Module}/{Validator}.cs` |
|
|
550
|
+
| Infrastructure Configs | `Infrastructure/Persistence/Configurations/{ContextPascal}/{App}/{Module}/{Config}.cs` |
|
|
551
|
+
| API Controllers | `Api/Controllers/{ContextShort}/{App}/{Entity}Controller.cs` |
|
|
552
|
+
| Tests | `Tests/Unit/Domain/{ContextPascal}/{App}/{Module}/{Tests}.cs` |
|
|
553
|
+
|
|
554
|
+
2. **Controller context-to-folder mapping (`{ContextShort}`):**
|
|
526
555
|
|
|
527
556
|
| NavRoute Prefix | Controller Folder |
|
|
528
557
|
|-----------------|-------------------|
|
|
@@ -531,12 +560,7 @@ When executing `api` category tasks, follow these rules strictly:
|
|
|
531
560
|
| `business.*` | `Business` |
|
|
532
561
|
| `personal.*` | `User` |
|
|
533
562
|
|
|
534
|
-
3. **
|
|
535
|
-
- `Admin/Tenants/` for tenant-related controllers
|
|
536
|
-
- `Admin/AI/` for AI-related controllers
|
|
537
|
-
- `Admin/Communications/` for communication controllers
|
|
538
|
-
|
|
539
|
-
4. **Reference:** SmartStack.app `Api/Controllers/` for the canonical structure
|
|
563
|
+
3. **Reference:** SmartStack.app `Api/Controllers/` for the canonical structure
|
|
540
564
|
|
|
541
565
|
### 4. Explore Context (if needed)
|
|
542
566
|
|