@atlashub/smartstack-cli 3.4.1 → 3.5.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 +147 -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/_architecture.md +3 -3
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +43 -0
- package/templates/skills/business-analyse/templates/tpl-handoff.md +6 -6
- package/templates/skills/ralph-loop/steps/step-01-task.md +249 -6
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ Domain → Application → Infrastructure → API → Web
|
|
|
21
21
|
|-------|-----------------|-----------|------------------|
|
|
22
22
|
| Domain | Entities, ValueObjects | `SmartStack.Domain.{Context}.{App}.{Module}` | `Domain/{Context}/{App}/{Module}/` |
|
|
23
23
|
| Application | Interfaces (Common), DTOs | `SmartStack.Application.Common.Interfaces` / `.{Context}.{App}.{Module}.DTOs` | `Application/Common/Interfaces/` + `Application/{Context}/{App}/{Module}/DTOs/` |
|
|
24
|
-
| Infrastructure | Service Impl, EF Core | `SmartStack.Infrastructure.Services.{Module}` | `Infrastructure/Services/{Module}/` + `Persistence/Configurations/{Module}/` |
|
|
24
|
+
| Infrastructure | Service Impl, EF Core | `SmartStack.Infrastructure.Services.{Context}.{App}.{Module}` | `Infrastructure/Services/{Context}/{App}/{Module}/` + `Persistence/Configurations/{Context}/{App}/{Module}/` |
|
|
25
25
|
| API | Controllers, Middleware | `SmartStack.Api.Controllers.{Context}` | `Api/Controllers/{Context}/` |
|
|
26
26
|
| Web | React components, pages | N/A | `pages/{context}/{app}/{module}/` + `components/{context}/{module}/` |
|
|
27
27
|
|
|
@@ -86,9 +86,9 @@ Args:
|
|
|
86
86
|
|
|
87
87
|
The tool generates (paths organized by navRoute hierarchy `{context}.{application}.{module}`):
|
|
88
88
|
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs` - Entity with BaseEntity
|
|
89
|
-
- `Infrastructure/Persistence/Configurations/{Module}/{EntityName}Configuration.cs` - EF Config
|
|
89
|
+
- `Infrastructure/Persistence/Configurations/{Context}/{Application}/{Module}/{EntityName}Configuration.cs` - EF Config
|
|
90
90
|
- `Application/Common/Interfaces/I{EntityName}Service.cs` - Service Interface
|
|
91
|
-
- `Infrastructure/Services/{Module}/{EntityName}Service.cs` - Service Implementation
|
|
91
|
+
- `Infrastructure/Services/{Context}/{Application}/{Module}/{EntityName}Service.cs` - Service Implementation
|
|
92
92
|
- `Api/Controllers/{controller_folder}/{Application}/{EntityName}Controller.cs` - REST Controller
|
|
93
93
|
- `Application/{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs` - Response DTO
|
|
94
94
|
- `Application/{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs` - Create request
|
|
@@ -103,8 +103,8 @@ The tool generates (paths organized by navRoute hierarchy `{context}.{applicatio
|
|
|
103
103
|
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs`
|
|
104
104
|
|
|
105
105
|
### Infrastructure Layer
|
|
106
|
-
- `Persistence/Configurations/{Module}/{EntityName}Configuration.cs`
|
|
107
|
-
- `Services/{Module}/{EntityName}Service.cs`
|
|
106
|
+
- `Persistence/Configurations/{Context}/{Application}/{Module}/{EntityName}Configuration.cs`
|
|
107
|
+
- `Services/{Context}/{Application}/{Module}/{EntityName}Service.cs`
|
|
108
108
|
|
|
109
109
|
### Application Layer
|
|
110
110
|
- `Common/Interfaces/I{EntityName}Service.cs`
|
|
@@ -83,7 +83,7 @@ public interface I$MODULE_PASCALService
|
|
|
83
83
|
## TEMPLATE: SERVICE IMPLEMENTATION
|
|
84
84
|
|
|
85
85
|
```csharp
|
|
86
|
-
// src/SmartStack.Infrastructure/Services/$MODULE_PASCAL/$MODULE_PASCALService.cs
|
|
86
|
+
// src/SmartStack.Infrastructure/Services/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/$MODULE_PASCALService.cs
|
|
87
87
|
|
|
88
88
|
using Microsoft.EntityFrameworkCore;
|
|
89
89
|
using Microsoft.Extensions.Logging;
|
|
@@ -91,7 +91,7 @@ using SmartStack.Application.Common.Interfaces;
|
|
|
91
91
|
using SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
|
|
92
92
|
using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
93
93
|
|
|
94
|
-
namespace SmartStack.Infrastructure.Services.$MODULE_PASCAL;
|
|
94
|
+
namespace SmartStack.Infrastructure.Services.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
95
95
|
|
|
96
96
|
public class $MODULE_PASCALService : I$MODULE_PASCALService
|
|
97
97
|
{
|
|
@@ -359,13 +359,13 @@ public class $ENTITY_PASCAL : BaseEntity
|
|
|
359
359
|
## TEMPLATE: EF CONFIGURATION
|
|
360
360
|
|
|
361
361
|
```csharp
|
|
362
|
-
// src/SmartStack.Infrastructure/Persistence/Configurations/$MODULE_PASCAL/$ENTITY_PASCALConfiguration.cs
|
|
362
|
+
// src/SmartStack.Infrastructure/Persistence/Configurations/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/$ENTITY_PASCALConfiguration.cs
|
|
363
363
|
|
|
364
364
|
using Microsoft.EntityFrameworkCore;
|
|
365
365
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
366
366
|
using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
367
367
|
|
|
368
|
-
namespace SmartStack.Infrastructure.Persistence.Configurations.$MODULE_PASCAL;
|
|
368
|
+
namespace SmartStack.Infrastructure.Persistence.Configurations.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
369
369
|
|
|
370
370
|
public class $ENTITY_PASCALConfiguration : IEntityTypeConfiguration<$ENTITY_PASCAL>
|
|
371
371
|
{
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
| **Domain** | `{Project}.Domain/Business/{Application}/{Module}/` | `Domain/Business/Operations/FreeBicke/FreeBicke.cs` |
|
|
64
64
|
| **Application/DTOs** | `{Project}.Application/Business/{Application}/{Module}/DTOs/` | `Application/Business/Operations/FreeBicke/DTOs/FreeBickeDto.cs` |
|
|
65
65
|
| **Application/Interfaces** | `{Project}.Application/Common/Interfaces/` | `Application/Common/Interfaces/IFreeBickeService.cs` |
|
|
66
|
-
| **Infrastructure/Config** | `{Project}.Infrastructure/Persistence/Configurations/{Module}/` | `Configurations/FreeBicke/FreeBickeConfiguration.cs` |
|
|
67
|
-
| **Infrastructure/Services** | `{Project}.Infrastructure/Services/{Module}/` | `Services/FreeBicke/FreeBickeService.cs` |
|
|
68
|
-
| **Infrastructure/SeedData** | `{Project}.Infrastructure/Persistence/Seeding/Data/{Module}/` | `Seeding/Data/FreeBicke/FreeBickeSeedData.cs` |
|
|
66
|
+
| **Infrastructure/Config** | `{Project}.Infrastructure/Persistence/Configurations/{Context}/{Application}/{Module}/` | `Configurations/Business/Operations/FreeBicke/FreeBickeConfiguration.cs` |
|
|
67
|
+
| **Infrastructure/Services** | `{Project}.Infrastructure/Services/{Context}/{Application}/{Module}/` | `Services/Business/Operations/FreeBicke/FreeBickeService.cs` |
|
|
68
|
+
| **Infrastructure/SeedData** | `{Project}.Infrastructure/Persistence/Seeding/Data/{Context}/{Application}/{Module}/` | `Seeding/Data/Business/Operations/FreeBicke/FreeBickeSeedData.cs` |
|
|
69
69
|
| **API/Controllers** | `{Project}.Api/Controllers/Business/{Application}/` | `Controllers/Business/Operations/FreeBickeController.cs` |
|
|
70
70
|
| **Frontend/Pages** | `web/src/pages/business/{application}/{module}/` | `pages/business/operations/freebicke/page.tsx` |
|
|
71
71
|
| **Frontend/i18n** | `web/src/i18n/locales/{lang}/{module}.json` | `locales/fr/freebicke.json` |
|
|
@@ -78,6 +78,49 @@ prd.json generated for module {moduleCode}:
|
|
|
78
78
|
- prd.json version: "2.0.0"
|
|
79
79
|
- source.type: "ba-handoff-programmatic"
|
|
80
80
|
|
|
81
|
+
**POST-CHECK (BLOCKING — DO NOT SKIP):**
|
|
82
|
+
|
|
83
|
+
After `ss derive-prd` execution, read the generated prd-{moduleCode}.json and verify structural integrity:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
const prd = readJSON(`.ralph/prd-${moduleCode}.json`);
|
|
87
|
+
const featureHandoff = moduleFeature.handoff.filesToCreate;
|
|
88
|
+
|
|
89
|
+
// 1. Verify structure: filesToCreate MUST be under implementation (NOT root level)
|
|
90
|
+
if (prd.filesToCreate && !prd.implementation?.filesToCreate) {
|
|
91
|
+
BLOCKING_ERROR("prd.json has filesToCreate at ROOT level — this is NOT from ss derive-prd");
|
|
92
|
+
BLOCKING_ERROR("Re-run: ss derive-prd --feature {path} --output .ralph/prd-{moduleCode}.json");
|
|
93
|
+
STOP;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. Verify ALL 7 categories present and match feature.json
|
|
97
|
+
const categories = ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests'];
|
|
98
|
+
for (const cat of categories) {
|
|
99
|
+
const prdCount = prd.implementation.filesToCreate[cat]?.length ?? 0;
|
|
100
|
+
const featureCount = featureHandoff[cat]?.length ?? 0;
|
|
101
|
+
if (prdCount !== featureCount) {
|
|
102
|
+
BLOCKING_ERROR(`${cat}: prd has ${prdCount} files but feature.json has ${featureCount}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Display verification table:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
POST-CHECK: prd-{moduleCode}.json integrity
|
|
111
|
+
| Category | feature.json | prd.json | Match |
|
|
112
|
+
|----------------|-------------|----------|-------|
|
|
113
|
+
| domain | {n} | {n} | OK/FAIL |
|
|
114
|
+
| application | {n} | {n} | OK/FAIL |
|
|
115
|
+
| infrastructure | {n} | {n} | OK/FAIL |
|
|
116
|
+
| api | {n} | {n} | OK/FAIL |
|
|
117
|
+
| frontend | {n} | {n} | OK/FAIL |
|
|
118
|
+
| seedData | {n} | {n} | OK/FAIL |
|
|
119
|
+
| tests | {n} | {n} | OK/FAIL |
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
IF ANY category shows FAIL → **STOP AND RE-RUN `ss derive-prd`**. DO NOT proceed with incomplete PRD.
|
|
123
|
+
|
|
81
124
|
---
|
|
82
125
|
|
|
83
126
|
### 2. Initialize Progress Tracker
|
|
@@ -47,8 +47,8 @@ BACKEND:
|
|
|
47
47
|
- src/{project_namespace}.Domain/Business/ -> Entity structure + folder hierarchy
|
|
48
48
|
- src/{project_namespace}.Application/Business/ -> DTOs + service interfaces
|
|
49
49
|
- src/{project_namespace}.Application/Common/Interfaces/ -> Service contracts
|
|
50
|
-
- src/{project_namespace}.Infrastructure/Persistence/Configurations/ -> EF Core configs (subfolder per module)
|
|
51
|
-
- src/{project_namespace}.Infrastructure/Services/ -> Service implementations (subfolder per module)
|
|
50
|
+
- src/{project_namespace}.Infrastructure/Persistence/Configurations/ -> EF Core configs (subfolder per context/app/module)
|
|
51
|
+
- src/{project_namespace}.Infrastructure/Services/ -> Service implementations (subfolder per context/app/module)
|
|
52
52
|
- src/{project_namespace}.Infrastructure/Persistence/Seeding/Data/ -> SeedData patterns
|
|
53
53
|
|
|
54
54
|
SEED DATA (CRITICAL):
|
|
@@ -76,8 +76,8 @@ FRONTEND:
|
|
|
76
76
|
| DTOs | `Application/Business/{Application}/{Module}/DTOs/{Entity}Dto.cs` | Response, Create, Update records |
|
|
77
77
|
| Mapping | `Application/Business/{Application}/{Module}/DTOs/{Entity}MappingExtensions.cs` | Entity <-> DTO mapping |
|
|
78
78
|
| Permission Constants | `Application/Business/{Application}/{Module}/Permissions.cs` | Compile-time constants |
|
|
79
|
-
| EF Configuration | `Infrastructure/Persistence/Configurations/{Module}/{Entity}Configuration.cs` | Table + indexes + HasData |
|
|
80
|
-
| Service Implementation | `Infrastructure/Services/{Module}/{Entity}Service.cs` | Business logic |
|
|
79
|
+
| EF Configuration | `Infrastructure/Persistence/Configurations/{Context}/{Application}/{Module}/{Entity}Configuration.cs` | Table + indexes + HasData |
|
|
80
|
+
| Service Implementation | `Infrastructure/Services/{Context}/{Application}/{Module}/{Entity}Service.cs` | Business logic |
|
|
81
81
|
| Controller | `Api/Controllers/Business/{Application}/{Module}Controller.cs` | NavRoute + RequirePermission |
|
|
82
82
|
|
|
83
83
|
### 3.2 SeedData Core (CRITICAL -- without these, module is invisible and returns 403)
|
|
@@ -156,8 +156,8 @@ From: `feature.specification.seedDataCore` (5 core files), `feature.specificatio
|
|
|
156
156
|
**Folder Conventions:**
|
|
157
157
|
- [ ] Domain entities in `Domain/Business/{Application}/{Module}/`
|
|
158
158
|
- [ ] Application DTOs in `Application/Business/{Application}/{Module}/DTOs/`
|
|
159
|
-
- [ ] Infrastructure configs in `Configurations/{Module}/`
|
|
160
|
-
- [ ] Infrastructure services in `Services/{Module}/`
|
|
159
|
+
- [ ] Infrastructure configs in `Configurations/{Context}/{Application}/{Module}/`
|
|
160
|
+
- [ ] Infrastructure services in `Services/{Context}/{Application}/{Module}/`
|
|
161
161
|
- [ ] Controller in `Controllers/Business/{Application}/`
|
|
162
162
|
|
|
163
163
|
**SeedData Core (CRITICAL):**
|
|
@@ -136,7 +136,10 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
|
136
136
|
{ key: "tests", category: "test" }
|
|
137
137
|
];
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
// Support BOTH formats: nested (from ss derive-prd) and flat (LLM-generated)
|
|
140
|
+
const filesToCreate = prdJson.implementation?.filesToCreate
|
|
141
|
+
|| prdJson.filesToCreate
|
|
142
|
+
|| {};
|
|
140
143
|
|
|
141
144
|
// 1. Generate tasks from implementation.filesToCreate (primary source)
|
|
142
145
|
// CRITICAL: Frontend and i18n tasks are CONSOLIDATED into ONE module-level task each.
|
|
@@ -175,15 +178,23 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
// Build acceptance criteria from linked FRs and UCs
|
|
181
|
+
// Support both nested (requirements.functionalRequirements) and flat (functionalRequirements) formats
|
|
182
|
+
const allFRs = prdJson.requirements?.functionalRequirements || prdJson.functionalRequirements || [];
|
|
178
183
|
const linkedFRs = (fileSpec.linkedFRs || [])
|
|
179
184
|
.map(frId => {
|
|
180
|
-
const fr =
|
|
185
|
+
const fr = allFRs.find(f => f.id === frId);
|
|
181
186
|
return fr ? fr.statement : frId;
|
|
182
187
|
});
|
|
183
|
-
|
|
188
|
+
let criteria = linkedFRs.length > 0
|
|
184
189
|
? `Implements: ${linkedFRs.join("; ")}`
|
|
185
190
|
: `File ${fileSpec.path} created and compiles correctly`;
|
|
186
191
|
|
|
192
|
+
// Enhance acceptance criteria for API tasks with permission enforcement
|
|
193
|
+
const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
|
|
194
|
+
if (layer.category === "api" && permMatrix?.permissions?.length > 0) {
|
|
195
|
+
criteria += "; MANDATORY: [RequirePermission] attributes on every endpoint (NOT [Authorize])";
|
|
196
|
+
}
|
|
197
|
+
|
|
187
198
|
tasks.push({
|
|
188
199
|
id: taskId,
|
|
189
200
|
description: fileSpec.description
|
|
@@ -273,18 +284,250 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
|
273
284
|
taskId++;
|
|
274
285
|
}
|
|
275
286
|
|
|
287
|
+
// 1d. GUARDRAIL: Inject missing layer tasks when filesToCreate is incomplete
|
|
288
|
+
// This handles LLM-generated PRDs that omit layers present in the source data.
|
|
289
|
+
// Uses architecture data (entities, apiEndpoints, sections, wireframes) as fallback.
|
|
290
|
+
|
|
291
|
+
const entities = prdJson.architecture?.entities || prdJson.entities || [];
|
|
292
|
+
const apiEndpoints = prdJson.architecture?.apiEndpoints || prdJson.apiEndpoints || [];
|
|
293
|
+
const sections = prdJson.architecture?.sections || prdJson.sections || [];
|
|
294
|
+
const wireframes = prdJson.specification?.uiWireframes || prdJson.uiWireframes || [];
|
|
295
|
+
const dashboards = prdJson.specification?.dashboards || prdJson.dashboards || [];
|
|
296
|
+
|
|
297
|
+
const hasDomainTasks = tasks.some(t => t.category === 'domain');
|
|
298
|
+
const hasInfraTasks = tasks.some(t => t.category === 'infrastructure');
|
|
299
|
+
const hasApiTasks = tasks.some(t => t.category === 'api');
|
|
300
|
+
const hasFrontendTasks = frontendFiles.length > 0; // Already handled in 1b
|
|
301
|
+
const hasTestTasks = tasks.some(t => t.category === 'test');
|
|
302
|
+
|
|
303
|
+
// INJECT consolidated infrastructure task if missing
|
|
304
|
+
if (!hasInfraTasks && entities.length > 0) {
|
|
305
|
+
const domainDepId = lastIdByCategory["domain"];
|
|
306
|
+
tasks.push({
|
|
307
|
+
id: taskId,
|
|
308
|
+
description: `[infrastructure] Create EF Core configurations, services, and seed data for module ${moduleCode} (${entities.length} entities)`,
|
|
309
|
+
status: "pending",
|
|
310
|
+
category: "infrastructure",
|
|
311
|
+
dependencies: domainDepId ? [domainDepId] : [],
|
|
312
|
+
acceptance_criteria: `EF Core configs for all ${entities.length} entities; Service implementations; DbSet in ExtensionsDbContext; DI registration; Seed data`,
|
|
313
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
314
|
+
files_changed: { created: [], modified: [] },
|
|
315
|
+
validation: null, error: null, module: moduleCode
|
|
316
|
+
});
|
|
317
|
+
lastIdByCategory["infrastructure"] = taskId;
|
|
318
|
+
taskId++;
|
|
319
|
+
console.log(`⚠️ GUARDRAIL: Injected missing [infrastructure] task from ${entities.length} entities`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// INJECT consolidated API task if missing
|
|
323
|
+
if (!hasApiTasks && apiEndpoints.length > 0) {
|
|
324
|
+
const appDepId = lastIdByCategory["application"] || lastIdByCategory["infrastructure"];
|
|
325
|
+
const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
|
|
326
|
+
const hasPermissions = permMatrix?.permissions?.length > 0;
|
|
327
|
+
|
|
328
|
+
tasks.push({
|
|
329
|
+
id: taskId,
|
|
330
|
+
description: `[api] Create API controllers for module ${moduleCode} (${apiEndpoints.length} endpoints)`,
|
|
331
|
+
status: "pending",
|
|
332
|
+
category: "api",
|
|
333
|
+
dependencies: appDepId ? [appDepId] : [],
|
|
334
|
+
acceptance_criteria: [
|
|
335
|
+
`Controllers for all ${apiEndpoints.length} endpoints`,
|
|
336
|
+
hasPermissions
|
|
337
|
+
? "MANDATORY: [RequirePermission(Permissions.{Action})] on EVERY endpoint (NOT generic [Authorize])"
|
|
338
|
+
: "Authorization attributes",
|
|
339
|
+
"Swagger docs"
|
|
340
|
+
].join("; "),
|
|
341
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
342
|
+
files_changed: { created: [], modified: [] },
|
|
343
|
+
validation: null, error: null, module: moduleCode
|
|
344
|
+
});
|
|
345
|
+
lastIdByCategory["api"] = taskId;
|
|
346
|
+
taskId++;
|
|
347
|
+
console.log(`⚠️ GUARDRAIL: Injected missing [api] task from ${apiEndpoints.length} endpoints`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// CRITICAL: INJECT consolidated frontend task if missing
|
|
351
|
+
// This is the MOST COMMON failure mode — LLM-generated PRDs often omit frontend
|
|
352
|
+
if (!hasFrontendTasks && (sections.length > 0 || wireframes.length > 0 || apiEndpoints.length > 0)) {
|
|
353
|
+
const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
|
|
354
|
+
|
|
355
|
+
// Derive frontend file list from available data sources
|
|
356
|
+
const derivedFrontendFiles = [];
|
|
357
|
+
|
|
358
|
+
// From wireframes → pages
|
|
359
|
+
for (const wf of wireframes) {
|
|
360
|
+
const screenId = wf.screen || wf.id;
|
|
361
|
+
derivedFrontendFiles.push({
|
|
362
|
+
path: `web/src/pages/${moduleCode}/${screenId}.tsx`,
|
|
363
|
+
type: screenId.includes('dashboard') ? 'DashboardPage' : 'Page',
|
|
364
|
+
linkedWireframes: [screenId],
|
|
365
|
+
linkedUCs: wf.linkedUCs || [],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// From dashboards → dashboard pages (if not already from wireframes)
|
|
370
|
+
for (const dash of dashboards) {
|
|
371
|
+
const dashId = dash.code || dash.id;
|
|
372
|
+
if (!derivedFrontendFiles.some(f => f.path.includes(dashId))) {
|
|
373
|
+
derivedFrontendFiles.push({
|
|
374
|
+
path: `web/src/pages/${moduleCode}/${dashId}.tsx`,
|
|
375
|
+
type: 'DashboardPage',
|
|
376
|
+
linkedWireframes: [dashId],
|
|
377
|
+
dashboardRef: dashId,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// From API endpoints → API client service
|
|
383
|
+
if (apiEndpoints.length > 0) {
|
|
384
|
+
derivedFrontendFiles.push({
|
|
385
|
+
path: `web/src/services/api/${moduleCode}Api.ts`,
|
|
386
|
+
type: 'ApiService',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const allWireframeIds = derivedFrontendFiles.flatMap(f => f.linkedWireframes || []);
|
|
391
|
+
|
|
392
|
+
tasks.push({
|
|
393
|
+
id: taskId,
|
|
394
|
+
description: `[frontend] Generate COMPLETE frontend for module ${moduleCode} via MCP scaffold tools (${derivedFrontendFiles.length} files: pages, components, hooks, API client)`,
|
|
395
|
+
status: "pending",
|
|
396
|
+
category: "frontend",
|
|
397
|
+
dependencies: apiDepId ? [apiDepId] : [],
|
|
398
|
+
acceptance_criteria: [
|
|
399
|
+
"MCP scaffold_api_client called → API client + types generated",
|
|
400
|
+
"MCP scaffold_routes called → routes.tsx updated with nested routes inside Layout wrapper",
|
|
401
|
+
allWireframeIds.length > 0 ? "Pages match wireframes: " + allWireframeIds.join(", ") : "Pages created for all UI sections",
|
|
402
|
+
"SmartTable for lists (NOT HTML tables), EntityCard for grids (NOT custom divs)",
|
|
403
|
+
"CSS variables ONLY (NO hardcoded Tailwind colors like bg-blue-600)",
|
|
404
|
+
"useNavigate + useParams for routing, NOT window.location",
|
|
405
|
+
"Loading/error/empty states on all pages",
|
|
406
|
+
"4-language i18n keys (fr, en, it, de)",
|
|
407
|
+
"npm run typecheck passes"
|
|
408
|
+
].join("; "),
|
|
409
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
410
|
+
files_changed: { created: derivedFrontendFiles.map(f => f.path), modified: [] },
|
|
411
|
+
validation: null, error: null, module: moduleCode,
|
|
412
|
+
_frontendMeta: {
|
|
413
|
+
wireframes: allWireframeIds,
|
|
414
|
+
filesToCreate: derivedFrontendFiles,
|
|
415
|
+
source: "guardrail-derived"
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
lastIdByCategory["frontend"] = taskId;
|
|
419
|
+
taskId++;
|
|
420
|
+
console.log(`⚠️ GUARDRAIL: Injected missing [frontend] task derived from ${wireframes.length} wireframes + ${dashboards.length} dashboards + ${apiEndpoints.length} endpoints`);
|
|
421
|
+
|
|
422
|
+
// Also inject i18n task if not already present
|
|
423
|
+
if (i18nFiles.length === 0) {
|
|
424
|
+
tasks.push({
|
|
425
|
+
id: taskId,
|
|
426
|
+
description: `[i18n] Generate i18n translations for module ${moduleCode} (4 languages: fr, en, it, de)`,
|
|
427
|
+
status: "pending",
|
|
428
|
+
category: "i18n",
|
|
429
|
+
dependencies: [lastIdByCategory["frontend"]],
|
|
430
|
+
acceptance_criteria: "4 JSON files (fr, en, it, de) with identical keys; all UI labels translated",
|
|
431
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
432
|
+
files_changed: { created: [], modified: [] },
|
|
433
|
+
validation: null, error: null, module: moduleCode
|
|
434
|
+
});
|
|
435
|
+
lastIdByCategory["i18n"] = taskId;
|
|
436
|
+
taskId++;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// INJECT consolidated test task if missing
|
|
441
|
+
if (!hasTestTasks && entities.length > 0) {
|
|
442
|
+
const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
|
|
443
|
+
tasks.push({
|
|
444
|
+
id: taskId,
|
|
445
|
+
description: `[test] Create unit and integration tests for module ${moduleCode}`,
|
|
446
|
+
status: "pending",
|
|
447
|
+
category: "test",
|
|
448
|
+
dependencies: apiDepId ? [apiDepId] : [],
|
|
449
|
+
acceptance_criteria: "Domain unit tests + service unit tests + controller integration tests; dotnet test passes; coverage >= 80%",
|
|
450
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
451
|
+
files_changed: { created: [], modified: [] },
|
|
452
|
+
validation: null, error: null, module: moduleCode
|
|
453
|
+
});
|
|
454
|
+
lastIdByCategory["test"] = taskId;
|
|
455
|
+
taskId++;
|
|
456
|
+
console.log(`⚠️ GUARDRAIL: Injected missing [test] task`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 1e. GUARDRAIL: Inject seedData tasks when core seed data exists but filesToCreate.seedData is empty
|
|
460
|
+
// This is the MOST CRITICAL guardrail — without core seed data, the application has:
|
|
461
|
+
// - No navigation menu entries
|
|
462
|
+
// - No RBAC permissions in the database
|
|
463
|
+
// - No role-permission mappings
|
|
464
|
+
// - Controllers accessible to ANY authenticated user
|
|
465
|
+
const coreSeedData = prdJson.seedData?.core
|
|
466
|
+
|| prdJson.seedDataCore
|
|
467
|
+
|| prdJson.specification?.seedDataCore;
|
|
468
|
+
const hasSeedDataTasks = (filesToCreate["seedData"]?.length ?? 0) > 0;
|
|
469
|
+
const hasSeedDataInTasks = tasks.some(t =>
|
|
470
|
+
t.description?.includes("SeedData") || t.description?.includes("seed data"));
|
|
471
|
+
|
|
472
|
+
if (coreSeedData && !hasSeedDataTasks && !hasSeedDataInTasks) {
|
|
473
|
+
const infraDepId = lastIdByCategory["infrastructure"] || lastIdByCategory["domain"];
|
|
474
|
+
|
|
475
|
+
// Derive navigation/permissions/roles from coreSeedData
|
|
476
|
+
const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
|
|
477
|
+
const permissions = coreSeedData.permissions || [];
|
|
478
|
+
const rolePerms = coreSeedData.rolePermissions || [];
|
|
479
|
+
|
|
480
|
+
// Inject consolidated core seedData task
|
|
481
|
+
tasks.push({
|
|
482
|
+
id: taskId,
|
|
483
|
+
description: `[infrastructure] Create core seed data files for module ${moduleCode}: NavigationModuleSeedData, PermissionsSeedData, RolesSeedData, SeedConstants`,
|
|
484
|
+
status: "pending",
|
|
485
|
+
category: "infrastructure",
|
|
486
|
+
dependencies: infraDepId ? [infraDepId] : [],
|
|
487
|
+
acceptance_criteria: [
|
|
488
|
+
`NavigationModuleSeedData.cs with ${navModules.length} navigation module(s)`,
|
|
489
|
+
`PermissionsSeedData.cs with ${permissions.length} permission(s) from seedData.core`,
|
|
490
|
+
`RolesSeedData.cs with ${rolePerms.length} role-permission mapping(s)`,
|
|
491
|
+
"SeedConstants.cs with deterministic GUIDs",
|
|
492
|
+
"All seed data classes are static with GetSeedData() method"
|
|
493
|
+
].join("; "),
|
|
494
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
495
|
+
files_changed: { created: [
|
|
496
|
+
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/NavigationModuleSeedData.cs`,
|
|
497
|
+
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/PermissionsSeedData.cs`,
|
|
498
|
+
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/RolesSeedData.cs`,
|
|
499
|
+
"src/Infrastructure/Persistence/Seeding/Data/SeedConstants.cs"
|
|
500
|
+
], modified: [] },
|
|
501
|
+
validation: null, error: null, module: moduleCode,
|
|
502
|
+
_seedDataMeta: { source: "guardrail-derived", coreSeedData }
|
|
503
|
+
});
|
|
504
|
+
lastIdByCategory["infrastructure"] = taskId;
|
|
505
|
+
taskId++;
|
|
506
|
+
console.log(`GUARDRAIL: Injected missing [infrastructure] core seed data task from seedData.core (${navModules.length} nav, ${permissions.length} perms, ${rolePerms.length} roles)`);
|
|
507
|
+
}
|
|
508
|
+
|
|
276
509
|
// 2. Add IClientSeedDataProvider task for client projects (ExtensionsDbContext)
|
|
277
510
|
// This is MANDATORY - without it, core seed data (navigation, permissions, roles) is dead code
|
|
278
511
|
const hasClientSeedData = filesToCreate["seedData"]?.some(f =>
|
|
279
512
|
f.type === "IClientSeedDataProvider" || f.path?.includes("SeedDataProvider"));
|
|
280
|
-
|
|
513
|
+
const hasProviderInTasks = tasks.some(t =>
|
|
514
|
+
t.description?.includes("IClientSeedDataProvider") || t.description?.includes("SeedDataProvider"));
|
|
515
|
+
|
|
516
|
+
// Trigger when: seedData files exist OR coreSeedData exists (fallback for incomplete PRDs)
|
|
517
|
+
if (!hasClientSeedData && !hasProviderInTasks &&
|
|
518
|
+
(filesToCreate["seedData"]?.length > 0 || coreSeedData)) {
|
|
281
519
|
tasks.push({
|
|
282
520
|
id: taskId,
|
|
283
|
-
description: `[infrastructure] Create IClientSeedDataProvider to inject core seed data at runtime`,
|
|
521
|
+
description: `[infrastructure] Create IClientSeedDataProvider to inject core seed data (navigation, permissions, roles) at runtime`,
|
|
284
522
|
status: "pending",
|
|
285
523
|
category: "infrastructure",
|
|
286
524
|
dependencies: [lastIdByCategory["infrastructure"] || lastIdByCategory["domain"]].filter(Boolean),
|
|
287
|
-
acceptance_criteria:
|
|
525
|
+
acceptance_criteria: [
|
|
526
|
+
"Implements IClientSeedDataProvider with SeedNavigationAsync, SeedPermissionsAsync, SeedRolePermissionsAsync",
|
|
527
|
+
"Registered in DI: services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()",
|
|
528
|
+
"All methods are idempotent (check existence before inserting)",
|
|
529
|
+
"Consumes seed data from NavigationModuleSeedData, PermissionsSeedData, RolesSeedData"
|
|
530
|
+
].join("; "),
|
|
288
531
|
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
289
532
|
files_changed: { created: ["src/Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs"], modified: ["src/Infrastructure/DependencyInjection.cs"] },
|
|
290
533
|
validation: null, error: null, module: moduleCode
|