@atlashub/smartstack-cli 4.50.0 → 4.52.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 +53 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/skills/apex/SKILL.md +30 -0
- package/templates/skills/apex/references/core-seed-data.md +15 -0
- package/templates/skills/apex/references/error-classification.md +27 -3
- package/templates/skills/apex/references/post-checks.md +3 -1
- package/templates/skills/apex/steps/step-00-init.md +57 -0
- package/templates/skills/apex/steps/step-03-execute.md +40 -5
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +4 -1
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +22 -1
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +4 -1
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +7 -1
- package/templates/skills/apex/steps/step-04-examine.md +35 -0
- package/templates/skills/business-analyse-develop/references/init-resume-recovery.md +54 -0
- package/templates/skills/business-analyse-develop/steps/step-00-init.md +10 -3
- package/templates/skills/business-analyse-develop/steps/step-01-task.md +14 -2
- package/templates/skills/business-analyse-develop/steps/step-04-check.md +12 -2
- package/templates/skills/business-analyse-handoff/references/entity-canonicalization.md +158 -0
- package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +14 -0
- package/templates/skills/business-analyse-handoff/steps/step-02-export.md +14 -0
- package/templates/skills/business-analyse-html/SKILL.md +4 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +7 -0
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +7 -0
- package/templates/skills/business-analyse-html/references/data-build.md +24 -17
- package/templates/skills/business-analyse-html/references/data-mapping.md +79 -35
- package/templates/skills/business-analyse-html/references/output-modes.md +2 -1
- package/templates/skills/business-analyse-html/steps/step-01-collect.md +7 -2
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +168 -40
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +39 -3
|
@@ -111,20 +111,34 @@ For EACH module, use the flat file data from `collected_data.modules[moduleCode]
|
|
|
111
111
|
```javascript
|
|
112
112
|
const mod = collected_data.modules[moduleCode];
|
|
113
113
|
|
|
114
|
+
// NORMALIZE: usecases.json may use "useCases" (camelCase) or "usecases" (lowercase)
|
|
115
|
+
const rawUCs = mod.usecases?.useCases || mod.usecases?.usecases || [];
|
|
116
|
+
// NORMALIZE: rules.json may use "rules" or "businessRules"
|
|
117
|
+
const rawBRs = mod.rules?.rules || mod.rules?.businessRules || [];
|
|
118
|
+
|
|
114
119
|
moduleSpecs[moduleCode] = {
|
|
115
|
-
useCases:
|
|
116
|
-
name: uc.name,
|
|
120
|
+
useCases: rawUCs.map(uc => ({
|
|
121
|
+
name: uc.name || uc.title || uc.id || "",
|
|
117
122
|
sectionCode: uc.sectionCode || "",
|
|
118
|
-
actor: uc.primaryActor,
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
actor: uc.primaryActor || uc.actor || "",
|
|
124
|
+
// SAFETY NET: steps may be strings[] (mainScenario) or objects[] ({step, action})
|
|
125
|
+
steps: (uc.mainScenario || uc.steps || []).map(s =>
|
|
126
|
+
typeof s === 'string' ? s : (s.action || s.description || "")
|
|
127
|
+
).join("\n"),
|
|
128
|
+
alternative: (uc.alternativeScenarios || uc.alternativeFlows || []).map(a =>
|
|
129
|
+
(a.name || a.trigger || "") + ": " + (a.steps || a.actions || []).map(s =>
|
|
130
|
+
typeof s === 'string' ? s : (s.action || s.description || "")
|
|
131
|
+
).join(", ")
|
|
132
|
+
).join("\n")
|
|
121
133
|
})),
|
|
122
|
-
businessRules:
|
|
123
|
-
name: br.name,
|
|
134
|
+
businessRules: rawBRs.map(br => ({
|
|
135
|
+
name: br.name || br.id || "",
|
|
124
136
|
sectionCode: br.sectionCode || "",
|
|
125
|
-
category: br.category,
|
|
126
|
-
statement: br.statement,
|
|
127
|
-
example: (br.examples || []).map(e =>
|
|
137
|
+
category: br.category || "",
|
|
138
|
+
statement: br.statement || br.description || "",
|
|
139
|
+
example: (br.examples || []).map(e =>
|
|
140
|
+
typeof e === 'string' ? e : ((e.input || "") + " → " + (e.expected || ""))
|
|
141
|
+
).join("; ")
|
|
128
142
|
})),
|
|
129
143
|
// ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated
|
|
130
144
|
entities: (mod.entities?.entities || []).map(ent => ({
|
|
@@ -141,9 +155,45 @@ moduleSpecs[moduleCode] = {
|
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
// BUILD screens[] for HTML interactive mockups (SmartTable/SmartForm rendering)
|
|
158
|
+
// NORMALIZE: screens.json may use "screens[]" (flat) or "sections[]" (nested) format
|
|
144
159
|
const flatScr = mod.screens?.screens || [];
|
|
160
|
+
const sectionScr = mod.screens?.sections || [];
|
|
145
161
|
let screens = [];
|
|
162
|
+
|
|
163
|
+
// Helper: normalize a column definition regardless of field naming convention
|
|
164
|
+
function normalizeColumn(c) {
|
|
165
|
+
return {
|
|
166
|
+
field: c.code || c.name || c.id || c.field || c.fieldCode || "",
|
|
167
|
+
label: c.label || c.displayName || c.code || c.name || "",
|
|
168
|
+
type: c.renderAs === "badge" ? "badge" : (c.type || c.dataType || "text"),
|
|
169
|
+
sortable: !!c.sortable
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Helper: normalize a form field
|
|
174
|
+
function normalizeField(f) {
|
|
175
|
+
return {
|
|
176
|
+
field: f.code || f.name || f.id || f.fieldCode || f.field || "",
|
|
177
|
+
label: f.label || f.displayName || f.code || f.name || "",
|
|
178
|
+
type: f.type || f.fieldType || f.dataType || "text",
|
|
179
|
+
required: !!f.required
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Helper: normalize a filter
|
|
184
|
+
function normalizeFilter(f) {
|
|
185
|
+
if (typeof f === 'string') return f;
|
|
186
|
+
return f.label || f.displayName || f.filterLabel || f.name || f.code || f.filterCode || "";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Helper: normalize an action
|
|
190
|
+
function normalizeAction(a) {
|
|
191
|
+
if (typeof a === 'string') return a;
|
|
192
|
+
return a.code || a.id || a.label || a.actionCode || "";
|
|
193
|
+
}
|
|
194
|
+
|
|
146
195
|
if (flatScr.length > 0) {
|
|
196
|
+
// FORMAT A: flat screens[] array (ba-003 style)
|
|
147
197
|
const bySec = {};
|
|
148
198
|
flatScr.forEach(s => {
|
|
149
199
|
const sec = s.section || "default";
|
|
@@ -156,28 +206,54 @@ if (flatScr.length > 0) {
|
|
|
156
206
|
code: s.screen || s.name || "",
|
|
157
207
|
label: s.sectionLabel || s.description || s.screen || "",
|
|
158
208
|
type: s.componentType || "unknown",
|
|
159
|
-
columns: (s.columns || []).map(
|
|
160
|
-
|
|
161
|
-
type: c.type || c.dataType || "text", sortable: !!c.sortable
|
|
162
|
-
})),
|
|
163
|
-
filters: (s.filters || []).map(f =>
|
|
164
|
-
typeof f === 'string' ? f : (f.label || f.filterLabel || f.code || f.filterCode || "")),
|
|
209
|
+
columns: (s.columns || []).map(normalizeColumn),
|
|
210
|
+
filters: (s.filters || []).map(normalizeFilter),
|
|
165
211
|
fields: [],
|
|
166
212
|
tabs: (s.tabs || []).map(t => ({
|
|
167
213
|
label: t.label || t.tabLabel || t.code || "",
|
|
168
|
-
fields: (t.fields || []).map(
|
|
169
|
-
field: f.code || f.fieldCode || f.field || "",
|
|
170
|
-
label: f.label || f.code || "",
|
|
171
|
-
type: f.type || f.dataType || "text",
|
|
172
|
-
required: !!f.required
|
|
173
|
-
}))
|
|
214
|
+
fields: (t.fields || []).map(normalizeField)
|
|
174
215
|
})),
|
|
175
|
-
actions: (s.actions || []).map(
|
|
176
|
-
|
|
177
|
-
|
|
216
|
+
actions: (s.actions || []).map(normalizeAction),
|
|
217
|
+
kpis: (s.kpis || []).map(k => typeof k === 'object'
|
|
218
|
+
? { label: k.label || k.displayName || "", value: k.value || k.metric || "0" }
|
|
219
|
+
: { label: String(k), value: "0" }),
|
|
178
220
|
options: s.options || [],
|
|
179
221
|
permission: s.permission || "",
|
|
180
|
-
notes: s.description || ""
|
|
222
|
+
notes: s.description || s.sectionDescription || ""
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
screens = Object.values(bySec);
|
|
226
|
+
} else if (sectionScr.length > 0) {
|
|
227
|
+
// FORMAT B: sections[] array (ba-004 style — id/displayName/layout convention)
|
|
228
|
+
// Each section IS a screen resource (columns, tabs, filters are ON the section itself)
|
|
229
|
+
const bySec = {};
|
|
230
|
+
sectionScr.forEach(sec => {
|
|
231
|
+
const secId = sec.id || sec.code || sec.sectionCode || "";
|
|
232
|
+
const secLabel = sec.displayName || sec.label || sec.sectionLabel || secId;
|
|
233
|
+
const componentType = sec.layout || sec.componentType || "unknown";
|
|
234
|
+
if (!bySec[secId]) bySec[secId] = {
|
|
235
|
+
sectionCode: secId,
|
|
236
|
+
sectionLabel: secLabel,
|
|
237
|
+
resources: []
|
|
238
|
+
};
|
|
239
|
+
bySec[secId].resources.push({
|
|
240
|
+
code: secId,
|
|
241
|
+
label: secLabel,
|
|
242
|
+
type: componentType,
|
|
243
|
+
columns: (sec.columns || []).map(normalizeColumn),
|
|
244
|
+
filters: (sec.filters || []).map(normalizeFilter),
|
|
245
|
+
fields: [],
|
|
246
|
+
tabs: (sec.tabs || []).map(t => ({
|
|
247
|
+
label: t.displayName || t.label || t.code || t.id || "",
|
|
248
|
+
fields: (t.fields || []).map(normalizeField)
|
|
249
|
+
})),
|
|
250
|
+
actions: (sec.actions || []).map(normalizeAction),
|
|
251
|
+
kpis: (sec.kpis || []).map(k => typeof k === 'object'
|
|
252
|
+
? { label: k.displayName || k.label || "", value: k.format === "percentage" ? "80%" : "1,234" }
|
|
253
|
+
: { label: String(k), value: "0" }),
|
|
254
|
+
options: sec.options || [],
|
|
255
|
+
permission: (typeof sec.permissions === 'object' ? sec.permissions?.view : sec.permission) || "",
|
|
256
|
+
notes: sec.description || ""
|
|
181
257
|
});
|
|
182
258
|
});
|
|
183
259
|
screens = Object.values(bySec);
|
|
@@ -216,20 +292,36 @@ FEATURE_DATA.modules.forEach(m => {
|
|
|
216
292
|
const source = collected_data.modules[m.code];
|
|
217
293
|
if (!spec) { errors.push(`MISSING moduleSpecs[${m.code}]`); return; }
|
|
218
294
|
|
|
219
|
-
|
|
295
|
+
// Check useCases (handle both key conventions)
|
|
296
|
+
const srcUC = (source?.usecases?.useCases || source?.usecases?.usecases || []).length;
|
|
220
297
|
const bltUC = (spec.useCases || []).length;
|
|
221
298
|
if (srcUC > 0 && bltUC === 0)
|
|
222
299
|
errors.push(`${m.code}: useCases EMPTY but source has ${srcUC}`);
|
|
223
300
|
|
|
224
|
-
|
|
301
|
+
// Check businessRules (handle both key conventions)
|
|
302
|
+
const srcBR = (source?.rules?.rules || source?.rules?.businessRules || []).length;
|
|
225
303
|
const bltBR = (spec.businessRules || []).length;
|
|
226
304
|
if (srcBR > 0 && bltBR === 0)
|
|
227
305
|
errors.push(`${m.code}: businessRules EMPTY but source has ${srcBR}`);
|
|
228
306
|
|
|
307
|
+
// Check entities
|
|
229
308
|
const srcEnt = (source?.entities?.entities || []).length;
|
|
230
309
|
const bltEnt = (spec.entities || []).length;
|
|
231
310
|
if (srcEnt > 0 && bltEnt === 0)
|
|
232
311
|
errors.push(`${m.code}: entities EMPTY but source has ${srcEnt}`);
|
|
312
|
+
|
|
313
|
+
// Check screens (handle both formats: screens[] and sections[])
|
|
314
|
+
const srcScreens = (source?.screens?.screens || source?.screens?.sections || []).length;
|
|
315
|
+
const bltScreenResources = (spec.screens || []).reduce(
|
|
316
|
+
(acc, s) => acc + (s.resources || []).length, 0);
|
|
317
|
+
if (srcScreens > 0 && bltScreenResources === 0)
|
|
318
|
+
errors.push(`${m.code}: screens EMPTY (0 resources) but source has ${srcScreens} screens/sections`);
|
|
319
|
+
|
|
320
|
+
// Check serialization: [object Object] in useCases steps = BUG
|
|
321
|
+
(spec.useCases || []).forEach(uc => {
|
|
322
|
+
if (uc.steps && uc.steps.includes("[object Object]"))
|
|
323
|
+
errors.push(`${m.code}: UC "${uc.name}" has [object Object] in steps — steps[] contains objects not strings`);
|
|
324
|
+
});
|
|
233
325
|
});
|
|
234
326
|
|
|
235
327
|
if (errors.length > 0) {
|
|
@@ -242,6 +334,19 @@ if (errors.length > 0) {
|
|
|
242
334
|
> If self-check detects errors, you MUST re-read the source data and re-build the failing moduleSpecs entries.
|
|
243
335
|
> Do NOT proceed with empty arrays when source data exists.
|
|
244
336
|
|
|
337
|
+
**dependencies[] (TOP-LEVEL — CRITICAL for HTML navigation):**
|
|
338
|
+
```javascript
|
|
339
|
+
dependencies: (master.consolidation?.crossModuleInteractions || master.dependencyGraph?.edges || []).map(i => ({
|
|
340
|
+
from: i.fromModule || i.from || "",
|
|
341
|
+
to: i.toModule || i.to || "",
|
|
342
|
+
description: i.description || ""
|
|
343
|
+
}))
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
> **WARNING:** `data.dependencies` is used by the sidebar (navigation), module rendering, consolidation views,
|
|
347
|
+
> handoff summary, and export. If this array is missing, the entire HTML page crashes with a TypeError.
|
|
348
|
+
> ALWAYS include it, even if empty (`dependencies: []`).
|
|
349
|
+
|
|
245
350
|
**consolidation:**
|
|
246
351
|
```javascript
|
|
247
352
|
consolidation: {
|
|
@@ -297,39 +402,62 @@ rawWireframes.push(...flatScreens.filter(s => s.mockup || s.mockupFormat));
|
|
|
297
402
|
// Source B: nested sections[].resources[].wireframe (design step output)
|
|
298
403
|
const sections = mod.screens?.sections || [];
|
|
299
404
|
for (const section of sections) {
|
|
405
|
+
// B1: wireframe directly on section (ba-004 style: sections[].wireframe)
|
|
406
|
+
if (section.wireframe) {
|
|
407
|
+
rawWireframes.push({
|
|
408
|
+
...section.wireframe,
|
|
409
|
+
section: section.wireframe.section || section.id || section.code || section.sectionCode || "",
|
|
410
|
+
screen: section.wireframe.screen || section.id || section.code || section.sectionCode || ""
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// B2: wireframe on resources (nested: sections[].resources[].wireframe)
|
|
300
414
|
for (const resource of (section.resources || [])) {
|
|
301
415
|
if (resource.wireframe) {
|
|
302
|
-
// Unwrap nested wireframe and add section context
|
|
303
416
|
rawWireframes.push({
|
|
304
417
|
...resource.wireframe,
|
|
305
|
-
section: resource.wireframe.section || section.sectionCode || "",
|
|
306
|
-
screen: resource.wireframe.screen || `${section.sectionCode}-${resource.code}` || ""
|
|
418
|
+
section: resource.wireframe.section || section.sectionCode || section.id || "",
|
|
419
|
+
screen: resource.wireframe.screen || `${section.sectionCode || section.id}-${resource.code}` || ""
|
|
307
420
|
});
|
|
308
421
|
}
|
|
309
422
|
}
|
|
310
423
|
}
|
|
311
424
|
|
|
312
|
-
// Source C: screens without mockup → auto-generate text description as wireframe fallback
|
|
425
|
+
// Source C: screens/sections without mockup → auto-generate text description as wireframe fallback
|
|
426
|
+
// C1: from flat screens[]
|
|
313
427
|
const screensWithoutMockup = flatScreens.filter(s => !s.mockup && !s.mockupFormat);
|
|
314
|
-
|
|
315
|
-
|
|
428
|
+
// C2: from sections[] (ba-004 style) — sections without wireframe property
|
|
429
|
+
const sectionsWithoutWireframe = sections.filter(s => !s.wireframe);
|
|
430
|
+
const allNoMockup = [
|
|
431
|
+
...screensWithoutMockup.map(s => ({
|
|
432
|
+
type: s.componentType || "", columns: s.columns || [], tabs: s.tabs || [],
|
|
433
|
+
kpis: s.kpis || [], screen: s.screen || "", section: s.section || "",
|
|
434
|
+
description: s.sectionDescription || ""
|
|
435
|
+
})),
|
|
436
|
+
...sectionsWithoutWireframe.map(s => ({
|
|
437
|
+
type: s.layout || s.componentType || "", columns: s.columns || [], tabs: s.tabs || [],
|
|
438
|
+
kpis: s.kpis || [], screen: s.id || s.code || "", section: s.id || s.code || "",
|
|
439
|
+
description: s.description || ""
|
|
440
|
+
}))
|
|
441
|
+
];
|
|
442
|
+
for (const screen of allNoMockup) {
|
|
443
|
+
const type = screen.type;
|
|
316
444
|
let desc = "";
|
|
317
445
|
if (type.includes("Table") || type.includes("Grid")) {
|
|
318
|
-
const cols = screen.columns
|
|
319
|
-
desc = "Tableau avec " + cols.length + " colonnes : " + cols.map(c => c.label || c.code).join(", ");
|
|
446
|
+
const cols = screen.columns;
|
|
447
|
+
desc = "Tableau avec " + cols.length + " colonnes : " + cols.map(c => c.label || c.displayName || c.code || c.name).join(", ");
|
|
320
448
|
} else if (type.includes("Form")) {
|
|
321
|
-
const tabs = screen.tabs
|
|
449
|
+
const tabs = screen.tabs;
|
|
322
450
|
desc = "Formulaire " + (tabs.length > 0 ? "avec " + tabs.length + " onglet(s)" : "");
|
|
323
451
|
} else if (type.includes("Dashboard")) {
|
|
324
|
-
desc = "Tableau de bord avec KPIs";
|
|
452
|
+
desc = "Tableau de bord avec " + (screen.kpis.length || "") + " KPIs";
|
|
325
453
|
} else if (type.includes("Kanban")) {
|
|
326
454
|
desc = "Vue Kanban";
|
|
327
455
|
}
|
|
328
456
|
if (desc) {
|
|
329
457
|
rawWireframes.push({
|
|
330
|
-
screen: screen.screen
|
|
458
|
+
screen: screen.screen, section: screen.section,
|
|
331
459
|
mockupFormat: "text", mockup: desc,
|
|
332
|
-
description: screen.
|
|
460
|
+
description: screen.description, elements: [], componentMapping: []
|
|
333
461
|
});
|
|
334
462
|
}
|
|
335
463
|
}
|
|
@@ -8,7 +8,7 @@ model: opus
|
|
|
8
8
|
|
|
9
9
|
## YOUR TASK
|
|
10
10
|
|
|
11
|
-
Run
|
|
11
|
+
Run 9 blocking validations on the generated HTML file and display the completion summary.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -57,18 +57,54 @@ FOR each module in FEATURE_DATA.modules:
|
|
|
57
57
|
```
|
|
58
58
|
FOR each module in FEATURE_DATA.modules:
|
|
59
59
|
Read source: docs/{app}/{module.code}/business-analyse/v{version}/usecases.json
|
|
60
|
-
sourceUCCount = count of useCases in source file
|
|
60
|
+
sourceUCCount = count of useCases OR usecases in source file
|
|
61
61
|
htmlUCCount = count in FEATURE_DATA.moduleSpecs[module.code].useCases
|
|
62
62
|
IF sourceUCCount > 0 AND htmlUCCount === 0:
|
|
63
63
|
BLOCKING_ERROR("Module {module.code}: 0 useCases in HTML but {sourceUCCount} in source")
|
|
64
64
|
|
|
65
65
|
Read source: docs/{app}/{module.code}/business-analyse/v{version}/rules.json
|
|
66
|
-
sourceBRCount = count of rules in source file
|
|
66
|
+
sourceBRCount = count of rules OR businessRules in source file
|
|
67
67
|
htmlBRCount = count in FEATURE_DATA.moduleSpecs[module.code].businessRules
|
|
68
68
|
IF sourceBRCount > 0 AND htmlBRCount === 0:
|
|
69
69
|
BLOCKING_ERROR("Module {module.code}: 0 businessRules in HTML but {sourceBRCount} in source")
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
**Check 7 — Screens/mockups populated (prevents ASCII-only regression):**
|
|
73
|
+
```
|
|
74
|
+
FOR each module in FEATURE_DATA.modules:
|
|
75
|
+
Read source: docs/{app}/{module.code}/business-analyse/v{version}/screens.json
|
|
76
|
+
sourceScreenCount = count of "screens" array OR "sections" array in source file
|
|
77
|
+
htmlScreenResources = sum of resources[].length across moduleSpecs[module.code].screens[]
|
|
78
|
+
IF sourceScreenCount > 0 AND htmlScreenResources === 0:
|
|
79
|
+
BLOCKING_ERROR("Module {module.code}: 0 screen resources but source has {sourceScreenCount}")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Check 8 — No serialization bugs ([object Object]):**
|
|
83
|
+
```
|
|
84
|
+
Search ba-interactive.html for the literal string "[object Object]"
|
|
85
|
+
IF found:
|
|
86
|
+
BLOCKING_ERROR("Serialization bug: [object Object] found in HTML — steps[] or other arrays contain objects instead of strings")
|
|
87
|
+
Identify which module/field contains the bug and fix the mapping
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Check 9 — No runtime crash (CRITICAL — validates page won't be blank):**
|
|
91
|
+
```
|
|
92
|
+
Open ba-interactive.html and search for the FEATURE_DATA JSON block.
|
|
93
|
+
Verify these MANDATORY top-level fields exist:
|
|
94
|
+
- "modules": [...] (array, can be empty)
|
|
95
|
+
- "dependencies": [...] (array, can be empty)
|
|
96
|
+
- "cadrage": {...} (object with stakeholders[], scope{})
|
|
97
|
+
- "moduleSpecs": {...} (object, one key per module)
|
|
98
|
+
- "consolidation": {...} (object)
|
|
99
|
+
|
|
100
|
+
IF "dependencies" is missing from FEATURE_DATA:
|
|
101
|
+
Add: "dependencies": [] to the FEATURE_DATA object in the HTML
|
|
102
|
+
(This is the #1 cause of blank HTML pages — TypeError on data.dependencies.length crashes all JS)
|
|
103
|
+
|
|
104
|
+
IF "modules" is missing:
|
|
105
|
+
BLOCKING_ERROR("FEATURE_DATA.modules missing — page will crash")
|
|
106
|
+
```
|
|
107
|
+
|
|
72
108
|
> **IF any check fails:** fix the issue and re-run the failing step before completing.
|
|
73
109
|
|
|
74
110
|
---
|