@atlashub/smartstack-cli 4.51.0 → 4.53.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 +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/skills/apex/references/core-seed-data.md +15 -0
  5. package/templates/skills/apex/references/error-classification.md +27 -3
  6. package/templates/skills/apex/references/post-checks.md +3 -1
  7. package/templates/skills/apex/steps/step-00-init.md +57 -0
  8. package/templates/skills/apex/steps/step-03-execute.md +33 -5
  9. package/templates/skills/apex/steps/step-03b-layer1-seed.md +18 -0
  10. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +3 -0
  11. package/templates/skills/apex/steps/step-04-examine.md +35 -0
  12. package/templates/skills/business-analyse/references/canonical-json-formats.md +200 -0
  13. package/templates/skills/business-analyse/steps/step-03-specify.md +94 -20
  14. package/templates/skills/business-analyse-design/steps/step-01-screens.md +41 -4
  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 +26 -3
  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/references/data-build.md +24 -17
  24. package/templates/skills/business-analyse-html/references/data-mapping.md +79 -35
  25. package/templates/skills/business-analyse-html/references/output-modes.md +2 -1
  26. package/templates/skills/business-analyse-html/steps/step-01-collect.md +7 -2
  27. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +155 -40
  28. package/templates/skills/business-analyse-html/steps/step-04-verify.md +22 -4
