@atlashub/smartstack-cli 3.4.1 → 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 +160 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +4 -3
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/skills/_shared.md +1 -1
- package/templates/skills/application/steps/step-04-backend.md +4 -4
- package/templates/skills/application/templates-backend.md +4 -4
- package/templates/skills/business-analyse/SKILL.md +26 -15
- package/templates/skills/business-analyse/_architecture.md +4 -4
- 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-handoff.md +6 -6
- 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 +273 -7
- 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 -432
|
@@ -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
|
|