@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
@@ -0,0 +1,158 @@
1
+ # Entity Name Canonicalization
2
+
3
+ > **Used by:** step-01-transform (section 2: file mapping) and step-02-export (POST-CHECK)
4
+ > **Purpose:** Convert BA entity names (potentially French, with spaces/apostrophes/accents) into valid C# identifiers for file paths.
5
+
6
+ ---
7
+
8
+ ## Why This Exists
9
+
10
+ Business analysts write entity names in the user's language (e.g., French: "Type d'absence", "Employé", "Congé maladie"). These names are used as-is in `entities.json`. However, C# file paths and class names **MUST** be valid identifiers:
11
+ - No spaces
12
+ - No apostrophes
13
+ - No accents (diacritics)
14
+ - PascalCase convention
15
+
16
+ Without canonicalization, the handoff generates paths like `src/Domain/Entities/Type d'absence.cs` → build failure (CS1001).
17
+
18
+ ---
19
+
20
+ ## Canonicalization Rules
21
+
22
+ ### Step 1: Map to English PascalCase (PREFERRED)
23
+
24
+ If the BA provides an explicit `codeIdentifier` or `englishName` field on the entity, use it directly.
25
+
26
+ ### Step 2: Strip Diacritics
27
+
28
+ Remove accents from characters:
29
+
30
+ | Input | Output |
31
+ |-------|--------|
32
+ | é, è, ê, ë | e |
33
+ | à, â, ä | a |
34
+ | ù, û, ü | u |
35
+ | ô, ö | o |
36
+ | î, ï | i |
37
+ | ç | c |
38
+
39
+ Example: `Employé` → `Employe`, `Congé` → `Conge`
40
+
41
+ ### Step 3: Remove Apostrophes and Split on Spaces
42
+
43
+ Split the name on spaces, apostrophes, hyphens, and underscores:
44
+
45
+ | Input | Tokens |
46
+ |-------|--------|
47
+ | `Type d'absence` | `["Type", "d", "absence"]` |
48
+ | `Congé maladie` | `["Conge", "maladie"]` |
49
+ | `Mise à jour` | `["Mise", "a", "jour"]` |
50
+
51
+ ### Step 4: Remove Articles and Prepositions (1-2 letter tokens)
52
+
53
+ Remove French articles and prepositions that don't contribute to the identifier:
54
+
55
+ **Remove:** `d`, `de`, `du`, `l`, `la`, `le`, `les`, `un`, `une`, `des`, `a`, `au`, `aux`, `en`
56
+
57
+ | Input Tokens | Filtered |
58
+ |-------------|----------|
59
+ | `["Type", "d", "absence"]` | `["Type", "absence"]` |
60
+ | `["Mise", "a", "jour"]` | `["Mise", "jour"]` |
61
+
62
+ ### Step 5: PascalCase Assembly
63
+
64
+ Capitalize first letter of each remaining token, join without separator:
65
+
66
+ | Filtered Tokens | Result |
67
+ |----------------|--------|
68
+ | `["Type", "absence"]` | `TypeAbsence` |
69
+ | `["Conge", "maladie"]` | `CongeMaladie` |
70
+ | `["Mise", "jour"]` | `MiseJour` |
71
+ | `["Employe"]` | `Employe` |
72
+
73
+ ---
74
+
75
+ ## Canonicalization Function (Pseudocode)
76
+
77
+ ```javascript
78
+ function canonicalizeEntityName(baName) {
79
+ // Step 1: Use explicit code identifier if available
80
+ // (handled by caller — check entity.codeIdentifier || entity.englishName first)
81
+
82
+ // Step 2: Strip diacritics
83
+ let name = baName.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
84
+
85
+ // Step 3: Split on non-alphanumeric
86
+ let tokens = name.split(/[\s'\-_]+/).filter(t => t.length > 0);
87
+
88
+ // Step 4: Remove French articles/prepositions
89
+ const stopWords = new Set(['d', 'de', 'du', 'l', 'la', 'le', 'les', 'un', 'une', 'des', 'a', 'au', 'aux', 'en']);
90
+ tokens = tokens.filter(t => !stopWords.has(t.toLowerCase()));
91
+
92
+ // Step 5: PascalCase
93
+ return tokens.map(t => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase()).join('');
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Validation: Is Valid C# Identifier?
100
+
101
+ After canonicalization, verify the result is a valid C# identifier:
102
+
103
+ ```javascript
104
+ function isValidCSharpIdentifier(name) {
105
+ // Must start with letter or underscore
106
+ // Must contain only letters, digits, underscores
107
+ // Must not be a C# keyword
108
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
109
+ && !CSHARP_KEYWORDS.has(name);
110
+ }
111
+
112
+ const CSHARP_KEYWORDS = new Set([
113
+ 'abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch',
114
+ 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default',
115
+ 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit',
116
+ 'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach',
117
+ 'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal',
118
+ 'is', 'lock', 'long', 'namespace', 'new', 'null', 'object',
119
+ 'operator', 'out', 'override', 'params', 'private', 'protected',
120
+ 'public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short',
121
+ 'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch',
122
+ 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong',
123
+ 'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void',
124
+ 'volatile', 'while'
125
+ ]);
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Examples
131
+
132
+ | BA Entity Name (French) | Canonicalized | File Path |
133
+ |-------------------------|---------------|-----------|
134
+ | `Type d'absence` | `TypeAbsence` | `src/Domain/Entities/App/Module/TypeAbsence.cs` |
135
+ | `Employé` | `Employe` | `src/Domain/Entities/App/Module/Employe.cs` |
136
+ | `Congé maladie` | `CongeMaladie` | `src/Domain/Entities/App/Module/CongeMaladie.cs` |
137
+ | `Mise à jour` | `MiseJour` | `src/Domain/Entities/App/Module/MiseJour.cs` |
138
+ | `Département` | `Departement` | `src/Domain/Entities/App/Module/Departement.cs` |
139
+ | `Employee` | `Employee` | `src/Domain/Entities/App/Module/Employee.cs` (no change) |
140
+ | `AbsenceType` | `AbsenceType` | `src/Domain/Entities/App/Module/AbsenceType.cs` (no change) |
141
+
142
+ ---
143
+
144
+ ## Integration Points
145
+
146
+ 1. **step-01-transform.md**: Apply `canonicalizeEntityName()` to ALL entity names BEFORE generating `filesToCreate` paths
147
+ 2. **step-02-export.md**: POST-CHECK validates all paths in `filesToCreate` are valid C# identifiers
148
+ 3. **entity-domain-mapping.md**: `{EntityName}` in path templates refers to the CANONICALIZED name
149
+ 4. **handoff-file-templates.md**: All `{EntityName}` placeholders use canonicalized names
150
+
151
+ ## Anti-Patterns
152
+
153
+ | Anti-Pattern | Risk | Correct Approach |
154
+ |-------------|------|-----------------|
155
+ | Translate to English semantically | `MiseJour` → `Update`? No — loses domain meaning | Only strip illegal chars, keep semantics |
156
+ | Remove all short words | `TypeAbsence` missing `Type`? | Only remove articles/prepositions, not nouns |
157
+ | Lowercase everything | `typeabsence` is not PascalCase | PascalCase each token |
158
+ | Skip validation | `Task` is a valid name but also a C# type | Warn on namespace conflicts, don't block |
@@ -19,6 +19,7 @@ next_step: steps/step-02-export.md
19
19
  - **NEVER** invent entities/BRs not in module JSON files