@@ -64,8 +64,14 @@ const FEATURE_DATA = {
64
64
  // FLAT-FILE: Data comes from collected_data.modules[moduleCode] (flat files), NOT module index.json
65
65
  "{moduleCode}": {
66
66
  // const mod = collected_data.modules[moduleCode];
67
- useCases: mod.usecases?.useCases || [],
68
- businessRules: mod.rules?.rules || [],
67
+ // NORMALIZE: handle both key conventions (useCases/usecases, rules/businessRules)
68
+ useCases: (mod.usecases?.useCases || mod.usecases?.usecases || []).map(uc => ({
69
+ // Handle both "name"/"title"/"id" and "primaryActor"/"actor" conventions
70
+ // Handle steps as strings (mainScenario) or objects ({step, action})
71
+ })),
72
+ businessRules: (mod.rules?.rules || mod.rules?.businessRules || []).map(br => ({
73
+ // Handle both "name"/"id" and "statement"/"description" conventions
74
+ })),
69
75
  // ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
70
76
  entities: (mod.entities?.entities || []).map(ent => ({
71
77
  name: ent.name,
@@ -124,26 +130,27 @@ const FEATURE_DATA = {
124
130
  const EMBEDDED_ARTIFACTS = {
125
131
  wireframes: {
126
132
  // PER-MODULE keyed object (NOT a flat array)
127
- // FLAT-FILE: Read from screens.json in each module directory (collected in step-01)
133
+ // TWO SOURCE FORMATS: screens.json may use screens[] (Format A) or sections[] (Format B)
128
134
  // const mod = collected_data.modules[moduleCode];
129
- // const screens = mod.screens?.screens || [];
130
- [moduleCode]: screens
131
- .filter(s => s.wireframe || s.mockup || s.mockupFormat)
132
- .map(wf => ({
133
- screen: wf.screen || wf.name || wf.title || wf.id || "", // SAFETY NET: fallback name/title/id screen
134
- section: wf.section || "", // e.g. "list"
135
- format: wf.mockupFormat || "ascii", // RENAME: mockupFormat format
136
- content: wf.mockup || wf.ascii || wf.content || "", // SAFETY NET: mockup/ascii/content → content
137
- svgContent: null, // Populated by SVG generation step (see wireframe-svg-style-guide.md)
135
+ // Collect from ALL sources:
136
+ // Source A: mod.screens.screens[] with mockup/mockupFormat (flat format)
137
+ // Source B1: mod.screens.sections[].wireframe (section-level wireframe)
138
+ // Source B2: mod.screens.sections[].resources[].wireframe (resource-level wireframe)
139
+ // Source C: auto-generated text for screens/sections without wireframe
140
+ [moduleCode]: rawWireframes.map(wf => ({
141
+ screen: wf.screen || wf.name || wf.title || wf.id || "",
142
+ section: wf.section || "",
143
+ format: wf.mockupFormat || wf.format || "ascii", // RENAME: mockupFormat → format
144
+ content: wf.mockup || wf.ascii || wf.content || "", // SAFETY NET: mockup/ascii/content → content
145
+ svgContent: null, // Populated by SVG generation step
138
146
  description: wf.description || "",
139
- elements: wf.elements || [], // [{ id, type, label }] or ["DataGrid", ...]
140
- actions: wf.actions || [], // [{ trigger, action }] or ["filter", ...]
141
- // SAFETY NET: convert object → array format if agent used { "Header": "PageHeader" } instead of [{ wireframeElement, reactComponent }]
147
+ elements: wf.elements || [],
148
+ actions: wf.actions || [],
142
149
  componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
143
150
  : typeof wf.componentMapping === 'object' && wf.componentMapping !== null
144
151
  ? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
145
152
  : [],
146
- layout: typeof wf.layout === 'object' ? wf.layout : null, // SAFETY NET: ignore string layout ("grid")
153
+ layout: typeof wf.layout === 'object' ? wf.layout : null,
147
154
  permissionsRequired: wf.permissionsRequired || []
148
155
  }))
149
156
  },
@@ -167,7 +174,7 @@ const EMBEDDED_ARTIFACTS = {
167
174
 
168
175
  ### Artifact Gathering
169
176
 
170
- 1. For EACH module: read `screens.json` flat file from `collected_data.modules[moduleCode].screens`, filter screens with wireframe data, **rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`, `screen`/`name`/`title`→`screen`), store under `wireframes[moduleCode]`
177
+ 1. For EACH module: read `screens.json` from `collected_data.modules[moduleCode].screens`. **Detect format:** if `screens[]` exists → Format A (flat). If `sections[]` exists → Format B (nested). Collect wireframes from all sources (A: screen.mockup, B1: section.wireframe, B2: section.resources[].wireframe, C: auto-text). **Rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`). Store under `wireframes[moduleCode]`
171
178
  2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
172
179
  3. Read master's `dependencyGraph` and build nodes/edges
173
180
  4. Serialize as JSON with 2-space indentation
@@ -99,36 +99,52 @@ For EACH module in `master.modules[]`:
99
99
  2. Data sources: `entities.json`, `rules.json`, `usecases.json`, `permissions.json`, `screens.json`
100
100
  3. Do NOT read from `moduleIndex.specification` or `moduleIndex.analysis` — these don't exist in flat-file format
101
101
 
102
+ > **TWO INPUT FORMATS:** The `/business-analyse` skill may produce JSON in two schema conventions.
103
+ > You MUST normalize both to the same output format. The mapping below shows both variants.
104
+
102
105
  ```javascript
103
106
  const mod = collected_data.modules[moduleCode];
104
107
 
108
+ // NORMALIZE: usecases.json key may be "useCases" (camelCase) or "usecases" (lowercase)
109
+ const rawUCs = mod.usecases?.useCases || mod.usecases?.usecases || [];
110
+ // NORMALIZE: rules.json key may be "rules" or "businessRules"
111
+ const rawBRs = mod.rules?.rules || mod.rules?.businessRules || [];
112
+
105
113
  moduleSpecs[moduleCode] = {
106
- useCases: (mod.usecases?.useCases || []).map(uc => ({
107
- name: uc.name,
108
- sectionCode: uc.sectionCode || "", // links UC to anticipatedSections[].code
109
- actor: uc.primaryActor,
110
- steps: (uc.mainScenario || []).join("\n"), // array newline-separated string
111
- alternative: (uc.alternativeScenarios || [])
112
- .map(a => a.name + ": " + (a.steps || []).join(", ")).join("\n")
114
+ useCases: rawUCs.map(uc => ({
115
+ name: uc.name || uc.title || uc.id || "", // Format A: "name", Format B: "title" or "id"
116
+ sectionCode: uc.sectionCode || "",
117
+ actor: uc.primaryActor || uc.actor || "", // Format A: "primaryActor", Format B: "actor"
118
+ // SAFETY NET: steps may be string[] or object[] ({step, action})
119
+ steps: (uc.mainScenario || uc.steps || []).map(s =>
120
+ typeof s === 'string' ? s : (s.action || s.description || "")
121
+ ).join("\n"),
122
+ alternative: (uc.alternativeScenarios || uc.alternativeFlows || []).map(a =>
123
+ (a.name || a.trigger || "") + ": " + (a.steps || a.actions || []).map(s =>
124
+ typeof s === 'string' ? s : (s.action || s.description || "")
125
+ ).join(", ")
126
+ ).join("\n")
113
127
  })),
114
- businessRules: (mod.rules?.rules || []).map(br => ({
115
- name: br.name,
116
- sectionCode: br.sectionCode || "", // links BR to anticipatedSections[].code
117
- category: br.category, // "validation"|"calculation"|"workflow"|"security"|"data"
118
- statement: br.statement,
119
- example: (br.examples || []).map(e => e.input + " → " + e.expected).join("; ")
128
+ businessRules: rawBRs.map(br => ({
129
+ name: br.name || br.id || "",
130
+ sectionCode: br.sectionCode || "",
131
+ category: br.category || "",
132
+ statement: br.statement || br.description || "",
133
+ example: (br.examples || []).map(e =>
134
+ typeof e === 'string' ? e : ((e.input || "") + " → " + (e.expected || ""))
135
+ ).join("; ")
120
136
  })),
121
137
  entities: (mod.entities?.entities || []).map(ent => ({
122
138
  name: ent.name,
123
139
  description: ent.description || "",
124
- attributes: (ent.attributes || []).map(a => ({
140
+ attributes: (ent.attributes || ent.fields || []).map(a => ({
125
141
  name: a.name,
126
- type: a.type || "string", // string, int, decimal, date, datetime, bool, enum, guid, text
142
+ type: a.type || "string",
127
143
  required: a.required || false,
128
144
  description: a.description || ""
129
145
  })),
130
146
  relationships: (ent.relationships || []).map(r =>
131
- r.target + " (" + r.type + ") - " + (r.description || "")
147
+ typeof r === 'string' ? r : (r.target || r.to || "") + " (" + (r.type || r.cardinality || "") + ") - " + (r.description || "")
132
148
  )
133
149
  })),
134
150
  permissions: buildPermissionKeys(mod.permissions), // see below
@@ -215,31 +231,59 @@ Build a JSON object containing ALL visual artifacts from module JSON files (inde
215
231
 
216
232
  ### Wireframes Mapping (FLAT-FILE: from screens.json)
217
233
 
234
+ > **TWO SOURCE FORMATS:** screens.json may use `screens[]` (Format A) or `sections[]` (Format B).
235
+ > Wireframes may be at top level (Format A: `screen.mockup`) or nested (Format B: `section.wireframe`).
236
+
218
237
  For EACH module in `master.modules[]`:
219
238
 
220
239
  ```javascript
221
- // Use flat file data from collected_data (loaded in step-01)
222
240
  const mod = collected_data.modules[moduleCode];
223
- const screens = mod.screens?.screens || [];
224
-
225
- // Extract wireframes from screens that have mockup/wireframe data
226
- const wireframes = screens
227
- .filter(s => s.wireframe || s.mockup || s.mockupFormat)
228
- .map(wf => ({
229
- screen: wf.screen || wf.name || wf.id || "", // e.g. "UM-list", "UM-form"
230
- section: wf.section || "", // e.g. "list", "form"
231
- format: wf.mockupFormat || "ascii", // "ascii" | "svg"
232
- content: wf.mockup || wf.ascii || wf.content || "", // ASCII art or SVG markup
241
+ let rawWireframes = [];
242
+
243
+ // Source A: flat screens[] with mockup/mockupFormat at screen level
244
+ const flatScreens = mod.screens?.screens || [];
245
+ rawWireframes.push(...flatScreens.filter(s => s.mockup || s.mockupFormat));
246
+
247
+ // Source B: sections[] with wireframe at section or resource level
248
+ const sections = mod.screens?.sections || [];
249
+ for (const section of sections) {
250
+ // B1: wireframe directly on section (section.wireframe)
251
+ if (section.wireframe) {
252
+ rawWireframes.push({
253
+ ...section.wireframe,
254
+ section: section.wireframe.section || section.id || section.code || "",
255
+ screen: section.wireframe.screen || section.id || section.code || ""
256
+ });
257
+ }
258
+ // B2: wireframe nested in section.resources[]
259
+ for (const resource of (section.resources || [])) {
260
+ if (resource.wireframe) {
261
+ rawWireframes.push({
262
+ ...resource.wireframe,
263
+ section: resource.wireframe.section || section.id || section.code || "",
264
+ screen: resource.wireframe.screen || `${section.id || section.code}-${resource.code}`
265
+ });
266
+ }
267
+ }
268
+ }
269
+
270
+ // Map to HTML format (RENAME: mockupFormat → format, mockup → content)
271
+ EMBEDDED_ARTIFACTS.wireframes[moduleCode] = rawWireframes.map(wf => ({
272
+ screen: wf.screen || wf.name || wf.id || "",
273
+ section: wf.section || "",
274
+ format: wf.mockupFormat || wf.format || "ascii", // RENAME: mockupFormat → format
275
+ content: wf.mockup || wf.ascii || wf.content || "", // RENAME: mockup → content
276
+ svgContent: null,
233
277
  description: wf.description || "",
234
- elements: wf.elements || [], // ["DataGrid", "FilterBar", ...]
235
- actions: wf.actions || [], // ["filter", "sort", "create", ...]
236
- componentMapping: wf.componentMapping || [], // [{ wireframeElement, reactComponent }]
237
- layout: wf.layout || null, // { type, regions: [...] }
278
+ elements: wf.elements || [],
279
+ actions: wf.actions || [],
280
+ componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
281
+ : typeof wf.componentMapping === 'object' && wf.componentMapping !== null
282
+ ? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
283
+ : [],
284
+ layout: typeof wf.layout === 'object' ? wf.layout : null,
238
285
  permissionsRequired: wf.permissionsRequired || []
239
- }));
240
-
241
- // Store in artifacts object
242
- EMBEDDED_ARTIFACTS.wireframes[moduleCode] = wireframes;
286
+ }));
243
287
  ```
244
288
 
245
289
  ### E2E Flows Mapping
@@ -112,7 +112,8 @@ Full handoff across multiple applications within a project:
112
112
  | ba-interactive.html is too small (< 100KB) | FEATURE_DATA not serialized correctly. Verify JSON structure. Re-run FEATURE_DATA build. |
113
113
  | moduleSpecs missing from HTML | FEATURE_DATA.moduleSpecs is empty or not included in JSON. Verify each module is iterated and populated. |
114
114
  | Scope keys still show "inScope" instead of "inscope" | Conversion from JSON keys to HTML keys failed. Check cadrage.scope conversion logic. |
115
- | Wireframes array is empty | Verify each module has specification.uiWireframes. If missing, add wireframe stubs. |
115
+ | Wireframes array is empty | Verify each module has screens.json with wireframe data (Format A: screens[].mockup, Format B: sections[].wireframe). If missing, step-02 will auto-generate text descriptions from columns/tabs. |
116
+ | Mockups show ASCII instead of HTML tables | screens[] in moduleSpecs has empty resources. Verify screens.json format was normalized correctly (Format A: screens[] or Format B: sections[]) by step-02. |
116
117
  | dependencyGraph missing | Verify master index.json has consolidation.dependencyGraph. If missing, create empty nodes/edges arrays. |
117
118
  | HTML opens but data blank (localStorage issue) | Clear browser localStorage. HTML will load pre-populated FEATURE_DATA on first load. |
118
119
  | Export/re-import fails | Verify exported JSON matches original schema. Validate with `/business-analyse -x --validate`. |
@@ -87,10 +87,15 @@ FOR each module in master.index.modules[]:
87
87
  if (["specified", "consolidated", "designed", "reviewed", "handed-off"].includes(moduleStatus)) {
88
88
  if ((moduleData.entities.entities || []).length === 0)
89
89
  warnings.push("entities.json is EMPTY for specified module " + module.code);
90
- if ((moduleData.usecases.useCases || []).length === 0)
90
+ // Check both key conventions: useCases (camelCase) and usecases (lowercase)
91
+ if ((moduleData.usecases.useCases || moduleData.usecases.usecases || []).length === 0)
91
92
  warnings.push("usecases.json is EMPTY for specified module " + module.code);
92
- if ((moduleData.rules.rules || []).length === 0)
93
+ // Check both key conventions: rules and businessRules
94
+ if ((moduleData.rules.rules || moduleData.rules.businessRules || []).length === 0)
93
95
  warnings.push("rules.json is EMPTY for specified module " + module.code);
96
+ // Check screens: both screens[] (Format A) and sections[] (Format B)
97
+ if ((moduleData.screens.screens || moduleData.screens.sections || []).length === 0)
98
+ warnings.push("screens.json is EMPTY for specified module " + module.code);
94
99
  }
95
100
  if (warnings.length > 0) {
96
101
  Display("⚠ WARNING — Missing data for module " + module.code + ":");
@@ -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) {
@@ -310,39 +402,62 @@ rawWireframes.push(...flatScreens.filter(s => s.mockup || s.mockupFormat));
310
402
  // Source B: nested sections[].resources[].wireframe (design step output)
311
403
  const sections = mod.screens?.sections || [];
312
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)
313
414
  for (const resource of (section.resources || [])) {
314
415
  if (resource.wireframe) {
315
- // Unwrap nested wireframe and add section context
316
416
  rawWireframes.push({
317
417
  ...resource.wireframe,
318
- section: resource.wireframe.section || section.sectionCode || "",
319
- 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}` || ""
320
420
  });
321
421
  }
322
422
  }
323
423
  }
324
424
 
325
- // 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[]
326
427
  const screensWithoutMockup = flatScreens.filter(s => !s.mockup && !s.mockupFormat);
327
- for (const screen of screensWithoutMockup) {
328
- 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;
329
444
  let desc = "";
330
445
  if (type.includes("Table") || type.includes("Grid")) {
331
- const cols = screen.columns || [];
332
- 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(", ");
333
448
  } else if (type.includes("Form")) {
334
- const tabs = screen.tabs || [];
449
+ const tabs = screen.tabs;
335
450
  desc = "Formulaire " + (tabs.length > 0 ? "avec " + tabs.length + " onglet(s)" : "");
336
451
  } else if (type.includes("Dashboard")) {
337
- desc = "Tableau de bord avec KPIs";
452
+ desc = "Tableau de bord avec " + (screen.kpis.length || "") + " KPIs";
338
453
  } else if (type.includes("Kanban")) {
339
454
  desc = "Vue Kanban";
340
455
  }
341
456
  if (desc) {
342
457
  rawWireframes.push({
343
- screen: screen.screen || "", section: screen.section || "",
458
+ screen: screen.screen, section: screen.section,
344
459
  mockupFormat: "text", mockup: desc,
345
- description: screen.sectionDescription || "", elements: [], componentMapping: []
460
+ description: screen.description, elements: [], componentMapping: []
346
461
  });
347
462
  }
348
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,19 +57,37 @@ 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 — No runtime crash (CRITICAL validates page won't be blank):**
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):**
73
91
  ```
74
92
  Open ba-interactive.html and search for the FEATURE_DATA JSON block.
75
93
  Verify these MANDATORY top-level fields exist: