@atlashub/smartstack-cli 3.21.0 → 3.22.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.
Files changed (28) hide show
  1. package/dist/index.js +17 -5
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +68 -3
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/skills/application/references/application-roles-template.md +2 -2
  7. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  8. package/templates/skills/application/templates-frontend.md +64 -36
  9. package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
  10. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
  11. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  12. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  13. package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
  14. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  15. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  16. package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
  17. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  18. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  19. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  20. package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
  21. package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
  22. package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
  23. package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
  24. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  25. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  26. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  27. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  28. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
@@ -46,8 +46,8 @@ Validate the module specification for completeness and consistency, write to fea
46
46
  | entities | 1 | `analysis.entities.length >= 1` | PASS/FAIL |
47
47
  | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | `analysis.entities.every(e => e.attributes?.length > 0)` | PASS/FAIL |
48
48
  | entityAttributeTypes | ALL attributes have `type` field (BLOCKING) | `analysis.entities.every(e => e.attributes.every(a => a.type))` | PASS/FAIL |
49
- | wireframes | 1 per section (BLOCKING) | `specification.wireframes.length >= specification.sections.length` (count REAL elements) | PASS/FAIL |
50
- | wireframeSchema | All required fields present (BLOCKING) | `specification.wireframes.every(w => w.screen && w.section && (w.mockup \|\| w.content))` | PASS/FAIL |
49
+ | wireframes | 1 per section (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).length >= (specification.sections \|\| []).length` (count REAL elements, check BOTH key names) | PASS/FAIL |
50
+ | wireframeSchema | All required fields present (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).every(w => (w.screen \|\| w.title) && w.section && (w.mockup \|\| w.ascii \|\| w.content))` | PASS/FAIL |
51
51
  | sections | 1 (BLOCKING) | `specification.sections.length >= 1` (EVERY module needs at least 1 section) | PASS/FAIL |
52
52
  | gherkinScenarios | 1 array entry | `Array.isArray(specification.gherkinScenarios) && specification.gherkinScenarios.length >= 1` | PASS/FAIL |
53
53
  | gherkinFormat | Array not object (BLOCKING) | `Array.isArray(specification.gherkinScenarios)` (NOT a single object) | PASS/FAIL |
@@ -97,9 +97,74 @@ IF failures.length > 0:
97
97
  - Entity names PascalCase
98
98
  - Field names camelCase
99
99
  - Entity attribute format: `attributes[]` with {name, description}, NOT `fields[]` with {name, type} — entities must NOT have tableName or primaryKey