20
20
  - **NEVER** load all module data in the main conversation (causes context overflow on 5+ modules)
21
21
  - **ALWAYS** generate API endpoints from use cases + entities (BA does not produce apiEndpoints)
22
+ - **ALWAYS** canonicalize entity names before generating file paths — see `references/entity-canonicalization.md`
22
23
 
23
24
  ## YOUR TASK
24
25
 
@@ -96,6 +97,19 @@ Then build the handoff data following these rules:
96
97
  ### Section UC/BR Enrichment
97
98
  Using sectionCode from usecases.json and rules.json, link each UC and BR to its section.
98
99
 
100
+ ### Entity Name Canonicalization (MANDATORY)
101
+ Before generating any file paths, canonicalize ALL entity names from `entities.json`:
102
+ 1. Read `references/entity-canonicalization.md` for complete rules
103
+ 2. For each entity in `entities.json > entities[]`:
104
+ - If entity has `codeIdentifier` or `englishName` → use it directly
105
+ - Otherwise → apply canonicalization: strip diacritics, remove apostrophes/spaces, remove French articles (d, de, du, l, la, le, les, un, une, des, a, au, aux, en), PascalCase
106
+ 3. Use the CANONICALIZED name in ALL file path templates (`{EntityName}`, `{ServiceName}`, `{DtoName}`, etc.)
107
+ 4. Store the mapping `{ originalName: "Type d'absence", canonicalName: "TypeAbsence" }` in handoff metadata for traceability
108
+
109
+ **Example:** Entity `"Type d'absence"` → canonicalized to `"TypeAbsence"` → path `src/Domain/Entities/App/Module/TypeAbsence.cs`
110
+
111
+ **BLOCKING:** If ANY entity name after canonicalization is not a valid C# identifier (`/^[A-Za-z_][A-Za-z0-9_]*$/`), STOP and report the error.
112
+
99
113
  ### File Mapping (8 Categories)
