@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.
Files changed (30) hide show
  1. package/dist/index.js +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/skills/apex/SKILL.md +30 -0
  5. package/templates/skills/apex/references/core-seed-data.md +15 -0
  6. package/templates/skills/apex/references/error-classification.md +27 -3
  7. package/templates/skills/apex/references/post-checks.md +3 -1
  8. package/templates/skills/apex/steps/step-00-init.md +57 -0
  9. package/templates/skills/apex/steps/step-03-execute.md +40 -5
  10. package/templates/skills/apex/steps/step-03a-layer0-domain.md +4 -1
  11. package/templates/skills/apex/steps/step-03b-layer1-seed.md +22 -1
  12. package/templates/skills/apex/steps/step-03c-layer2-backend.md +4 -1
  13. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +7 -1
  14. package/templates/skills/apex/steps/step-04-examine.md +35 -0
  15. package/templates/skills/business-analyse-develop/references/init-resume-recovery.md +54 -0
  16. package/templates/skills/business-analyse-develop/steps/step-00-init.md +10 -3
  17. package/templates/skills/business-analyse-develop/steps/step-01-task.md +14 -2
  18. package/templates/skills/business-analyse-develop/steps/step-04-check.md +12 -2
  19. package/templates/skills/business-analyse-handoff/references/entity-canonicalization.md +158 -0
  20. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +14 -0
  21. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +14 -0
  22. package/templates/skills/business-analyse-html/SKILL.md +4 -0
  23. package/templates/skills/business-analyse-html/html/ba-interactive.html +7 -0
  24. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +7 -0
  25. package/templates/skills/business-analyse-html/references/data-build.md +24 -17
  26. package/templates/skills/business-analyse-html/references/data-mapping.md +79 -35
  27. package/templates/skills/business-analyse-html/references/output-modes.md +2 -1
  28. package/templates/skills/business-analyse-html/steps/step-01-collect.md +7 -2
  29. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +168 -40
  30. 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: (mod.usecases?.useCases || []).map(uc => ({
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
- steps: (uc.mainScenario || []).join("\n"),
120
- alternative: (uc.alternativeScenarios || []).map(a => a.name + ": " + (a.steps || []).join(", ")).join("\n")
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: (mod.rules?.rules || []).map(br => ({
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 => e.input + " → " + e.expected).join("; ")
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(c => ({
160
- field: c.code || c.field || "", label: c.label || c.code || "",
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(f => ({
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(a =>
176
- typeof a === 'string' ? a : (a.code || a.label || a.actionCode || "")),
177
- kpis: s.kpis || [],
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
- const srcUC = (source?.usecases?.useCases || []).length;
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
- const srcBR = (source?.rules?.rules || []).length;
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
- for (const screen of screensWithoutMockup) {
315
- const type = screen.componentType || "";
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 || "", section: screen.section || "",
458
+ screen: screen.screen, section: screen.section,
331
459
  mockupFormat: "text", mockup: desc,
332
- description: screen.sectionDescription || "", elements: [], componentMapping: []
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 6 blocking validations on the generated HTML file and display the completion summary.
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
  ---