100
- - Wireframe structure: `screen` (not `name`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
100
+ - Wireframe structure: `screen` (not `name`/`title`), `mockup` (not `ascii`/`content`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
101
+ - Wireframe field naming: uses `screen` (not `title`), `mockup` (not `ascii`) — auto-fix if wrong (see 9c-fix below)
101
102
  - Permission paths dot-separated lowercase
102
103
 
104
+ #### 9c-fix. Auto-Fix Wireframe Field Names (MANDATORY before writing)
105
+
106
+ > **CRITICAL:** The agent may use non-canonical field names (`title`, `ascii`, `name`, `content`).
107
+ > These MUST be normalized to canonical names before writing to feature.json.
108
+
109
+ ```javascript
110
+ // AUTO-FIX: Normalize wireframe field names before writing
111
+ const wireframes = specification.uiWireframes || specification.wireframes || [];
112
+ for (const wf of wireframes) {
113
+ if (wf.title && !wf.screen) { wf.screen = wf.title; delete wf.title; }
114
+ if (wf.ascii && !wf.mockup) { wf.mockup = wf.ascii; delete wf.ascii; }
115
+ if (wf.content && !wf.mockup) { wf.mockup = wf.content; delete wf.content; }
116
+ if (wf.name && !wf.screen) { wf.screen = wf.name; delete wf.name; }
117
+ }
118
+ // Store normalized wireframes back under canonical key
119
+ specification.uiWireframes = wireframes;
120
+ ```
121
+
122
+ #### 9c-fix-entities. Auto-Fix Entity Attribute Types (MANDATORY before writing)
123
+
124
+ > **DEFENSE-IN-DEPTH:** step-03c POST-CHECK should catch attributes without `type`,
125
+ > but if any slip through to step-03d, auto-fix them here before writing to feature.json.
126
+ > Uses the same type inference algorithm as step-03c POST-CHECK.
127
+
128
+ ```javascript
129
+ // AUTO-FIX: Ensure all entity attributes have structured type
130
+ const entities = analysis.entities || [];
131
+ let entityAutoFixCount = 0;
132
+ for (const entity of entities) {
133
+ for (const attr of entity.attributes || []) {
134
+ if (!attr.type) {
135
+ // Infer type from attribute name pattern
136
+ if (attr.validation?.match(/max\s*\d+/i) || attr.name.match(/name|title|code|description|label|email|phone|address/i)) {
137
+ attr.type = "string";
138
+ const maxMatch = attr.validation?.match(/max\s*(\d+)/i);
139
+ if (maxMatch) attr.maxLength = parseInt(maxMatch[1]);
140
+ } else if (attr.name.match(/id$/i)) {
141
+ attr.type = "Guid";
142
+ } else if (attr.name.match(/date|At$/i)) {
143
+ attr.type = "DateTime";
144
+ } else if (attr.name.match(/is[A-Z]|has[A-Z]|active|enabled/)) {
145
+ attr.type = "bool";
146
+ } else if (attr.name.match(/amount|salary|rate|price|total/i)) {
147
+ attr.type = "decimal";
148
+ } else if (attr.name.match(/count|number|order|sort|index/i)) {
149
+ attr.type = "int";
150
+ } else {
151
+ attr.type = "string"; // safe default
152
+ }
153
+ // Normalize free-text maxLength
154
+ if (typeof attr.validation === 'string' && !attr.maxLength) {
155
+ const m = attr.validation.match(/max\s*(\d+)/i);
156
+ if (m) attr.maxLength = parseInt(m[1]);
157
+ }
158
+ if (attr.required === undefined) attr.required = true;
159
+ entityAutoFixCount++;
160
+ }
161
+ }
162
+ }
163
+ if (entityAutoFixCount > 0) {
164
+ console.warn(`DEFENSE-IN-DEPTH: auto-fixed ${entityAutoFixCount} attributes without type in step-03d`);
165
+ }
166
+ ```
167
+
103
168
  #### 9d. Decision
104
169
 
105
170
  IF validation PASS:
@@ -165,10 +230,10 @@ ba-writer.enrichSection({
165
230
 
166
231
  **Execute the comprehensive validation checklist:**
167
232
 
168
- Run the 27-check validation process across 10 categories:
233
+ Run the 29-check validation process across 10 categories:
169
234
  - Data Model (4 checks) | Business Rules (3 checks) | Use Cases & FRs (4 checks)
170
235
  - Permissions (3 checks) | UI & Navigation (4 checks) | I18N & Messages (3 checks)
171
- - Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (1 check)
236
+ - Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (3 checks)
172
237
 
173
238
  ```javascript
174
239
  const blockingFailures = checks.filter(c => c.blocking && c.status === "FAIL");
@@ -243,18 +308,13 @@ Display comprehensive summary:
243
308
 
244
309
  → Validation: {PASS/FAIL}
245
310
  ═══════════════════════════════════════════════════════════
246
- ```
247
311
 
248
- Ask via AskUserQuestion:
312
+ {IF validation PASS}:
313
+ → Module validé automatiquement
314
+ → Passage automatique au module suivant (section 12)
249
315
 
250
- ```
251
- question: "Le module {currentModule} est-il correctement spécifié ?"
252
- header: "Module"
253
- options:
254
- - label: "Validé"
255
- description: "Passer au module suivant"
256
- - label: "Réviser"
257
- description: "Modifier des éléments du module"
316
+ {IF validation FAIL}:
317
+ Afficher les erreurs et offrir les options de correction (section 9d)
258
318
  ```
259
319
 
260
320
  ---
@@ -319,7 +379,7 @@ const written = ba-reader.read({module_feature_id});
319
379
  const checks = [
320
380
  { key: "specification.actors", actual: written.specification?.actors?.length, min: 2 },
321
381
  { key: "specification.useCases", actual: written.specification?.useCases?.length, min: 2 },
322
- { key: "specification.wireframes", actual: written.specification?.wireframes?.length, min: 1 },
382
+ { key: "specification.wireframes", actual: (written.specification?.uiWireframes?.length || written.specification?.wireframes?.length || 0), min: 1 },
323
383
  { key: "specification.sections", actual: written.specification?.sections?.length, min: 1 },
324
384
  { key: "specification.seedDataCore", actual: Object.keys(written.specification?.seedDataCore || {}).length, min: 7 },
325
385
  { key: "specification.lifeCycles", actual: written.specification?.lifeCycles?.length, min: 0 },
@@ -334,16 +394,78 @@ IF failures.length > 0:
334
394
  → Re-execute section 11 write with ALL specification data
335
395
  → DO NOT proceed to next module until ALL checks pass
336
396
 
337
- // SPECIAL CHECK: wireframes content verification
338
- IF written.specification?.wireframes?.length > 0:
339
- const emptyMockups = written.specification.wireframes.filter(wf => !wf.mockup && !wf.content);
397
+ // SPECIAL CHECK: wireframes content verification (check BOTH key names)
398
+ const wireframes = written.specification?.uiWireframes || written.specification?.wireframes || [];
399
+ IF wireframes.length > 0:
400
+ const emptyMockups = wireframes.filter(wf => !wf.mockup && !wf.ascii && !wf.content);
340
401
  IF emptyMockups.length > 0:
341
402
  WARNING: "{emptyMockups.length} wireframes have empty mockup content — verify step-03b data"
403
+
404
+ // SPECIAL CHECK: wireframes >= sections
405
+ const sectionCount = written.specification?.sections?.length || 0;
406
+ IF wireframes.length < sectionCount:
407
+ BLOCKING ERROR: "{wireframes.length} wireframes < {sectionCount} sections — wireframes MISSING for some sections"
408
+ → Re-read wireframes from conversation context and re-write to feature.json
342
409
  ```
343
410
 
344
- > **WHY:** Step-03b generates wireframes in memory, step-03c only writes seedDataCore explicitly.
345
- > If the comprehensive write in section 11 is skipped or partial, wireframes/sections are LOST.
346
- > This POST-CHECK catches the data loss BEFORE advancing to the next module.
411
+ #### 11-POST-CHECK-BASH: Acceptance Criteria Verification (BLOCKING)
412
+
413
+ > **CRITICAL:** The pseudocode checks above are interpreted by the model they can be "passed" incorrectly.
414
+ > This bash check reads the REAL file on disk and provides an objective verification.
415
+ > See [references/acceptance-criteria.md](../references/acceptance-criteria.md) for the full acceptance criteria definition.
416
+
417
+ ```bash
418
+ MODULE_JSON="{module_feature_json_path}"
419
+ node -e "
420
+ const fs = require('fs');
421
+ const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
422
+ const spec = data.specification || {};
423
+ const analysis = data.analysis || {};
424
+ const wf = spec.uiWireframes || spec.wireframes || [];
425
+ const sections = spec.sections || [];
426
+ const checks = [
427
+ ['entities >= 1', (analysis.entities||[]).length, 1],
428
+ ['useCases >= 2', (spec.useCases||[]).length, 2],
429
+ ['FRs >= 4', (spec.functionalRequirements||[]).length,4],
430
+ ['wireframes >= 1', wf.length, 1],
431
+ ['wireframes >= sections', wf.length, sections.length],
432
+ ['sections >= 1', sections.length, 1],
433
+ ['seedDataCore 7 arrays', Object.keys(spec.seedDataCore||{}).filter(k=>(spec.seedDataCore||{})[k]&&(spec.seedDataCore||{})[k].length>0).length, 7],
434
+ ['gherkin is array', Array.isArray(spec.gherkinScenarios)?1:0, 1],
435
+ ['apiEndpoints >= 1', (spec.apiEndpoints||[]).length, 1],
436
+ ['messages >= 4', (spec.messages||[]).length, 4],
437
+ ['validations >= 1', (spec.validations||[]).length, 1]
438
+ ];
439
+ const fails = checks.filter(c => c[1] < c[2]);
440
+ fails.forEach(f => console.error('FAIL: ' + f[0] + ' = ' + f[1] + ' (min: ' + f[2] + ')'));
441
+ // Check wireframe content
442
+ const emptyWf = wf.filter(w => !w.mockup && !w.ascii && !w.content);
443
+ if (emptyWf.length > 0) { fails.push(['wireframe content', 0, 1]); console.error('FAIL: ' + emptyWf.length + ' wireframes have EMPTY content'); }
444
+ // Check entity attribute types
445
+ const badAttrs = (analysis.entities||[]).flatMap(e => (e.attributes||[]).filter(a => !a.type).map(a => e.name+'.'+a.name));
446
+ if (badAttrs.length > 0) { fails.push(['attr.type', 0, 1]); console.error('FAIL: attributes without type: ' + badAttrs.join(', ')); }
447
+ // AC-15: Validation rules format
448
+ const badRules = (spec.validations||[]).filter(v => v.rules && !Array.isArray(v.rules));
449
+ if (badRules.length > 0) { fails.push(['AC-15: rules not array', badRules.length, 0]); console.error('FAIL: AC-15: ' + badRules.length + ' validations have rules as string'); }
450
+ // AC-16: Messages must have message field
451
+ const noMsg = (spec.messages||[]).filter(m => !m.message);
452
+ if (noMsg.length > 0) { fails.push(['AC-16: message missing', noMsg.length, 0]); console.error('FAIL: AC-16: ' + noMsg.length + ' messages missing message field'); }
453
+ // AC-17: Gherkin content structure
454
+ if (Array.isArray(spec.gherkinScenarios)) { const badG = spec.gherkinScenarios.filter(g => !g.feature || !Array.isArray(g.scenarios)); if (badG.length > 0) { fails.push(['AC-17: gherkin content', badG.length, 0]); console.error('FAIL: AC-17: ' + badG.length + ' gherkin entries invalid'); } }
455
+ if (fails.length > 0) { console.error('BLOCKING: ' + fails.length + ' acceptance criteria failed'); process.exit(1); }
456
+ console.log('PASS: All acceptance criteria met');
457
+ " "$MODULE_JSON"
458
+ ```
459
+
460
+ IF this check FAILS:
461
+ - Identify which criteria failed from the output
462
+ - Fix the corresponding data (re-read from conversation context, re-generate, or re-run the step that produces it)
463
+ - Re-write to feature.json
464
+ - Re-run the POST-CHECK until PASS
465
+
466
+ > **WHY:** Step-03b now writes wireframes intermediately (since fix), but step-03d section 11 does the FULL write.
467
+ > This bash POST-CHECK is the ultimate safety net — it reads the REAL file and verifies ALL acceptance criteria.
468
+ > The pseudocode checks (section 9a) catch issues early; this bash check catches anything the model missed.
347
469
 
348
470
  ---
349
471
 
@@ -200,6 +200,55 @@ See [references/handoff-file-templates.md](../references/handoff-file-templates.
200
200
  | **4.6 SeedData** | `specification.seedDataCore` | 5 CORE + business + IClientSeedDataProvider |
201
201
  | **4.7 Tests** | All layers | Unit, Integration, Security tests |
202
202
 
203
+ #### Route Convention (CRITICAL)
204
+
205
+ > **MANDATORY:** All NavigationModule/Section routes MUST use kebab-case transformation.
206
+ > See [references/naming-conventions.md](../references/naming-conventions.md) for complete guide.
207
+
208
+ **Problem:**
209
+ - Module codes in feature.json are **PascalCase** (e.g., `"HumanResources"`)
210
+ - Routes in seed data MUST be **kebab-case** (e.g., `"/business/human-resources"`)
211
+ - Failing to transform causes 404 when clicking menu items
212
+
213
+ **Solution:**
214
+
215
+ When generating `seedDataCore` for handoff, transform PascalCase codes to kebab-case routes:
216
+
217
+ ```javascript
218
+ // Transform module codes to kebab-case routes
219
+ function toKebabCase(code) {
220
+ return code
221
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
222
+ .toLowerCase();
223
+ }
224
+
225
+ // Example usage in seedDataCore generation
226
+ const moduleCode = "TimeManagement"; // PascalCase (C#)
227
+ const appCode = "HumanResources"; // PascalCase (C#)
228
+ const contextCode = "business"; // lowercase
229
+
230
+ const route = `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(moduleCode)}`;
231
+ // Result: "/business/human-resources/time-management"
232
+ ```
233
+
234
+ **Include in task instructions:**
235
+
236
+ For NavigationSeedData tasks in `handoff.filesToCreate`, add:
237
+
238
+ ```json
239
+ {
240
+ "path": "Infrastructure/Persistence/Seeding/Data/{Module}/NavigationSeedData.cs",
241
+ "type": "SeedData",
242
+ "category": "core",
243
+ "instructions": "Generate NavigationModule seed data. CRITICAL: Use ToKebabCase() helper for Route property. Example: Route = ToKebabCase($\"/{context}/{app}/{module}\"). See core-seed-data.md template."
244
+ }
245
+ ```
246
+
247
+ **Validation:**
248
+ - ralph-loop POST-CHECK validates routes after generation (see step-02-execute.md)
249
+ - MCP `validate_frontend_routes` detects case mismatches
250
+ - Convention documented in naming-conventions.md
251
+
203
252
  **Critical rules:**
204
253
  - All backend paths include `{ContextPascal}/{ApplicationName}/` hierarchy
205
254
  - Frontend pages MUST have `linkedWireframes[]` + `wireframeAcceptanceCriteria`
@@ -249,7 +298,7 @@ Additional per-section pages (dashboard, import, etc.) are separate entries.
249
298
 
250
299
  **Route wiring task (MANDATORY — separate entry):**
251
300
  ```json
252
- { "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx Layout wrappers (standard + tenant-prefixed blocks). BLOCKING: pages without route wiring = blank pages." }
301
+ { "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx (detect pattern: contextRoutes array OR JSX Route wrappers). BLOCKING: pages without route wiring = blank pages." }
253
302
  ```
254
303
 
255
304
  ### 6c. Cross-Module PRD (MANDATORY for multi-module features)
@@ -359,7 +408,43 @@ Add to progress.txt after all module tasks.
359
408
  ```
360
409
  // Derive seedDataCore from modules[], applicationRoles[], and coverageMatrix[]
361
410
 
411
+ // Helper: PascalCase → "Human Readable" (e.g., "HumanResources" → "Human Resources")
412
+ function toHumanReadable(pascalCase) {
413
+ return pascalCase
414
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
415
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
416
+ }
417
+
418
+ const contextCode = master.metadata.context || "business";
419
+ const appCode = master.metadata.application;
420
+ const appLabel = toHumanReadable(appCode);
421
+ const appDesc = master.cadrage?.problem?.split('.')[0] || `Gestion ${appLabel}`;
422
+ const lang = master.metadata.language || "fr";
423
+
362
424
  const seedDataCore = {
425
+ // Application-level navigation (MANDATORY — one entry per application)
426
+ navigationApplications: [{
427
+ code: appCode.toLowerCase(),
428
+ name: appCode,
429
+ labels: {
430
+ fr: lang === "fr" ? (master.cadrage?.applicationName || appLabel) : appLabel,
431
+ en: lang === "en" ? (master.cadrage?.applicationName || appLabel) : appLabel,
432
+ it: appLabel,
433
+ de: appLabel
434
+ },
435
+ description: {
436
+ fr: lang === "fr" ? appDesc : `Gestion ${appLabel}`,
437
+ en: lang === "en" ? appDesc : `${appLabel} management`,
438
+ it: `Gestione ${appLabel}`,
439
+ de: `${appLabel} Verwaltung`
440
+ },
441
+ icon: inferIconFromApplication(master) || "layout-grid",
442
+ iconType: "lucide",
443
+ context: contextCode,
444
+ route: `/${contextCode}/${toKebabCase(appCode)}`,
445
+ displayOrder: 1
446
+ }],
447
+
363
448
  navigationModules: master.modules.map((m, i) => ({
364
449
  code: m.code,
365
450
  label: m.name || m.code,
@@ -433,6 +518,27 @@ const seedDataCore = {
433
518
  )
434
519
  };
435
520
 
521
+ // Icon inference for APPLICATION level (NEVER leave as null)
522
+ function inferIconFromApplication(master) {
523
+ const appLC = (master.metadata.application || '').toLowerCase();
524
+ const iconMap = {
525
+ 'humanresource': 'users', 'rh': 'users', 'hr': 'users', 'personnel': 'users',
526
+ 'sales': 'shopping-cart', 'vente': 'shopping-cart', 'commerce': 'shopping-cart',
527
+ 'finance': 'wallet', 'comptabilite': 'calculator', 'accounting': 'calculator',
528
+ 'inventory': 'warehouse', 'stock': 'package', 'logistic': 'truck',
529
+ 'crm': 'building-2', 'customer': 'building', 'client': 'building-2',
530
+ 'project': 'folder-kanban', 'task': 'check-square', 'gestion': 'layout-grid',
531
+ 'admin': 'shield', 'support': 'headphones', 'maintenance': 'wrench',
532
+ 'production': 'factory', 'quality': 'badge-check', 'document': 'file-text',
533
+ 'fleet': 'car', 'vehicle': 'car', 'transport': 'truck',
534
+ 'education': 'graduation-cap', 'formation': 'graduation-cap', 'training': 'graduation-cap'
535
+ };
536
+ for (const [keyword, icon] of Object.entries(iconMap)) {
537
+ if (appLC.includes(keyword)) return icon;
538
+ }
539
+ return "layout-grid"; // fallback — NEVER null
540
+ }
541
+
436
542
  // Icon inference from module context (NEVER leave as null)
437
543
  function inferIconFromModule(module) {
438
544
  const codeLC = module.code.toLowerCase();
@@ -462,8 +568,14 @@ ba-writer.enrichSection({
462
568
  })
463
569
  ```
464
570
 
465
- **POST-CHECK (BLOCKING for icons, warning for counts):**
571
+ **POST-CHECK (BLOCKING for icons/application, warning for counts):**
466
572
  ```
573
+ IF !seedDataCore.navigationApplications || seedDataCore.navigationApplications.length === 0:
574
+ BLOCKING ERROR: "navigationApplications is empty — Application navigation will NOT be seeded. Menu will show Context → (nothing) → Modules."
575
+ IF seedDataCore.navigationApplications[0].icon === null || seedDataCore.navigationApplications[0].icon === undefined:
576
+ BLOCKING ERROR: "Application icon is null — ralph-loop will render empty navigation"
577
+ IF !seedDataCore.navigationApplications[0].labels?.fr || !seedDataCore.navigationApplications[0].labels?.en:
578
+ BLOCKING ERROR: "Application labels missing fr/en — translations will fail"
467
579
  IF seedDataCore.navigationModules.length !== master.modules.length:
468
580
  WARNING: seedDataCore has ${seedDataCore.navigationModules.length} nav modules but ${master.modules.length} modules exist
469
581
  IF seedDataCore.permissions.length === 0:
@@ -238,6 +238,34 @@ See [references/deploy-data-build.md](../references/deploy-data-build.md) for:
238
238
 
239
239
  ---
240
240
 
241
+ ### 7-bis. Generate SVG Wireframes (Parallel Enrichment)
242
+
243
+ > **After EMBEDDED_ARTIFACTS is built (sections 5-7) but BEFORE writing the HTML (section 8).**
244
+ > This step enriches each ASCII wireframe with a professional SVG version for dual-view rendering.
245
+
246
+ See [references/wireframe-svg-style-guide.md](../references/wireframe-svg-style-guide.md) for the complete SVG style specification, prompt template, and orchestration process.
247
+
248
+ **Process summary:**
249
+
250
+ 1. **Read** `references/wireframe-svg-style-guide.md` to get the prompt template
251
+ 2. **Collect** all wireframes across ALL modules in `EMBEDDED_ARTIFACTS.wireframes` where `content` exists and `svgContent` is null
252
+ 3. **Spawn parallel Task(sonnet) agents** — ONE per wireframe, ALL in a single message
253
+ 4. **Collect and validate** results: strip markdown fences if present, verify SVG starts with `<svg` and contains `</svg>`
254
+ 5. **Inject** valid SVGs into `EMBEDDED_ARTIFACTS.wireframes[moduleCode][index].svgContent`
255
+ 6. **Display summary:**
256
+ ```
257
+ SVG wireframe generation:
258
+ Total wireframes: {total}
259
+ SVG generated: {successCount}/{total}
260
+ Fallback (ASCII only): {failCount}/{total}
261
+ ```
262
+
263
+ > **CRITICAL:** This step is NEVER blocking. If all SVG generations fail, deployment
264
+ > continues with ASCII-only wireframes. The HTML renderer checks for `svgContent` before
265
+ > showing the SVG view. SVG is an enhancement, not a requirement.
266
+
267
+ ---
268
+
241
269
  ### 8. Write and Verify
242
270
 
243
271
  1. Write the populated HTML to the output directory: `docs/business/{app}/business-analyse/v{version}/ba-interactive.html`
@@ -304,7 +304,9 @@ fi
304
304
  **Execution sequence (IN ORDER):**
305
305
  1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
306
306
  2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
307
- 3. **Wire routes to App.tsx** insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
307
+ 3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
308
+ - **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
309
+ - **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
308
310
  4. Create pages using SmartStack components
309
311
  5. Create preferences hook: `use{Module}Preferences.ts`
310
312
  6. Generate i18n (4 languages: fr, en, it, de)
@@ -348,7 +350,8 @@ import axios from 'axios' → use @/services/api/apiClient
348
350
  Only fr/en translations → MUST have 4 languages
349
351
  src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
350
352
  Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
351
- Routes in standard block only → MUST also add to /t/:slug/ tenant block
353
+ Routes in standard block only → MUST also add to /t/:slug/ tenant block (Pattern B only; Pattern A auto-handles)
354
+ Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST use contextRoutes for business/platform/personal routes
352
355
  '00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
353
356
  const api = axios.create(...) → use @/services/api/apiClient (single instance)
354
357
  useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
@@ -110,7 +110,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
110
110
  |----------|--------|
111
111
  | `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → `dotnet ef database update` → `dotnet build` |
112
112
  | `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
113
- | `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx (standard + tenant blocks)** → create pages → `npm run typecheck && npm run lint` |
113
+ | `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`)** → create pages → `npm run typecheck && npm run lint` |
114
114
  | `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
115
115
  | `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
116
116
 
@@ -203,6 +203,57 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
203
203
  ```
204
204
  If FAIL → migration is broken → fix → rebuild → DO NOT commit
205
205
 
206
+ 5bis. **Core Seed Data Integrity (BLOCKING — if seed data tasks in batch):**
207
+ ```bash
208
+ # Verify NavigationApplicationSeedData.cs exists
209
+ APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
210
+ if [ -z "$APP_SEED" ]; then
211
+ echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
212
+ echo "Without this: nav_Applications empty → menu invisible → modules inaccessible"
213
+ echo "Fix: Generate from core-seed-data.md section 1b"
214
+ # Create fix task
215
+ fi
216
+
217
+ # Verify no hardcoded placeholders in provider
218
+ PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
219
+ if [ -n "$PROVIDER" ]; then
220
+ if grep -qE '\{appLabel_|\{appDesc_|\{appIcon\}|\{ApplicationGuid\}' "$PROVIDER" 2>/dev/null; then
221
+ echo "❌ BLOCKING: SeedDataProvider has unresolved placeholders"
222
+ echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry()"
223
+ fi
224
+ fi
225
+
226
+ # Verify ApplicationRolesSeedData references real GUID
227
+ ROLES_SEED=$(find . -path "*/Seeding/Data/ApplicationRolesSeedData.cs" 2>/dev/null | head -1)
228
+ if [ -n "$ROLES_SEED" ]; then
229
+ if grep -q '{ApplicationGuid}' "$ROLES_SEED" 2>/dev/null; then
230
+ echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
231
+ echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
232
+ fi
233
+ fi
234
+
235
+ # Quick startup test to verify seed data runs without exceptions
236
+ API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
237
+ if [ -n "$API_PROJECT" ] && [ -n "$APP_SEED" ]; then
238
+ echo "Running seed data startup test..."
239
+ dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development > /tmp/ralph-seed-check.log 2>&1 &
240
+ SEED_PID=$!
241
+ sleep 10
242
+ if ! kill -0 $SEED_PID 2>/dev/null; then
243
+ SEED_LOG=$(cat /tmp/ralph-seed-check.log 2>/dev/null | tail -20)
244
+ echo "❌ BLOCKING: Application crashed during startup — seed data likely failed"
245
+ echo "Last 20 lines: $SEED_LOG"
246
+ # Do NOT continue to frontend/test — fix seed data first
247
+ else
248
+ echo "✓ Seed data startup test passed"
249
+ kill $SEED_PID 2>/dev/null
250
+ wait $SEED_PID 2>/dev/null
251
+ fi
252
+ rm -f /tmp/ralph-seed-check.log
253
+ fi
254
+ ```
255
+ If FAIL → fix seed data → rebuild → DO NOT continue to frontend/test tasks
256
+
206
257
  **Error resolution cycle:**
207
258
  1. Read the FULL error output (not just first line)
208
259
  2. Identify root cause: file path + line number