@atlashub/smartstack-cli 4.51.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/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 +33 -5
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +18 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +3 -0
- 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/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 +155 -40
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +22 -4
|
@@ -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
|
|
|
@@ -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
|
-
|
|
68
|
-
|
|
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
|
-
//
|
|
133
|
+
// TWO SOURCE FORMATS: screens.json may use screens[] (Format A) or sections[] (Format B)
|
|
128
134
|
// const mod = collected_data.modules[moduleCode];
|
|
129
|
-
//
|
|
130
|
-
[
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 || [],
|
|
140
|
-
actions: wf.actions || [],
|
|
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,
|
|
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`
|
|
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:
|
|
107
|
-
name: uc.name,
|
|
108
|
-
sectionCode: uc.sectionCode || "",
|
|
109
|
-
actor: uc.primaryActor,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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:
|
|
115
|
-
name: br.name,
|
|
116
|
-
sectionCode: br.sectionCode || "",
|
|
117
|
-
category: br.category
|
|
118
|
-
statement: br.statement,
|
|
119
|
-
example: (br.examples || []).map(e =>
|
|
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",
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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 || [],
|
|
235
|
-
actions: wf.actions || [],
|
|
236
|
-
componentMapping: wf.componentMapping
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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) {
|
|
@@ -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
|
-
|
|
328
|
-
|
|
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
|
|
458
|
+
screen: screen.screen, section: screen.section,
|
|
344
459
|
mockupFormat: "text", mockup: desc,
|
|
345
|
-
description: screen.
|
|
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
|
|
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 —
|
|
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:
|