@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
|
@@ -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,21 +284,276 @@ 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: {
|
|
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
|
+
}
|
|
515
|
+
});
|
|
516
|
+
lastIdByCategory["infrastructure"] = taskId;
|
|
517
|
+
taskId++;
|
|
518
|
+
console.log(`GUARDRAIL: Injected missing [infrastructure] core seed data task from seedData.core (${navModules.length} nav, ${permissions.length} perms, ${rolePerms.length} roles)`);
|
|
519
|
+
}
|
|
520
|
+
|
|
276
521
|
// 2. Add IClientSeedDataProvider task for client projects (ExtensionsDbContext)
|
|
277
522
|
// This is MANDATORY - without it, core seed data (navigation, permissions, roles) is dead code
|
|
278
523
|
const hasClientSeedData = filesToCreate["seedData"]?.some(f =>
|
|
279
524
|
f.type === "IClientSeedDataProvider" || f.path?.includes("SeedDataProvider"));
|
|
280
|
-
|
|
525
|
+
const hasProviderInTasks = tasks.some(t =>
|
|
526
|
+
t.description?.includes("IClientSeedDataProvider") || t.description?.includes("SeedDataProvider"));
|
|
527
|
+
|
|
528
|
+
// Trigger when: seedData files exist OR coreSeedData exists (fallback for incomplete PRDs)
|
|
529
|
+
if (!hasClientSeedData && !hasProviderInTasks &&
|
|
530
|
+
(filesToCreate["seedData"]?.length > 0 || coreSeedData)) {
|
|
281
531
|
tasks.push({
|
|
282
532
|
id: taskId,
|
|
283
|
-
description: `[infrastructure] Create IClientSeedDataProvider to inject core seed data at runtime`,
|
|
533
|
+
description: `[infrastructure] Create IClientSeedDataProvider to inject core seed data (navigation, permissions, roles) at runtime`,
|
|
284
534
|
status: "pending",
|
|
285
535
|
category: "infrastructure",
|
|
286
536
|
dependencies: [lastIdByCategory["infrastructure"] || lastIdByCategory["domain"]].filter(Boolean),
|
|
287
|
-
acceptance_criteria:
|
|
537
|
+
acceptance_criteria: [
|
|
538
|
+
"Implements IClientSeedDataProvider with SeedNavigationAsync, SeedPermissionsAsync, SeedRolePermissionsAsync",
|
|
539
|
+
"Registered in DI: services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()",
|
|
540
|
+
"All methods are idempotent (check existence before inserting)",
|
|
541
|
+
"Consumes seed data from NavigationModuleSeedData, PermissionsSeedData, RolesSeedData"
|
|
542
|
+
].join("; "),
|
|
288
543
|
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
289
544
|
files_changed: { created: ["src/Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs"], modified: ["src/Infrastructure/DependencyInjection.cs"] },
|
|
290
|
-
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
|
+
}
|
|
291
557
|
});
|
|
292
558
|
lastIdByCategory["infrastructure"] = taskId;
|
|
293
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
|
|
|
@@ -168,7 +168,64 @@ if (hasQueue) {
|
|
|
168
168
|
const queue = readJSON(queuePath);
|
|
169
169
|
const currentModule = queue.modules[queue.currentIndex];
|
|
170
170
|
|
|
171
|
-
//
|
|
171
|
+
// ✅ FIX #4: MODULE COMPLETENESS CHECK (MANDATORY before advancing)
|
|
172
|
+
// A module is NOT complete unless all expected layers have at least one completed task.
|
|
173
|
+
// This prevents marking a module as "done" when only backend was implemented.
|
|
174
|
+
const completedCategories = new Set(
|
|
175
|
+
prd.tasks.filter(t => t.status === 'completed').map(t => t.category)
|
|
176
|
+
);
|
|
177
|
+
const expectedCategories = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
|
|
178
|
+
const missingCategories = expectedCategories.filter(c => !completedCategories.has(c));
|
|
179
|
+
|
|
180
|
+
if (missingCategories.length > 0) {
|
|
181
|
+
console.log(`
|
|
182
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
183
|
+
║ ⚠️ MODULE INCOMPLETE: Missing layers ║
|
|
184
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
185
|
+
║ Module: ${currentModule.code} ║
|
|
186
|
+
║ Missing: ${missingCategories.join(', ')} ║
|
|
187
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
188
|
+
║ Injecting guardrail tasks for missing layers... ║
|
|
189
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
190
|
+
`);
|
|
191
|
+
|
|
192
|
+
// Inject guardrail tasks for each missing category
|
|
193
|
+
let maxId = Math.max(...prd.tasks.map(t => t.id));
|
|
194
|
+
const lastCompletedByCategory = {};
|
|
195
|
+
for (const task of prd.tasks) {
|
|
196
|
+
if (task.status === 'completed') lastCompletedByCategory[task.category] = task.id;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const cat of missingCategories) {
|
|
200
|
+
maxId++;
|
|
201
|
+
const depId = cat === 'frontend'
|
|
202
|
+
? (lastCompletedByCategory['api'] || lastCompletedByCategory['application'])
|
|
203
|
+
: cat === 'test'
|
|
204
|
+
? (lastCompletedByCategory['api'] || lastCompletedByCategory['frontend'])
|
|
205
|
+
: (lastCompletedByCategory['domain'] || lastCompletedByCategory['infrastructure']);
|
|
206
|
+
|
|
207
|
+
prd.tasks.push({
|
|
208
|
+
id: maxId,
|
|
209
|
+
description: `[${cat}] GUARDRAIL: Generate missing ${cat} layer for module ${currentModule.code}`,
|
|
210
|
+
status: 'pending',
|
|
211
|
+
category: cat,
|
|
212
|
+
dependencies: depId ? [depId] : [],
|
|
213
|
+
acceptance_criteria: `${cat} layer fully implemented for module ${currentModule.code}`,
|
|
214
|
+
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
215
|
+
files_changed: { created: [], modified: [] },
|
|
216
|
+
validation: null, error: null, module: currentModule.code
|
|
217
|
+
});
|
|
218
|
+
console.log(` → Injected task ${maxId}: [${cat}] guardrail for ${currentModule.code}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
prd.updated_at = new Date().toISOString();
|
|
222
|
+
writeJSON('.ralph/prd.json', prd);
|
|
223
|
+
|
|
224
|
+
// DO NOT mark module as complete — continue the COMPACT LOOP with new tasks
|
|
225
|
+
// Fall through to section 5 (COMPACT LOOP)
|
|
226
|
+
} else {
|
|
227
|
+
|
|
228
|
+
// Mark current module as completed (only when ALL layers verified)
|
|
172
229
|
currentModule.status = 'completed';
|
|
173
230
|
queue.completedModules++;
|
|
174
231
|
|
|
@@ -231,6 +288,7 @@ if (hasQueue) {
|
|
|
231
288
|
║ ${queue.modules.map(m => m.code + ": " + m.status).join("\n║ ")} ║
|
|
232
289
|
╚══════════════════════════════════════════════════════════════════╝
|
|
233
290
|
`);
|
|
291
|
+
} // end else (module completeness check passed)
|
|
234
292
|
}
|
|
235
293
|
```
|
|
236
294
|
|
|
@@ -430,9 +488,19 @@ Batch: {tasksToExecute.length} task(s) [{firstCategory}]
|
|
|
430
488
|
8. Generate 4-language i18n (fr, en, it, de)
|
|
431
489
|
9. `npm run typecheck` MUST pass
|
|
432
490
|
|
|
433
|
-
**IF category = "
|
|
491
|
+
**IF category = "domain":** Entities in `Domain/Entities/{ContextPascal}/{App}/{Module}/`, Enums in `Domain/Enums/{ContextPascal}/{App}/{Module}/`
|
|
434
492
|
|
|
435
|
-
**IF category = "
|
|
493
|
+
**IF category = "application":** Services in `Application/Services/{ContextPascal}/{App}/{Module}/`, DTOs in `Application/DTOs/{ContextPascal}/{App}/{Module}/`
|
|
494
|
+
|
|
495
|
+
**IF category = "infrastructure":**
|
|
496
|
+
- EF configs in `Infrastructure/Persistence/Configurations/{ContextPascal}/{App}/{Module}/`
|
|
497
|
+
- Seed data in `Infrastructure/Persistence/Seeding/Data/{Module}/`
|
|
498
|
+
- **IF task description contains "seed data", "SeedData", or "IClientSeedDataProvider":**
|
|
499
|
+
Read `references/core-seed-data.md` for COMPLETE execution guidance (parameter extraction,
|
|
500
|
+
MCP `generate_permissions` call, code templates, IClientSeedDataProvider, verification checklist).
|
|
501
|
+
**Do NOT improvise core seed data generation.**
|
|
502
|
+
|
|
503
|
+
**IF category = "api":** Controllers in `Api/Controllers/{ContextShort}/{App}/{Entity}Controller.cs`
|
|
436
504
|
|
|
437
505
|
After ALL tasks in batch executed:
|
|
438
506
|
- Run `mcp__smartstack__validate_conventions` ONCE for the whole batch
|
|
@@ -581,17 +649,32 @@ Completion Check:
|
|
|
581
649
|
1. ALL tasks have `status: "completed"` or `status: "skipped"`
|
|
582
650
|
2. Zero tasks with `status: "blocked"` or `status: "failed"`
|
|
583
651
|
3. The statement is genuinely true
|
|
652
|
+
4. **Multi-module:** ALL modules in `modules-queue.json` have `status: "completed"`
|
|
584
653
|
|
|
585
654
|
**False promises to escape the loop are FORBIDDEN.**
|
|
586
655
|
|
|
587
656
|
**Always update `prd.status` and `prd.updated_at` before proceeding.**
|
|
588
657
|
|
|
658
|
+
**NEVER DELEGATE THE LOOP:**
|
|
659
|
+
- NEVER use the Task tool to spawn a sub-agent for Ralph loop execution.
|
|
660
|
+
- Sub-agents lose ALL skill context and WILL stop prematurely.
|
|
661
|
+
- The ONLY acceptable Task usage: isolated read-only research (e.g., Context7 docs lookup).
|
|
662
|
+
|
|
663
|
+
**MODULE COMPLETENESS (multi-module):**
|
|
664
|
+
- Before advancing to the next module, verify ALL expected layers have completed tasks.
|
|
665
|
+
- Expected layers: domain, infrastructure, application, api, frontend, test.
|
|
666
|
+
- If any layer is missing, inject guardrail tasks and continue the COMPACT LOOP.
|
|
667
|
+
- A module with only backend tasks is NOT complete.
|
|
668
|
+
|
|
589
669
|
**LOOP CONTINUATION IS MANDATORY:**
|
|
590
670
|
- After the first full iteration (step-01→02→03→04), ALL subsequent iterations use the COMPACT LOOP in section 5.
|
|
591
671
|
- DO NOT re-read step-01, step-02, step-03 files. You already know the instructions.
|
|
592
672
|
- DO NOT stop and wait for user input between iterations.
|
|
593
673
|
- DO NOT output a summary and pause. The loop is AUTONOMOUS.
|
|
594
|
-
-
|
|
674
|
+
- DO NOT decide to stop because "quality over quantity" or "this is enough for now".
|
|
675
|
+
- DO NOT stop after completing one module when others remain in the queue.
|
|
676
|
+
- DO NOT reduce scope autonomously ("I'll skip frontend/tests to save time").
|
|
677
|
+
- The ONLY reasons to stop: ALL tasks complete (all modules), max iterations, dead-end, or user Ctrl+C.
|
|
595
678
|
- Stopping for any other reason is a **BUG** that wastes user time and context.
|
|
596
679
|
- **BATCH tasks of the same category** to reduce iterations (max 5 per batch).
|
|
597
680
|
- Prefer compact output (1-2 lines per task) over verbose output during the loop.
|