100
114
  Read `references/handoff-file-templates.md` for complete JSON templates.
101
115
  All backend paths MUST include {ApplicationName}/ hierarchy.
@@ -111,6 +111,20 @@ for (const key of specKeys) {
111
111
  BLOCKING_ERROR(`Companion file empty or too small: ${companionPath} (${companionSize} bytes)`);
112
112
  }
113
113
  }
114
+
115
+ // Check 6: File paths must be valid C# identifiers (no spaces, apostrophes, accents)
116
+ // Reference: references/entity-canonicalization.md
117
+ for (const [cat, files] of Object.entries(ftc)) {
118
+ for (const file of (files || [])) {
119
+ const filePath = file.path || file;
120
+ // Extract filename without extension
121
+ const fileName = filePath.split('/').pop().replace(/\.(cs|tsx|ts|json)$/, '');
122
+ // Check for illegal characters in C# file names
123
+ if (/[\s'àâäéèêëïîôùûüçÀÂÄÉÈÊËÏÎÔÙÛÜÇ]/.test(fileName)) {
124
+ BLOCKING_ERROR(`${cat}: File path contains illegal characters for C# identifier: "${filePath}" — entity name must be canonicalized (see references/entity-canonicalization.md)`);
125
+ }
126
+ }
127
+ }
114
128
  ```
115
129
 
116
130
  Display verification table showing all 8 categories match between module JSON files and prd.json.
@@ -42,9 +42,13 @@ Generate the interactive HTML document of the business analysis from the JSON an
42
42
 
43
43
  - **NEVER** deploy an empty template — FEATURE_DATA must be injected
44
44
  - **moduleSpecs** MUST have ONE entry per module (empty = BROKEN)
45
+ - **moduleSpecs[].screens[]** MUST have populated resources when source screens.json has data (empty resources = NO HTML mockups)
45
46
  - **Scope keys** MUST be converted: `inScope` → `inscope`, `outOfScope` → `outofscope`
46
47
  - **Wireframe fields** MUST be renamed: `mockupFormat` → `format`, `mockup` → `content`
48
+ - **Input normalization** MUST handle both JSON schemas: Format A (`screens[]`, `useCases`, `mainScenario`) and Format B (`sections[]`, `usecases`, `steps[]` as objects)
49
+ - **NEVER** call `.join()` on arrays without checking element types — object elements produce `[object Object]`
47
50
  - **Final file** MUST be > 100KB
51
+ - **Final file** MUST NOT contain the string `[object Object]`
48
52
 
49
53
  ## References
50
54
 
@@ -2394,6 +2394,13 @@ let data = {{FEATURE_DATA}};
2394
2394
  const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
2395
2395
  const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
2396
2396
 
2397
+ // CRITICAL: Initialize top-level structures FIRST to prevent TypeError crashes
2398
+ data.cadrage = data.cadrage || {};
2399
+ data.modules = data.modules || [];
2400
+ data.dependencies = data.dependencies || [];
2401
+ data.handoff = data.handoff || {};
2402
+ data.cadrage.stakeholders = data.cadrage.stakeholders || [];
2403
+
2397
2404
  // Initialize applications array (multi-app support)
2398
2405
  data.applications = data.applications || [];
2399
2406
 
@@ -6,6 +6,13 @@ let data = {{FEATURE_DATA}};
6
6
  const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
7
7
  const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
8
8
 
9
+ // CRITICAL: Initialize top-level structures FIRST to prevent TypeError crashes
10
+ data.cadrage = data.cadrage || {};
11
+ data.modules = data.modules || [];
12
+ data.dependencies = data.dependencies || [];
13
+ data.handoff = data.handoff || {};
14
+ data.cadrage.stakeholders = data.cadrage.stakeholders || [];
15
+
9
16
  // Initialize applications array (multi-app support)
10
17
  data.applications = data.applications || [];
11
18
 
@@ -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 + ":");