@atlashub/smartstack-cli 4.75.0 → 4.79.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 +87 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
- package/templates/skills/ai-prompt/SKILL.md +64 -0
- package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
- package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
- package/templates/skills/apex/SKILL.md +2 -2
- package/templates/skills/apex/references/checks/frontend-checks.sh +123 -11
- package/templates/skills/apex/references/checks/seed-checks.sh +81 -7
- package/templates/skills/apex/references/core-seed-data.md +27 -22
- package/templates/skills/apex/references/domain-events-pattern.md +45 -0
- package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
- package/templates/skills/apex/references/licensing-enforcement.md +52 -0
- package/templates/skills/apex/references/post-checks.md +18 -1
- package/templates/skills/apex/references/smartstack-api.md +116 -5
- package/templates/skills/apex/references/smartstack-frontend.md +1 -1
- package/templates/skills/apex/references/smartstack-layers.md +6 -6
- package/templates/skills/apex/steps/step-00-init.md +1 -1
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +26 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +124 -2
- package/templates/skills/apex/steps/step-04-examine.md +163 -0
- package/templates/skills/apex-verify/SKILL.md +110 -0
- package/templates/skills/apex-verify/references/audit-rules.md +50 -0
- package/templates/skills/apex-verify/steps/step-00-init.md +119 -0
- package/templates/skills/apex-verify/steps/step-01-nav-audit.md +96 -0
- package/templates/skills/apex-verify/steps/step-02-crud-audit.md +127 -0
- package/templates/skills/apex-verify/steps/step-03-perm-audit.md +119 -0
- package/templates/skills/apex-verify/steps/step-04-route-audit.md +98 -0
- package/templates/skills/apex-verify/steps/step-05-report.md +110 -0
- package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
- package/templates/skills/application/references/extensions-system.md +158 -0
- package/templates/skills/application/references/frontend-route-naming.md +7 -5
- package/templates/skills/application/references/frontend-verification.md +7 -5
- package/templates/skills/application/references/provider-template.md +4 -2
- package/templates/skills/application/references/smartstack-provider.md +118 -0
- package/templates/skills/application/references/themes-db-driven.md +484 -0
- package/templates/skills/application/templates-frontend.md +2 -2
- package/templates/skills/application/templates-seed.md +4 -2
- package/templates/skills/audit-route/references/routing-pattern.md +3 -1
- package/templates/skills/business-analyse/SKILL.md +3 -3
- package/templates/skills/business-analyse/_shared.md +37 -0
- package/templates/skills/business-analyse/react/components.md +30 -28
- package/templates/skills/business-analyse/references/03-json-schemas.md +11 -3
- package/templates/skills/business-analyse/references/03-post-check-validation.md +64 -0
- package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
- package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
- package/templates/skills/business-analyse/references/validation-checklist.md +5 -5
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +15 -4
- package/templates/skills/business-analyse/steps/step-03-specify.md +162 -4
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +211 -1
- package/templates/skills/business-analyse/templates-react.md +15 -15
- package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +3 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +198 -16
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +64 -0
- package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +80 -11
- package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +2 -2
- package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +6 -3
- package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +46 -0
- package/templates/skills/business-analyse-html/references/02-feature-data-building.md +4 -2
- package/templates/skills/business-analyse-html/references/data-build.md +2 -0
- package/templates/skills/business-analyse-html/references/data-mapping.md +88 -21
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +6 -0
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +92 -3
- package/templates/skills/business-analyse-quick/SKILL.md +807 -0
- package/templates/skills/{sketch → business-analyse-quick}/references/domain-heuristics.md +59 -3
- package/templates/skills/business-analyse-quick/references/prd-schema.md +268 -0
- package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
- package/templates/skills/cli-app-sync/SKILL.md +105 -4
- package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
- package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
- package/templates/skills/dev-start/SKILL.md +7 -7
- package/templates/skills/documentation/templates.md +16 -16
- package/templates/skills/migrate/SKILL.md +312 -0
- package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
- package/templates/skills/sketch/SKILL.md +15 -153
- package/templates/skills/smoke-generation/SKILL.md +313 -0
- package/templates/skills/ui-components/SKILL.md +11 -1
- package/templates/skills/ui-components/patterns/data-table.md +1 -1
- package/templates/skills/ui-components/references/component-catalog.md +82 -0
- package/templates/skills/workflow/SKILL.md +70 -1
|
@@ -105,6 +105,70 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
// Normalize permissions: convert any object format to "Role|Action" pipe-delimited strings
|
|
109
|
+
// Handles 5 formats:
|
|
110
|
+
// A: "Role|Action" strings (already correct)
|
|
111
|
+
// B: {role, permissions: ["Read:all", ...]} with role-permission pairs
|
|
112
|
+
// C: {role, permissions: ["Module.Entity.Read", ...]} with path-based permissions
|
|
113
|
+
// D: {code, label, description} permission definitions without role assignment
|
|
114
|
+
// E: {role: "", permissions: []} empty entries (skip)
|
|
115
|
+
(data.modules || []).forEach(function(m) {
|
|
116
|
+
var spec = data.moduleSpecs[m.code];
|
|
117
|
+
if (!spec || !spec.permissions || spec.permissions.length === 0) return;
|
|
118
|
+
// Skip if already normalized (first element is a pipe-delimited string)
|
|
119
|
+
if (typeof spec.permissions[0] === 'string' && spec.permissions[0].indexOf('|') !== -1) return;
|
|
120
|
+
if (typeof spec.permissions[0] !== 'object') return;
|
|
121
|
+
|
|
122
|
+
var actionMap = {
|
|
123
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
124
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
125
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
126
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer',
|
|
127
|
+
'viewbalance': 'Consulter', 'invoicetime': 'Valider'
|
|
128
|
+
};
|
|
129
|
+
var normalized = [];
|
|
130
|
+
var first = spec.permissions[0];
|
|
131
|
+
|
|
132
|
+
// Detect Format D: {code, label, description} — permission codes without roles
|
|
133
|
+
if (first.code && !first.role && !first.permissions) {
|
|
134
|
+
// Extract actions from permission codes, assign to stakeholder roles
|
|
135
|
+
var roles = (data.cadrage.stakeholders || []).map(function(s) { return s.role; }).filter(Boolean);
|
|
136
|
+
if (roles.length === 0) roles = ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
137
|
+
spec.permissions.forEach(function(permDef) {
|
|
138
|
+
var code = permDef.code || '';
|
|
139
|
+
var raw = code.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
140
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
141
|
+
// Assign to first role (admin) by default — better than empty
|
|
142
|
+
if (roles[0]) {
|
|
143
|
+
var key = roles[0] + '|' + action;
|
|
144
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
// Format B/C: {role, permissions: [...]}
|
|
149
|
+
spec.permissions.forEach(function(entry) {
|
|
150
|
+
var role = entry.role || entry.name || '';
|
|
151
|
+
if (!role) return; // Skip Format E (empty role)
|
|
152
|
+
var perms = entry.permissions || entry.actions || [];
|
|
153
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
154
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
155
|
+
normalized.push(role + '|' + a);
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
perms.forEach(function(perm) {
|
|
160
|
+
if (typeof perm !== 'string') return;
|
|
161
|
+
var raw = perm.split(':')[0]; // "Read:all" → "Read"
|
|
162
|
+
if (raw.indexOf('.') !== -1) raw = raw.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
163
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
164
|
+
var key = role + '|' + action;
|
|
165
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
spec.permissions = normalized;
|
|
170
|
+
});
|
|
171
|
+
|
|
108
172
|
// Detect if modules use section-level specs (hierarchical mode)
|
|
109
173
|
function hasHierarchicalSpecs(mod) {
|
|
110
174
|
return (mod.anticipatedSections || []).some(function(s) {
|
|
@@ -210,6 +210,11 @@ function renderModuleSpecSection(mod) {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
function renderUseCase(code, uc, index) {
|
|
213
|
+
var preconditions = uc.preconditions || [];
|
|
214
|
+
var postconditions = uc.postconditions || [];
|
|
215
|
+
var errorScenarios = uc.errorScenarios || [];
|
|
216
|
+
var description = uc.description || '';
|
|
217
|
+
|
|
213
218
|
return `
|
|
214
219
|
<div class="uc-item">
|
|
215
220
|
<div class="uc-header">
|
|
@@ -220,8 +225,12 @@ function renderUseCase(code, uc, index) {
|
|
|
220
225
|
</div>
|
|
221
226
|
</div>
|
|
222
227
|
<div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
|
|
223
|
-
${
|
|
224
|
-
${
|
|
228
|
+
${description ? `<div class="uc-detail-label">Description</div><div class="uc-detail" style="font-style:italic;color:var(--text-muted);">${escapeHtml(description)}</div>` : ''}
|
|
229
|
+
${preconditions.length > 0 ? `<div class="uc-detail-label">Préconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${preconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
|
|
230
|
+
${uc.steps ? `<div class="uc-detail-label">Déroulement principal</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
|
|
231
|
+
${uc.alternative ? `<div class="uc-detail-label">Scénarios alternatifs</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
|
|
232
|
+
${errorScenarios.length > 0 ? `<div class="uc-detail-label">Scénarios d'erreur</div><div class="uc-detail" style="color:var(--danger);"><ul style="margin:0;padding-left:1.2rem;">${errorScenarios.map(e => '<li><strong>' + escapeHtml(typeof e === 'string' ? e : e.name || '') + '</strong>' + (typeof e === 'object' && e.steps ? ' : ' + escapeHtml(Array.isArray(e.steps) ? e.steps.join(', ') : e.steps) : '') + '</li>').join('')}</ul></div>` : ''}
|
|
233
|
+
${postconditions.length > 0 ? `<div class="uc-detail-label">Postconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${postconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
|
|
225
234
|
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
226
235
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
227
236
|
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
@@ -314,15 +323,16 @@ function renderEntity(code, ent, index) {
|
|
|
314
323
|
</div>
|
|
315
324
|
${(ent.attributes || []).length > 0 ? `
|
|
316
325
|
<table class="attr-table">
|
|
317
|
-
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
326
|
+
<thead><tr><th>Information</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
318
327
|
<tbody>
|
|
319
|
-
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
328
|
+
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
320
329
|
</tbody>
|
|
321
330
|
</table>` : ''}
|
|
322
331
|
<div style="padding:0.3rem 0.75rem;">
|
|
323
332
|
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
|
|
324
333
|
<div class="inline-form" id="${attrFormId}">
|
|
325
334
|
<div class="form-group"><label class="form-label">Nom de l'attribut</label><input type="text" class="form-input" id="attr-name-${code}-${index}" placeholder="Nom de l'attribut"></div>
|
|
335
|
+
<div class="form-group"><label class="form-label">Type</label><input type="text" class="form-input" id="attr-type-${code}-${index}" placeholder="string, guid, int, decimal, date, enum, boolean" value="string"></div>
|
|
326
336
|
<div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="attr-desc-${code}-${index}" placeholder="Description"></div>
|
|
327
337
|
<div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
|
|
328
338
|
</div>
|
|
@@ -347,7 +357,7 @@ function addEntity(code) {
|
|
|
347
357
|
|
|
348
358
|
const attrs = document.getElementById('ent-attrs-' + code).value.split('\n').filter(l => l.trim()).map(l => {
|
|
349
359
|
const parts = l.split(' - ');
|
|
350
|
-
return { name: parts[0]?.trim() || l.trim(), description: parts.slice(1).join(' - ').trim() };
|
|
360
|
+
return { name: parts[0]?.trim() || l.trim(), type: parts[1]?.trim() || 'string', description: parts.slice(2).join(' - ').trim() || parts.slice(1).join(' - ').trim() };
|
|
351
361
|
});
|
|
352
362
|
const rels = document.getElementById('ent-rels-' + code).value.split('\n').filter(l => l.trim());
|
|
353
363
|
|
|
@@ -371,13 +381,14 @@ function removeEntity(code, index) {
|
|
|
371
381
|
|
|
372
382
|
function addEntityAttribute(code, entityIndex) {
|
|
373
383
|
var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
|
|
384
|
+
var attrType = document.getElementById('attr-type-' + code + '-' + entityIndex);
|
|
374
385
|
var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
|
|
375
386
|
if (!attrName || !attrName.value.trim()) return;
|
|
376
387
|
if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
|
|
377
388
|
if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
|
|
378
389
|
data.moduleSpecs[code].entities[entityIndex].attributes = [];
|
|
379
390
|
}
|
|
380
|
-
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
|
|
391
|
+
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), type: (attrType?.value || 'string').trim(), description: (attrDesc?.value || '').trim() });
|
|
381
392
|
renderAllModuleSpecs();
|
|
382
393
|
autoSave();
|
|
383
394
|
}
|
|
@@ -488,13 +499,68 @@ function toggleWireframeView(wireframeId, view) {
|
|
|
488
499
|
}
|
|
489
500
|
}
|
|
490
501
|
|
|
502
|
+
// Normalize permissions array: handles 3 formats:
|
|
503
|
+
// Format 1 (string): "Role|Action" → already correct
|
|
504
|
+
// Format 2 (object): {role, permissions: ["Read:all", "Create:all", ...]}
|
|
505
|
+
// Format 3 (object): {role, permissions: ["Module.Entity.Read", ...]}
|
|
506
|
+
function normalizePermissions(permsArray) {
|
|
507
|
+
if (!permsArray || permsArray.length === 0) return [];
|
|
508
|
+
// If first element is a string with '|', already normalized
|
|
509
|
+
if (typeof permsArray[0] === 'string' && permsArray[0].includes('|')) return permsArray;
|
|
510
|
+
// If first element is a string without '|', return as-is (unknown format)
|
|
511
|
+
if (typeof permsArray[0] === 'string') return permsArray;
|
|
512
|
+
// Format 2/3: objects with {role, permissions[]}
|
|
513
|
+
const actionMap = {
|
|
514
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
515
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
516
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
517
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer'
|
|
518
|
+
};
|
|
519
|
+
const normalized = [];
|
|
520
|
+
permsArray.forEach(function(entry) {
|
|
521
|
+
if (typeof entry !== 'object' || !entry) return;
|
|
522
|
+
var role = entry.role || entry.name || '';
|
|
523
|
+
if (!role) return;
|
|
524
|
+
var perms = entry.permissions || entry.actions || [];
|
|
525
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
526
|
+
// Wildcard: expand to all standard actions
|
|
527
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
528
|
+
normalized.push(role + '|' + a);
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
perms.forEach(function(perm) {
|
|
533
|
+
var action = '';
|
|
534
|
+
if (typeof perm === 'string') {
|
|
535
|
+
// "Read:all" → extract "Read"
|
|
536
|
+
var raw = perm.split(':')[0];
|
|
537
|
+
// "Module.Entity.Read" → extract "Read"
|
|
538
|
+
if (raw.includes('.')) raw = raw.split('.').pop();
|
|
539
|
+
action = actionMap[raw.toLowerCase()] || raw;
|
|
540
|
+
}
|
|
541
|
+
if (action) {
|
|
542
|
+
var key = role + '|' + action;
|
|
543
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
return normalized;
|
|
548
|
+
}
|
|
549
|
+
|
|
491
550
|
function getPermRoles() {
|
|
492
|
-
// Extract roles from actual permission data (handles
|
|
551
|
+
// Extract roles from actual permission data (handles multiple formats)
|
|
493
552
|
const rolesFromPerms = [];
|
|
494
553
|
const seen = new Set();
|
|
495
554
|
data.modules.forEach(m => {
|
|
496
|
-
|
|
497
|
-
|
|
555
|
+
var perms = data.moduleSpecs[m.code]?.permissions || [];
|
|
556
|
+
// Handle object format directly for role extraction
|
|
557
|
+
perms.forEach(p => {
|
|
558
|
+
var role = '';
|
|
559
|
+
if (typeof p === 'string') {
|
|
560
|
+
role = p.split('|')[0];
|
|
561
|
+
} else if (typeof p === 'object' && p) {
|
|
562
|
+
role = p.role || p.name || '';
|
|
563
|
+
}
|
|
498
564
|
if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
|
|
499
565
|
});
|
|
500
566
|
});
|
|
@@ -517,7 +583,9 @@ function renderPermissionGrid(code) {
|
|
|
517
583
|
const baseRolesCount = roles.length - (data.customRoles || []).length;
|
|
518
584
|
const baseActionsCount = 6;
|
|
519
585
|
|
|
520
|
-
|
|
586
|
+
// Normalize permissions to "Role|Action" format (handles object and string formats)
|
|
587
|
+
const rawPerms = data.moduleSpecs[code]?.permissions || [];
|
|
588
|
+
const perms = normalizePermissions(rawPerms);
|
|
521
589
|
|
|
522
590
|
return `
|
|
523
591
|
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
@@ -722,7 +790,8 @@ function renderModuleMockups(code) {
|
|
|
722
790
|
}) : [];
|
|
723
791
|
|
|
724
792
|
// Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
|
|
725
|
-
|
|
793
|
+
var hasResources = screens.some(function(s) { return (s.resources || []).length > 0; });
|
|
794
|
+
if (screens.length > 0 && hasResources) {
|
|
726
795
|
var html = '';
|
|
727
796
|
if (typeof renderScreenMockups === 'function') {
|
|
728
797
|
html = renderScreenMockups(code);
|
|
@@ -70,9 +70,9 @@ function renderDataModel() {
|
|
|
70
70
|
${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
|
|
71
71
|
${attrs.length > 0 ? `
|
|
72
72
|
<table class="dm-attr-table">
|
|
73
|
-
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
73
|
+
<thead><tr><th>Champ</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
74
74
|
<tbody>
|
|
75
|
-
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
75
|
+
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
76
76
|
</tbody>
|
|
77
77
|
</table>` : ''}
|
|
78
78
|
${rels.length > 0 ? `
|
|
@@ -13,13 +13,16 @@ function renderScreenMockups(code) {
|
|
|
13
13
|
|
|
14
14
|
return screens.map(function(screen, si) {
|
|
15
15
|
var resources = screen.resources || [];
|
|
16
|
+
var content = resources.length > 0
|
|
17
|
+
? resources.map(function(res, ri) {
|
|
18
|
+
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
19
|
+
}).join('')
|
|
20
|
+
: '<div class="card" style="padding:1.5rem;color:var(--text-muted);text-align:center;"><p>Maquette en attente de spécification pour cette section.</p></div>';
|
|
16
21
|
return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
|
|
17
22
|
'<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
|
|
18
23
|
'<span style="color:var(--accent);">▸</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
|
|
19
24
|
'</h3>' +
|
|
20
|
-
|
|
21
|
-
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
22
|
-
}).join('') +
|
|
25
|
+
content +
|
|
23
26
|
'</div>';
|
|
24
27
|
}).join('');
|
|
25
28
|
}
|
|
@@ -24,6 +24,24 @@ function renderMermaidDiagrams() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// 1b. MCD (Modèle Conceptuel de Données) — after ERD
|
|
28
|
+
if (diagrams.mcd) {
|
|
29
|
+
var erdContainer = document.getElementById('dataModelContainer');
|
|
30
|
+
if (erdContainer) {
|
|
31
|
+
var mcdDiv = document.createElement('div');
|
|
32
|
+
mcdDiv.className = 'diagram-container diagram-mcd';
|
|
33
|
+
mcdDiv.innerHTML =
|
|
34
|
+
'<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Mod\u00e8le Conceptuel de Donn\u00e9es (MCD)</div>' +
|
|
35
|
+
'<div class="mermaid">' + escapeHtml(diagrams.mcd) + '</div>';
|
|
36
|
+
var erdDiv = erdContainer.querySelector('.diagram-erd');
|
|
37
|
+
if (erdDiv && erdDiv.nextSibling) {
|
|
38
|
+
erdContainer.insertBefore(mcdDiv, erdDiv.nextSibling);
|
|
39
|
+
} else {
|
|
40
|
+
erdContainer.appendChild(mcdDiv);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
// 2. State machine diagrams — inject in module spec sections or consol-datamodel
|
|
28
46
|
if (diagrams.stateMachines && Object.keys(diagrams.stateMachines).length > 0) {
|
|
29
47
|
const smContainer = document.getElementById('dataModelContainer');
|
|
@@ -46,6 +64,34 @@ function renderMermaidDiagrams() {
|
|
|
46
64
|
}
|
|
47
65
|
}
|
|
48
66
|
|
|
67
|
+
// 2b. Use Case diagrams — inject in each module's UC tab
|
|
68
|
+
if (diagrams.useCases && Object.keys(diagrams.useCases).length > 0) {
|
|
69
|
+
Object.entries(diagrams.useCases).forEach(function(entry) {
|
|
70
|
+
var moduleCode = entry[0];
|
|
71
|
+
var def = entry[1];
|
|
72
|
+
var ucTab = document.getElementById('tab-' + moduleCode + '-uc');
|
|
73
|
+
if (!ucTab) {
|
|
74
|
+
// Try kebab-case version
|
|
75
|
+
var kebab = moduleCode.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
76
|
+
ucTab = document.getElementById('tab-' + kebab + '-uc');
|
|
77
|
+
}
|
|
78
|
+
if (ucTab) {
|
|
79
|
+
var ucDiagramDiv = document.createElement('div');
|
|
80
|
+
ucDiagramDiv.className = 'diagram-container diagram-usecase';
|
|
81
|
+
ucDiagramDiv.innerHTML =
|
|
82
|
+
'<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Diagramme de cas d\'utilisation</div>' +
|
|
83
|
+
'<div class="mermaid">' + escapeHtml(def) + '</div>';
|
|
84
|
+
// Insert after the first paragraph (description text)
|
|
85
|
+
var firstP = ucTab.querySelector('p');
|
|
86
|
+
if (firstP && firstP.nextSibling) {
|
|
87
|
+
ucTab.insertBefore(ucDiagramDiv, firstP.nextSibling);
|
|
88
|
+
} else {
|
|
89
|
+
ucTab.insertBefore(ucDiagramDiv, ucTab.firstChild);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
49
95
|
// 3. Sequence diagrams in consol-flows
|
|
50
96
|
if (diagrams.sequences && Object.keys(diagrams.sequences).length > 0) {
|
|
51
97
|
var flowsContainer = document.getElementById('consolFlowsContainer');
|
|
@@ -36,10 +36,12 @@ moduleSpecs[moduleCode] = {
|
|
|
36
36
|
name: br.name || br.id || "",
|
|
37
37
|
sectionCode: br.sectionCode || "",
|
|
38
38
|
category: br.category || "",
|
|
39
|
+
severity: br.severity || "",
|
|
39
40
|
statement: br.statement || br.description || "",
|
|
40
|
-
example: (br.examples || []).map(e =>
|
|
41
|
+
example: (br.examples || (br.example ? [br.example] : [])).map(e =>
|
|
41
42
|
typeof e === 'string' ? e : ((e.input || "") + " → " + (e.expected || ""))
|
|
42
|
-
).join("; ")
|
|
43
|
+
).join("; "),
|
|
44
|
+
domainSpecific: br.domainSpecific || false
|
|
43
45
|
})),
|
|
44
46
|
// ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated
|
|
45
47
|
entities: (mod.entities?.entities || []).map(ent => ({
|
|
@@ -133,6 +133,8 @@ for rendering in the HTML viewer.
|
|
|
133
133
|
```javascript
|
|
134
134
|
mermaidDiagrams: {
|
|
135
135
|
erd: consolidation.mermaidDiagrams?.erd || null, // ERD string or null
|
|
136
|
+
mcd: consolidation.mermaidDiagrams?.mcd || null, // MCD string or null
|
|
137
|
+
useCases: consolidation.mermaidDiagrams?.useCases || {}, // {moduleCode: mermaid_def}
|
|
136
138
|
stateMachines: consolidation.mermaidDiagrams?.stateMachines || {}, // {entity: mermaid_def}
|
|
137
139
|
sequences: consolidation.mermaidDiagrams?.sequences || {} // {flowName: mermaid_def}
|
|
138
140
|
}
|
|
@@ -122,6 +122,7 @@ moduleSpecs[moduleCode] = {
|
|
|
122
122
|
name: uc.name || uc.title || uc.id || "", // Format A: "name", Format B: "title" or "id"
|
|
123
123
|
sectionCode: uc.sectionCode || "",
|
|
124
124
|
actor: uc.primaryActor || uc.actor || "", // Format A: "primaryActor", Format B: "actor"
|
|
125
|
+
description: uc.description || "", // Optional description field
|
|
125
126
|
// SAFETY NET: steps may be string[] or object[] ({step, action})
|
|
126
127
|
steps: (uc.mainScenario || uc.steps || []).map(s =>
|
|
127
128
|
typeof s === 'string' ? s : (s.action || s.description || "")
|
|
@@ -130,7 +131,10 @@ moduleSpecs[moduleCode] = {
|
|
|
130
131
|
(a.name || a.trigger || "") + ": " + (a.steps || a.actions || []).map(s =>
|
|
131
132
|
typeof s === 'string' ? s : (s.action || s.description || "")
|
|
132
133
|
).join(", ")
|
|
133
|
-
).join("\n")
|
|
134
|
+
).join("\n"),
|
|
135
|
+
preconditions: uc.preconditions || [], // Array of precondition objects or strings
|
|
136
|
+
postconditions: uc.postconditions || [], // Array of postcondition objects or strings
|
|
137
|
+
errorScenarios: uc.errorScenarios || [] // Array of error scenario objects
|
|
134
138
|
})),
|
|
135
139
|
businessRules: rawBRs.map(br => ({
|
|
136
140
|
name: br.name || br.id || "",
|
|
@@ -166,19 +170,28 @@ The HTML uses `"Role|Action"` format (e.g. `"RH Admin|Consulter"`):
|
|
|
166
170
|
|
|
167
171
|
```javascript
|
|
168
172
|
// Input: permissions object from flat file (mod.permissions), NOT moduleFeature
|
|
169
|
-
|
|
173
|
+
// lang: optional language override ('fr' or 'en'), defaults to 'fr'
|
|
174
|
+
function buildPermissionKeys(permissionsData, lang = 'fr') {
|
|
170
175
|
const keys = [];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
|
|
177
|
+
// Bilingual actionMap based on language parameter
|
|
178
|
+
const actionMap = lang === 'en'
|
|
179
|
+
? {
|
|
180
|
+
read: 'Read', create: 'Create', update: 'Update', delete: 'Delete',
|
|
181
|
+
approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin',
|
|
182
|
+
Read: 'Read', Create: 'Create', Update: 'Update', Delete: 'Delete',
|
|
183
|
+
Approve: 'Approve', Export: 'Export', Validate: 'Validate', Admin: 'Admin',
|
|
184
|
+
submit: 'Validate', import: 'Create', Submit: 'Validate', Import: 'Create'
|
|
185
|
+
}
|
|
186
|
+
: {
|
|
187
|
+
read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer',
|
|
188
|
+
approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer',
|
|
189
|
+
Read: 'Consulter', Create: 'Créer', Update: 'Modifier', Delete: 'Supprimer',
|
|
190
|
+
Approve: 'Valider', Export: 'Exporter', Validate: 'Valider', Admin: 'Administrer',
|
|
191
|
+
submit: 'Valider', import: 'Créer', Submit: 'Valider', Import: 'Créer'
|
|
192
|
+
};
|
|
179
193
|
|
|
180
194
|
const matrix = permissionsData?.matrix || permissionsData?.permissionMatrix;
|
|
181
|
-
if (!matrix) return keys;
|
|
182
195
|
|
|
183
196
|
// Format A: matrix is array of { role, permissions[] } (ba-009+ format)
|
|
184
197
|
if (Array.isArray(matrix)) {
|
|
@@ -202,25 +215,79 @@ function buildPermissionKeys(permissionsData) {
|
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
// Format B: legacy matrix.roleAssignments[] format
|
|
205
|
-
(matrix.roleAssignments
|
|
206
|
-
(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
if (matrix && matrix.roleAssignments) {
|
|
219
|
+
(matrix.roleAssignments || []).forEach(ra => {
|
|
220
|
+
(ra.permissions || []).forEach(permPath => {
|
|
221
|
+
const action = permPath.split(".").pop();
|
|
222
|
+
if (action === "*") {
|
|
223
|
+
Object.values(actionMap).forEach(uiAction => {
|
|
224
|
+
const key = ra.role + "|" + uiAction;
|
|
225
|
+
if (!keys.includes(key)) keys.push(key);
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
const uiAction = actionMap[action] || action;
|
|
210
229
|
const key = ra.role + "|" + uiAction;
|
|
211
230
|
if (!keys.includes(key)) keys.push(key);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
return keys;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Format C: permissions.json from step-03 with top-level roles[]
|
|
238
|
+
if (keys.length === 0 && permissionsData?.roles) {
|
|
239
|
+
const actionMap_C = lang === 'en'
|
|
240
|
+
? { read: 'Read', create: 'Create', update: 'Update', delete: 'Delete', approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin' }
|
|
241
|
+
: { read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer', approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer' };
|
|
242
|
+
|
|
243
|
+
permissionsData.roles.forEach(entry => {
|
|
244
|
+
const role = entry.role || entry.name || '';
|
|
245
|
+
(entry.permissions || entry.actions || []).forEach(perm => {
|
|
246
|
+
const action = typeof perm === 'string' ? perm.split('.').pop().toLowerCase() : (perm.action || '').toLowerCase();
|
|
247
|
+
const uiAction = actionMap_C[action] || action;
|
|
248
|
+
const key = role + '|' + uiAction;
|
|
249
|
+
if (role && !keys.includes(key)) keys.push(key);
|
|
250
|
+
});
|
|
251
|
+
// Handle wildcard patterns like "HumanResources.*"
|
|
252
|
+
if ((entry.permissionPattern || '').endsWith('*')) {
|
|
253
|
+
Object.values(actionMap_C).forEach(a => {
|
|
254
|
+
const key = (entry.role || entry.name) + '|' + a;
|
|
255
|
+
if (!keys.includes(key)) keys.push(key);
|
|
212
256
|
});
|
|
213
|
-
} else {
|
|
214
|
-
const uiAction = actionMap[action] || action;
|
|
215
|
-
keys.push(ra.role + "|" + uiAction);
|
|
216
257
|
}
|
|
217
258
|
});
|
|
218
|
-
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Format D: permissionMatrix.roleAssignments[]
|
|
262
|
+
if (keys.length === 0 && permissionsData?.permissionMatrix?.roleAssignments) {
|
|
263
|
+
const actionMap_D = lang === 'en'
|
|
264
|
+
? { read: 'Read', create: 'Create', update: 'Update', delete: 'Delete', approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin' }
|
|
265
|
+
: { read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer', approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer' };
|
|
266
|
+
|
|
267
|
+
permissionsData.permissionMatrix.roleAssignments.forEach(entry => {
|
|
268
|
+
const role = entry.role || '';
|
|
269
|
+
(entry.permissions || []).forEach(perm => {
|
|
270
|
+
if (typeof perm === 'string' && perm.endsWith('.*')) {
|
|
271
|
+
// Wildcard: expand to all standard actions
|
|
272
|
+
Object.values(actionMap_D).forEach(a => {
|
|
273
|
+
const key = role + '|' + a;
|
|
274
|
+
if (!keys.includes(key)) keys.push(key);
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
const action = typeof perm === 'string' ? perm.split('.').pop().toLowerCase() : '';
|
|
278
|
+
const uiAction = actionMap_D[action] || action;
|
|
279
|
+
const key = role + '|' + uiAction;
|
|
280
|
+
if (role && action && !keys.includes(key)) keys.push(key);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
219
286
|
return keys;
|
|
220
287
|
}
|
|
221
288
|
```
|
|
222
289
|
|
|
223
|
-
> **Language handling:** The
|
|
290
|
+
> **Language handling:** The function accepts an optional `lang` parameter ('fr' or 'en'). Defaults to 'fr' (French). For English labels: Read/Create/Update/Delete/Export/Validate. For French: Consulter/Créer/Modifier/Supprimer/Exporter/Valider.
|
|
224
291
|
|
|
225
292
|
### Frequency Mapping
|
|
226
293
|
|
|
@@ -64,6 +64,12 @@ This step is NEVER blocking — ASCII-only wireframes are acceptable.
|
|
|
64
64
|
- Wireframe fields use format/content (NOT mockupFormat/mockup)
|
|
65
65
|
- Per-module: useCases/businessRules/entities count must match source (empty when source has data = BUG)
|
|
66
66
|
- dependencies[] must be present (even if empty) to prevent HTML crashes
|
|
67
|
+
- **Permissions:** `buildPermissionKeys()` supports 4 formats:
|
|
68
|
+
- **Format A:** `permissions.matrix[]` with `{role, permissions[]}`
|
|
69
|
+
- **Format B:** `permissions.permissionMatrix.roleAssignments[]`
|
|
70
|
+
- **Format C:** `permissions.roles[]` with `{role, permissions[]}`
|
|
71
|
+
- **Format D:** `permissions.permissionMatrix.roleAssignments[]` with wildcard expansion
|
|
72
|
+
- Pass optional `lang` parameter ('fr'|'en') for bilingual labels. Defaults to 'fr'.
|
|
67
73
|
|
|
68
74
|
## NEXT STEP
|
|
69
75
|
|
|
@@ -8,7 +8,7 @@ model: opus
|
|
|
8
8
|
|
|
9
9
|
## YOUR TASK
|
|
10
10
|
|
|
11
|
-
Run
|
|
11
|
+
Run 16 blocking validations on the generated HTML file and display the completion summary.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -105,11 +105,97 @@ IF "modules" is missing:
|
|
|
105
105
|
BLOCKING_ERROR("FEATURE_DATA.modules missing — page will crash")
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
> **IF any check fails:** fix the issue and re-run the failing step before completing.
|
|
108
|
+
> **IF any check fails (1-9):** fix the issue and re-run the failing step before completing.
|
|
109
|
+
|
|
110
|
+
### 2. Content Quality Validations (NEW)
|
|
111
|
+
|
|
112
|
+
**Check 10 — Language coherence:**
|
|
113
|
+
```
|
|
114
|
+
Read FEATURE_DATA.metadata.language (or config.json language field)
|
|
115
|
+
IF language is set (e.g., "fr"):
|
|
116
|
+
FOR each module in FEATURE_DATA.modules:
|
|
117
|
+
const spec = FEATURE_DATA.moduleSpecs[module.code]
|
|
118
|
+
|
|
119
|
+
// Check UC names
|
|
120
|
+
FOR each uc in spec.useCases:
|
|
121
|
+
IF language == "fr" AND uc.name matches /^(Create|Update|Delete|View|List|Manage|Submit|Approve|Reject|Export)\b/:
|
|
122
|
+
WARNING("Module {module.code}: UC '{uc.name}' appears English (expected French)")
|
|
123
|
+
|
|
124
|
+
// Check BR statements
|
|
125
|
+
FOR each br in spec.businessRules:
|
|
126
|
+
IF language == "fr" AND br.statement matches /\b(must|should|cannot|when|if the|is required)\b/i:
|
|
127
|
+
WARNING("Module {module.code}: BR '{br.name}' statement appears English (expected French)")
|
|
128
|
+
|
|
129
|
+
Count total warnings. IF > 20% of items are in wrong language:
|
|
130
|
+
WARNING("⚠ Significant language mismatch: {count} items in wrong language")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Check 11 — Business rules have examples:**
|
|
134
|
+
```
|
|
135
|
+
FOR each module in FEATURE_DATA.modules:
|
|
136
|
+
const brs = FEATURE_DATA.moduleSpecs[module.code].businessRules || []
|
|
137
|
+
IF brs.length === 0: SKIP
|
|
138
|
+
const withExamples = brs.filter(br => br.example && br.example.trim() !== '')
|
|
139
|
+
const rate = withExamples.length / brs.length
|
|
140
|
+
IF rate < 0.5:
|
|
141
|
+
WARNING("Module {module.code}: only {withExamples.length}/{brs.length} BRs have examples ({rate*100}%)")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Check 12 — Entity attributes have type field:**
|
|
145
|
+
```
|
|
146
|
+
FOR each module in FEATURE_DATA.modules:
|
|
147
|
+
const entities = FEATURE_DATA.moduleSpecs[module.code].entities || []
|
|
148
|
+
FOR each entity in entities:
|
|
149
|
+
const attrs = entity.attributes || []
|
|
150
|
+
const withType = attrs.filter(a => a.type && a.type.trim() !== '' && a.type !== 'string')
|
|
151
|
+
IF attrs.length > 3 AND withType.length === 0:
|
|
152
|
+
WARNING("Module {module.code}: entity '{entity.name}' — all {attrs.length} attributes default to 'string' (types may be missing)")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Check 13 — Permissions populated per module:**
|
|
156
|
+
```
|
|
157
|
+
FOR each module in FEATURE_DATA.modules:
|
|
158
|
+
const perms = FEATURE_DATA.moduleSpecs[module.code].permissions || []
|
|
159
|
+
IF perms.length === 0:
|
|
160
|
+
WARNING("Module {module.code}: 0 permissions — permission grid will be empty")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Check 14 — Mockups not empty (screens with actual resources):**
|
|
164
|
+
```
|
|
165
|
+
FOR each module in FEATURE_DATA.modules:
|
|
166
|
+
const screens = FEATURE_DATA.moduleSpecs[module.code].screens || []
|
|
167
|
+
const wireframes = EMBEDDED_ARTIFACTS.wireframes?.[module.code] || []
|
|
168
|
+
IF screens.length === 0 AND wireframes.length === 0:
|
|
169
|
+
WARNING("Module {module.code}: no screens and no wireframes — Mockups tab will show 'Aucune maquette'")
|
|
170
|
+
ELSE IF screens.length > 0:
|
|
171
|
+
const totalResources = screens.reduce((sum, s) => sum + (s.resources || []).length, 0)
|
|
172
|
+
IF totalResources === 0:
|
|
173
|
+
WARNING("Module {module.code}: has screens[] but 0 resources — mockups will appear empty despite data existing")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Check 15 — MCD diagram present (multi-module projects):**
|
|
177
|
+
```
|
|
178
|
+
const diagrams = FEATURE_DATA.consolidation?.mermaidDiagrams || {}
|
|
179
|
+
IF FEATURE_DATA.modules.length > 1:
|
|
180
|
+
IF !diagrams.erd:
|
|
181
|
+
WARNING("No ERD diagram — data model section will have no visual")
|
|
182
|
+
IF !diagrams.mcd:
|
|
183
|
+
WARNING("No MCD diagram — conceptual model not generated")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Check 16 — Use Case diagrams present:**
|
|
187
|
+
```
|
|
188
|
+
const ucDiagrams = diagrams.useCases || {}
|
|
189
|
+
const totalUCs = Object.values(FEATURE_DATA.moduleSpecs).reduce((sum, s) => sum + (s.useCases?.length || 0), 0)
|
|
190
|
+
IF Object.keys(ucDiagrams).length === 0 AND totalUCs > 0:
|
|
191
|
+
WARNING("No use case diagrams generated — UC tabs will have no UML visual")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> **Checks 10-16 produce WARNINGs (non-blocking).** Display all warnings in the completion summary so the user is aware of potential quality issues.
|
|
109
195
|
|
|
110
196
|
---
|
|
111
197
|
|
|
112
|
-
###
|
|
198
|
+
### 3. Display Completion Summary
|
|
113
199
|
|
|
114
200
|
```
|
|
115
201
|
══════════════════════════════════════════════════════════════
|
|
@@ -131,5 +217,8 @@ Modules: {count} ({names})
|
|
|
131
217
|
4. If approved: /business-analyse-handoff then /business-analyse-develop to begin development
|
|
132
218
|
5. If corrections needed: export ba-review.json from the HTML, then run /business-analyse-review
|
|
133
219
|
|
|
220
|
+
Quality Warnings: {warning_count}
|
|
221
|
+
{FOR each warning: display warning message}
|
|
222
|
+
|
|
134
223
|
══════════════════════════════════════════════════════════════
|
|
135
224
|
```
|