@atlashub/smartstack-cli 4.50.0 → 4.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +53 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/skills/apex/SKILL.md +30 -0
- package/templates/skills/apex/references/core-seed-data.md +15 -0
- package/templates/skills/apex/references/error-classification.md +27 -3
- package/templates/skills/apex/references/post-checks.md +3 -1
- package/templates/skills/apex/steps/step-00-init.md +57 -0
- package/templates/skills/apex/steps/step-03-execute.md +40 -5
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +4 -1
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +22 -1
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +4 -1
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +7 -1
- package/templates/skills/apex/steps/step-04-examine.md +35 -0
- package/templates/skills/business-analyse-develop/references/init-resume-recovery.md +54 -0
- package/templates/skills/business-analyse-develop/steps/step-00-init.md +10 -3
- package/templates/skills/business-analyse-develop/steps/step-01-task.md +14 -2
- package/templates/skills/business-analyse-develop/steps/step-04-check.md +12 -2
- package/templates/skills/business-analyse-handoff/references/entity-canonicalization.md +158 -0
- package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +14 -0
- package/templates/skills/business-analyse-handoff/steps/step-02-export.md +14 -0
- package/templates/skills/business-analyse-html/SKILL.md +4 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +7 -0
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +7 -0
- package/templates/skills/business-analyse-html/references/data-build.md +24 -17
- package/templates/skills/business-analyse-html/references/data-mapping.md +79 -35
- package/templates/skills/business-analyse-html/references/output-modes.md +2 -1
- package/templates/skills/business-analyse-html/steps/step-01-collect.md +7 -2
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +168 -40
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +39 -3
|
@@ -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
|
-
|
|
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 + ":");
|