@atlashub/smartstack-cli 3.20.0 → 3.21.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 (26) hide show
  1. package/dist/index.js +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +1 -0
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/project/api.ts.template +8 -29
  7. package/templates/project/appsettings.json.template +1 -0
  8. package/templates/skills/business-analyse/html/ba-interactive.html +562 -150
  9. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  10. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  11. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  12. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
  13. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  14. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  15. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  16. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  17. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  18. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  19. package/templates/skills/business-analyse/html/src/template.html +8 -76
  20. package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
  21. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  22. package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
  23. package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
  24. package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
  25. package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
  26. package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -0
@@ -1,62 +1,120 @@
1
1
  /* ============================================
2
- SIDEBAR - Navigation 5 niveaux
2
+ SIDEBAR - Hierarchical Tree Navigation
3
3
  ============================================ */
4
- .nav-group { padding: 1rem 0; }
4
+
5
+ /* Sidebar header */
6
+ .sidebar-header {
7
+ padding: 0.75rem 1rem;
8
+ border-bottom: 1px solid var(--border);
9
+ background: var(--bg-hover);
10
+ }
11
+ .sidebar-app-name {
12
+ font-size: 0.95rem;
13
+ font-weight: 600;
14
+ color: var(--primary-light);
15
+ display: block;
16
+ white-space: nowrap;
17
+ overflow: hidden;
18
+ text-overflow: ellipsis;
19
+ }
20
+
21
+ /* Nav groups (top-level: Cadrage, Modules, Consolidation, Synthese) */
22
+ .nav-group { padding: 0.4rem 0; }
5
23
  .nav-group + .nav-group { border-top: 1px solid var(--border); }
6
24
 
7
25
  .nav-group-title {
8
- font-size: 0.65rem;
26
+ font-size: 0.7rem;
9
27
  text-transform: uppercase;
10
28
  letter-spacing: 0.1em;
11
29
  color: var(--text-muted);
12
- padding: 0 1rem;
13
- margin-bottom: 0.5rem;
30
+ padding: 0.4rem 1rem;
31
+ margin-bottom: 0.15rem;
14
32
  font-weight: 600;
33
+ cursor: pointer;
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 0.3rem;
37
+ user-select: none;
38
+ transition: color var(--transition-fast);
39
+ }
40
+ .nav-group-title:hover { color: var(--text-bright); }
41
+
42
+ /* Chevron icon for expand/collapse */
43
+ .nav-chevron {
44
+ font-size: 0.6rem;
45
+ display: inline-block;
46
+ transition: transform var(--transition-fast);
47
+ color: var(--text-muted);
48
+ width: 12px;
49
+ text-align: center;
50
+ flex-shrink: 0;
15
51
  }
52
+ .nav-chevron.expanded { transform: rotate(90deg); }
16
53
 
54
+ /* Nav items (leaf nodes) */
17
55
  .nav-item {
18
56
  display: flex;
19
57
  align-items: center;
20
- gap: 0.5rem;
21
- padding: 0.45rem 1rem;
58
+ gap: 0.4rem;
59
+ padding: 0.35rem 1rem;
22
60
  color: var(--text);
23
61
  text-decoration: none;
24
- font-size: 0.85rem;
62
+ font-size: 0.82rem;
25
63
  cursor: pointer;
26
64
  transition: all var(--transition-fast);
27
65
  border-left: 3px solid transparent;
28
66
  }
29
67
  .nav-item:hover { background: var(--bg-hover); color: var(--text-bright); }
30
- .nav-item.active { background: rgba(99,102,241,0.1); border-left-color: var(--primary); color: var(--primary-light); font-weight: 500; }
68
+ .nav-item.active {
69
+ background: rgba(99,102,241,0.1);
70
+ border-left-color: var(--primary);
71
+ color: var(--primary-light);
72
+ font-weight: 500;
73
+ }
31
74
 
32
- .nav-item .nav-icon { font-size: 1rem; width: 20px; text-align: center; }
75
+ .nav-item .nav-icon {
76
+ font-size: 0.45rem;
77
+ width: 12px;
78
+ text-align: center;
79
+ color: var(--border-light);
80
+ flex-shrink: 0;
81
+ }
33
82
  .nav-item .nav-badge {
34
83
  margin-left: auto;
35
- font-size: 0.65rem;
84
+ font-size: 0.6rem;
36
85
  background: var(--bg-hover);
37
- padding: 0.1rem 0.4rem;
86
+ padding: 0.1rem 0.35rem;
38
87
  border-radius: 10px;
39
88
  color: var(--text-muted);
89
+ min-width: 16px;
90
+ text-align: center;
40
91
  }
41
92
 
42
- .nav-children { margin-left: 1.2rem; }
43
- .nav-children .nav-item { font-size: 0.8rem; padding: 0.3rem 1rem; }
93
+ /* Nested children (indented) */
94
+ .nav-children { margin-left: 0; }
95
+ .nav-children .nav-item { padding-left: 1.6rem; font-size: 0.8rem; }
96
+ .nav-children .nav-children .nav-item { padding-left: 2.4rem; font-size: 0.78rem; }
97
+ .nav-children .nav-children .nav-children .nav-item { padding-left: 3rem; font-size: 0.75rem; }
44
98
 
45
- /* ============================================
46
- PHASE PROGRESS
47
- ============================================ */
48
- .phase-progress {
49
- display: flex; align-items: center; gap: 0.3rem;
50
- padding: 0.75rem 1rem; border-bottom: 1px solid var(--border);
51
- }
52
- .phase-dot {
53
- width: 24px; height: 24px; border-radius: 50%;
54
- display: flex; align-items: center; justify-content: center;
55
- font-size: 0.6rem; font-weight: 700; color: var(--text-muted);
56
- background: var(--bg-hover); border: 2px solid var(--border);
57
- transition: all var(--transition-fast);
99
+ /* Module header in nav (collapsible) */
100
+ .nav-module-header {
101
+ font-weight: 500;
102
+ color: var(--text-bright);
103
+ font-size: 0.85rem;
104
+ }
105
+ .nav-module-header:hover { color: var(--primary-light); }
106
+
107
+ /* Section/Resource items in nav */
108
+ .nav-section-item { margin-top: 0.1rem; }
109
+ .nav-section-link { font-style: italic; }
110
+ .nav-icon-section { font-size: 0.5rem; color: var(--accent); }
111
+ .nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
112
+ .nav-resource-link {
113
+ cursor: default;
114
+ font-size: 0.72rem;
115
+ color: var(--text-muted);
116
+ padding-top: 0.2rem;
117
+ padding-bottom: 0.2rem;
58
118
  }
59
- .phase-dot.completed { background: var(--success); border-color: var(--success); color: #fff; }
60
- .phase-dot.current { background: var(--primary); border-color: var(--primary); color: #fff; }
61
- .phase-line { flex: 1; height: 2px; background: var(--border); }
62
- .phase-line.completed { background: var(--success); }
119
+ .nav-resource-link:hover { background: transparent; color: var(--text-muted); }
120
+ .nav-resources { margin-left: 0.5rem; }
@@ -388,3 +388,67 @@
388
388
  padding-left: 0.75rem; border-left: 2px solid var(--accent);
389
389
  margin-bottom: 0.2rem;
390
390
  }
391
+
392
+ /* ============================================
393
+ STRUCTURE TAB (Sections/Resources)
394
+ ============================================ */
395
+ .struct-section {
396
+ background: var(--bg-card);
397
+ border: 1px solid var(--border);
398
+ border-radius: 10px;
399
+ margin-bottom: 1rem;
400
+ overflow: hidden;
401
+ }
402
+ .struct-section-header {
403
+ display: flex;
404
+ align-items: center;
405
+ gap: 0.75rem;
406
+ padding: 0.75rem 1rem;
407
+ background: var(--bg-hover);
408
+ border-bottom: 1px solid var(--border);
409
+ }
410
+ .struct-section-code {
411
+ font-weight: 600;
412
+ color: var(--text-bright);
413
+ font-size: 0.95rem;
414
+ }
415
+ .struct-section-desc {
416
+ flex: 1;
417
+ font-size: 0.8rem;
418
+ color: var(--text-muted);
419
+ }
420
+ .struct-section-badge {
421
+ font-size: 0.65rem;
422
+ color: var(--text-muted);
423
+ background: rgba(99,102,241,0.1);
424
+ padding: 0.1rem 0.5rem;
425
+ border-radius: 4px;
426
+ white-space: nowrap;
427
+ }
428
+ .struct-resources {
429
+ padding: 0.5rem 0;
430
+ }
431
+ .struct-resource {
432
+ display: flex;
433
+ align-items: baseline;
434
+ gap: 0.5rem;
435
+ padding: 0.35rem 1rem 0.35rem 1.5rem;
436
+ font-size: 0.85rem;
437
+ transition: background var(--transition-fast);
438
+ }
439
+ .struct-resource:hover {
440
+ background: var(--bg-hover);
441
+ }
442
+ .struct-resource-icon {
443
+ color: var(--primary-light);
444
+ font-size: 0.7rem;
445
+ flex-shrink: 0;
446
+ }
447
+ .struct-resource-name {
448
+ font-weight: 500;
449
+ color: var(--text-bright);
450
+ }
451
+ .struct-resource-desc {
452
+ font-size: 0.8rem;
453
+ color: var(--text-muted);
454
+ }
@@ -20,6 +20,7 @@
20
20
  <span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
21
21
  <div class="header-spacer"></div>
22
22
  <div class="header-actions">
23
+ <button class="btn btn-sm" onclick="resetToEmbedded()" title="Reinitialiser depuis les donnees d'origine (supprime les modifications locales)">Reset</button>
23
24
  <button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
24
25
  <button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
25
26
  <button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
@@ -32,85 +33,16 @@
32
33
 
33
34
  <div class="body" id="appBody">
34
35
  <!-- ============================================
35
- SIDEBAR - Navigation 5 niveaux
36
+ SIDEBAR - Navigation hierarchique
36
37
  ============================================ -->
37
38
  <aside class="sidebar">
38
- <!-- Phase Progress -->
39
- <div class="phase-progress">
40
- <div class="phase-dot current" id="phase-1" title="Cadrage">1</div>
41
- <div class="phase-line" id="pline-1"></div>
42
- <div class="phase-dot" id="phase-2" title="Decomposition">2</div>
43
- <div class="phase-line" id="pline-2"></div>
44
- <div class="phase-dot" id="phase-3" title="Specification">3</div>
45
- <div class="phase-line" id="pline-3"></div>
46
- <div class="phase-dot" id="phase-4" title="Consolidation">4</div>
47
- <div class="phase-line" id="pline-4"></div>
48
- <div class="phase-dot" id="phase-5" title="Synthese">5</div>
39
+ <!-- Application Name -->
40
+ <div class="sidebar-header">
41
+ <span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
49
42
  </div>
50
-
51
- <!-- Phase 1 : Cadrage -->
52
- <div class="nav-group">
53
- <div class="nav-group-title">1. Cadrage</div>
54
- <a class="nav-item active" onclick="showSection('cadrage-context')" data-section="cadrage-context">
55
- <span class="nav-icon">&#9679;</span> Contexte
56
- </a>
57
- <a class="nav-item" onclick="showSection('cadrage-stakeholders')" data-section="cadrage-stakeholders">
58
- <span class="nav-icon">&#9679;</span> Parties prenantes
59
- <span class="nav-badge" id="stakeholderCount">0</span>
60
- </a>
61
- <a class="nav-item" onclick="showSection('cadrage-scope')" data-section="cadrage-scope">
62
- <span class="nav-icon">&#9679;</span> Perimetre fonctionnel
63
- </a>
64
- <a class="nav-item" onclick="showSection('cadrage-risks')" data-section="cadrage-risks">
65
- <span class="nav-icon">&#9679;</span> Risques et hypotheses
66
- </a>
67
- <a class="nav-item" onclick="showSection('cadrage-success')" data-section="cadrage-success">
68
- <span class="nav-icon">&#9679;</span> Criteres de reussite
69
- </a>
70
- </div>
71
-
72
- <!-- Phase 2 : Decomposition -->
73
- <div class="nav-group">
74
- <div class="nav-group-title">2. Decomposition</div>
75
- <a class="nav-item" onclick="showSection('decomp-modules')" data-section="decomp-modules">
76
- <span class="nav-icon">&#9679;</span> Domaines fonctionnels
77
- <span class="nav-badge" id="moduleCount">0</span>
78
- </a>
79
- <a class="nav-item" onclick="showSection('decomp-dependencies')" data-section="decomp-dependencies">
80
- <span class="nav-icon">&#9679;</span> Dependances
81
- </a>
82
- </div>
83
-
84
- <!-- Phase 3 : Specification par module -->
85
- <div class="nav-group" id="modulesNav">
86
- <div class="nav-group-title">3. Specification</div>
87
- <!-- Populated dynamically per module -->
88
- </div>
89
-
90
- <!-- Phase 4 : Consolidation -->
91
- <div class="nav-group">
92
- <div class="nav-group-title">4. Consolidation</div>
93
- <a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
94
- <span class="nav-icon">&#9679;</span> Modele de donnees
95
- <span class="nav-badge" id="entityCount">0</span>
96
- </a>
97
- <a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
98
- <span class="nav-icon">&#9679;</span> Interactions
99
- </a>
100
- <a class="nav-item" onclick="showSection('consol-permissions')" data-section="consol-permissions">
101
- <span class="nav-icon">&#9679;</span> Coherence des acces
102
- </a>
103
- <a class="nav-item" onclick="showSection('consol-flows')" data-section="consol-flows">
104
- <span class="nav-icon">&#9679;</span> Parcours bout en bout
105
- </a>
106
- </div>
107
-
108
- <!-- Phase 5 : Synthese -->
109
- <div class="nav-group">
110
- <div class="nav-group-title">5. Synthese</div>
111
- <a class="nav-item" onclick="showSection('handoff-summary')" data-section="handoff-summary">
112
- <span class="nav-icon">&#9679;</span> Vue d'ensemble
113
- </a>
43
+ <!-- Dynamic Tree Navigation -->
44
+ <div id="sidebarNav">
45
+ <!-- Populated by buildNavTree() -->
114
46
  </div>
115
47
  </aside>
116
48
 
@@ -26,14 +26,15 @@ const FEATURE_DATA = {
26
26
  scope: {
27
27
  // CONVERT feature.json keys to HTML keys:
28
28
  // mustHave → vital, shouldHave → important, couldHave → optional, outOfScope → excluded
29
+ // CRITICAL: Preserve per-item `module` assignment from coverageMatrix (NOT modules[0])
29
30
  vital: (master.cadrage.globalScope?.mustHave || master.cadrage.coverageMatrix?.filter(i => i.category === "mustHave") || [])
30
- .map(item => typeof item === 'string' ? { name: item, description: "" } : { name: item.item || item, description: item.notes || "" }),
31
+ .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
31
32
  important: (master.cadrage.globalScope?.shouldHave || master.cadrage.coverageMatrix?.filter(i => i.category === "shouldHave") || [])
32
- .map(item => typeof item === 'string' ? { name: item, description: "" } : { name: item.item || item, description: item.notes || "" }),
33
+ .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
33
34
  optional: (master.cadrage.globalScope?.couldHave || master.cadrage.coverageMatrix?.filter(i => i.category === "couldHave") || [])
34
- .map(item => typeof item === 'string' ? { name: item, description: "" } : { name: item.item || item, description: item.notes || "" }),
35
+ .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
35
36
  excluded: (master.cadrage.globalScope?.outOfScope || master.cadrage.coverageMatrix?.filter(i => i.category === "outOfScope") || [])
36
- .map(item => typeof item === 'string' ? { name: item, description: "" } : { name: item.item || item, description: item.notes || "" })
37
+ .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null })
37
38
  },
38
39
  risks: (master.cadrage.risks || []).map(r => ({
39
40
  description: r.description,
@@ -123,8 +124,9 @@ const FEATURE_DATA = {
123
124
  const EMBEDDED_ARTIFACTS = {
124
125
  wireframes: {
125
126
  // PER-MODULE keyed object (NOT a flat array)
126
- // FOR EACH module: extract from specification.uiWireframes[]
127
- [moduleCode]: moduleFeature.specification.uiWireframes.map(wf => ({
127
+ // FOR EACH module: extract from specification.uiWireframes[] OR specification.wireframes[] (SAFETY NET)
128
+ // IMPORTANT: The agent may write wireframes under either key name — always check BOTH
129
+ [moduleCode]: (moduleFeature.specification.uiWireframes || moduleFeature.specification.wireframes || []).map(wf => ({
128
130
  screen: wf.screen || wf.name || wf.id || "", // SAFETY NET: fallback name/id → screen
129
131
  section: wf.section || "", // e.g. "list"
130
132
  format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
@@ -161,7 +163,7 @@ const EMBEDDED_ARTIFACTS = {
161
163
 
162
164
  ### Artifact Gathering
163
165
 
164
- 1. For EACH module: read `specification.uiWireframes[]`, **rename fields** (`mockupFormat`→`format`, `mockup`→`content`), store under `wireframes[moduleCode]`
166
+ 1. For EACH module: read `specification.uiWireframes[]` OR `specification.wireframes[]` (check BOTH keys — agent may use either), **rename fields** (`mockupFormat`→`format`, `mockup`→`content`), store under `wireframes[moduleCode]`
165
167
  2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
166
168
  3. Read master's `dependencyGraph` and build nodes/edges
167
169
  4. Serialize as JSON with 2-space indentation
@@ -20,25 +20,17 @@ Build a JSON object following this **exact mapping** from feature.json to the HT
20
20
  analysisMode: master.metadata.analysisMode || "interactive" // always "interactive"
21
21
  },
22
22
  cadrage: {
23
- problem: {
24
- description: master.cadrage.problem, // string problem.description
25
- trigger: master.cadrage.trigger, // string → problem.trigger
26
- impactedPeople: "", // not in feature.json, client fills
27
- history: "",
28
- consequences: ""
29
- },
30
- current: {
31
- tools: master.cadrage.asIs, // string current.tools
32
- steps: [], // client can add process steps
33
- painPoints: master.cadrage.stakeholders
34
- .flatMap(s => s.painPoints || []).join("\n"), // aggregate all painPoints
35
- errors: ""
36
- },
37
- vision: {
38
- changes: master.cadrage.toBe, // string → vision.changes
39
- results: master.cadrage.acceptanceCriteria
40
- .map(ac => ac.criterion).join("\n"), // AC → results (one per line)
41
- successSign: ""
23
+ // CONTEXT SECTION — lean format (merged from problem/asIs/toBe)
24
+ // NOTE: 01-data-init.js has backward compat for old problem/current/vision format
25
+ context: {
26
+ problem: master.cadrage.problem || "", // flat string from feature.json
27
+ trigger: master.cadrage.trigger || "", // flat string from feature.json
28
+ currentSituation: master.cadrage.asIs || "", // flat string from feature.json
29
+ desiredSituation: master.cadrage.toBe || "", // flat string from feature.json
30
+ painPoints: (master.cadrage.stakeholders || [])
31
+ .flatMap(s => s.painPoints || []).join("\n"), // aggregate all painPoints
32
+ acceptanceCriteria: (master.cadrage.acceptanceCriteria || [])
33
+ .map(ac => ac.criterion).join("\n") // AC → newline-separated string
42
34
  },
43
35
  stakeholders: master.cadrage.stakeholders.map(s => ({
44
36
  role: s.role,
@@ -64,22 +56,22 @@ Build a JSON object following this **exact mapping** from feature.json to the HT
64
56
  impact: r.impact,
65
57
  mitigation: r.mitigation || ""
66
58
  })),
67
- assumptions: "",
68
- success: {
69
- definition: (master.cadrage.acceptanceCriteria || [])
70
- .map(ac => ac.criterion).join("\n"),
71
- metrics: "",
72
- timeline: "",
73
- minimumConditions: ""
74
- }
59
+ criteria: (master.cadrage.acceptanceCriteria || []).map(ac => ({
60
+ text: ac.criterion,
61
+ validated: ac.validated || false
62
+ }))
75
63
  },
76
64
  modules: master.modules.map(m => ({
77
65
  code: m.code,
78
- name: m.code, // module code as name
66
+ name: m.name || m.code, // display name (MANDATORY, separate from code)
79
67
  description: m.description || "",
80
68
  featureType: m.featureType || "data-centric",
81
69
  priority: m.priority || "must",
82
70
  entities: m.entities || [],
71
+ anticipatedSections: m.anticipatedSections || [], // [{code, description, resources[]}]
72
+ dependencies: m.dependencies || [],
73
+ dependents: m.dependents || [],
74
+ estimatedComplexity: m.estimatedComplexity || "medium",
83
75
  status: m.status || "handed-off"
84
76
  })),
85
77
  dependencies: (master.dependencyGraph?.edges || []).map(e => ({
@@ -44,7 +44,8 @@ Build EMBEDDED_ARTIFACTS with wireframes for completed modules only:
44
44
  const EMBEDDED_ARTIFACTS = {
45
45
  wireframes: {
46
46
  // FOR EACH completed module: extract wireframes with RENAMED fields
47
- [moduleCode]: moduleFeature.specification.uiWireframes.map(wf => ({
47
+ // SAFETY NET: check BOTH key names (agent may use either)
48
+ [moduleCode]: (moduleFeature.specification.uiWireframes || moduleFeature.specification.wireframes || []).map(wf => ({
48
49
  screen: wf.screen,
49
50
  section: wf.section,
50
51
  format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
@@ -48,6 +48,45 @@ Compile all data collected in step-03a (data) and step-03b (UI) into the module
48
48
 
49
49
  Generate the complete specification for this module. **Each subsection below includes a STRUCTURE CARD showing the EXACT JSON format. Follow them precisely.**
50
50
 
51
+ #### ENTITY ATTRIBUTE SCHEMA (MANDATORY — applies to ALL entities in section 6b of step-03a)
52
+
53
+ > **CRITICAL:** Entity attributes MUST use STRUCTURED properties, not free-text validation strings.
54
+ > The ralph-loop needs parseable types to generate correct C# code.
55
+
56
+ > **STRUCTURE CARD: analysis.entities[].attributes[]**
57
+ > ```json
58
+ > {
59
+ > "name": "occupancyRate",
60
+ > "description": "Employee occupancy percentage",
61
+ > "type": "decimal",
62
+ > "maxLength": null,
63
+ > "nullable": true,
64
+ > "required": false,
65
+ > "unique": false,
66
+ > "indexed": false,
67
+ > "defaultValue": "100",
68
+ > "validation": "min:0, max:100, step:0.01",
69
+ > "foreignKey": null
70
+ > }
71
+ > ```
72
+ > **MANDATORY fields:** `name`, `type`
73
+ > **type values:** `string`, `int`, `long`, `decimal`, `double`, `bool`, `DateTime`, `DateOnly`, `TimeOnly`, `Guid`, `enum:{EnumName}`, `byte[]`
74
+ > **For FK attributes:**
75
+ > ```json
76
+ > {
77
+ > "name": "departmentId",
78
+ > "description": "FK to Department",
79
+ > "type": "Guid",
80
+ > "required": true,
81
+ > "foreignKey": { "targetEntity": "Department", "targetField": "Id", "onDelete": "restrict" }
82
+ > }
83
+ > ```
84
+ > **FORBIDDEN:** Free-text-only validation like `"FK vers Employee"` or `"Max 100 caractères"` without corresponding structured properties.
85
+ >
86
+ > **BaseEntity inheritance:** All entities inherit `tenantId`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` from SmartStack.BaseEntity. Do NOT redeclare these. Document this once at the top of the entities section.
87
+
88
+ ---
89
+
51
90
  #### 8a. Actors
52
91
 
53
92
  Inherited from application roles → mapped to module permissions.
@@ -234,7 +273,7 @@ const navigationResources = specification.sections.flatMap(section =>
234
273
  );
235
274
  ```
236
275
 
237
- **Write to seedDataCore:**
276
+ **Write seedDataCore to specification:**
238
277
 
239
278
  ```
240
279
  ba-writer.enrichSection({
@@ -254,6 +293,9 @@ ba-writer.enrichSection({
254
293
  })
255
294
  ```
256
295
 
296
+ > **NOTE:** This step writes ONLY seedDataCore. The FULL specification write (actors, useCases, wireframes, sections, etc.) happens in step-03d section 11.
297
+ > ALL data from 8a-8l MUST be carried forward to step-03d. Do NOT discard in-memory data.
298
+
257
299
  **Validation:**
258
300
 
259
301
  - EVERY section in `specification.sections[]` MUST appear in `navigationSections`
@@ -280,8 +322,9 @@ BDD acceptance tests per UC.
280
322
  > ]
281
323
  > }
282
324
  > ```
283
- > **STRUCTURE:** Object with `feature` string + `scenarios[]` array. Each scenario has `given`, `when`, `then` as ARRAYS of strings.
325
+ > **STRUCTURE:** MUST be an ARRAY of objects (NOT a single object). Each object has `feature` string + `scenarios[]` array. Each scenario has `given`, `when`, `then` as ARRAYS of strings.
284
326
  > **FORBIDDEN:** Do NOT use flat arrays of `{uc, scenario, given, when, then}` where given/when/then are single strings.
327
+ > **FORBIDDEN:** Do NOT use a single object `{feature, scenarios}` — MUST be an ARRAY `[{feature, scenarios}]` even with one feature.
285
328
 
286
329
  #### 8h. Validations
287
330
 
@@ -405,6 +448,16 @@ Before loading step-03d-validate, verify all 12 subsections (8a-8l) are populate
405
448
 
406
449
  **IF any subsection is missing → STOP and request the missing data before proceeding.**
407
450
 
451
+ **SCHEMA UNIFORMITY CHECK (multi-module mode):**
452
+ IF this is NOT the first module in moduleOrder:
453
+ Compare THIS module's specification structure against the FIRST specified module:
454
+ - `gherkinScenarios` MUST be array (not object) in ALL modules
455
+ - `validations[].rules` MUST be array (not singular `rule`) in ALL modules
456
+ - `messages[]` MUST have `message` field in ALL modules
457
+ - `specification.apiEndpoints[]` MUST be present in ALL modules
458
+ - `specification.i18nKeys` MUST be present in ALL modules
459
+ IF any structural divergence detected → AUTO-FIX to match the canonical format before proceeding.
460
+
408
461
  ---
409
462
 
410
463
  ## NEXT STEP
@@ -34,21 +34,50 @@ Validate the module specification for completeness and consistency, write to fea
34
34
 
35
35
  #### 9a. Completeness Checks
36
36
 
37
- | Section | Minimum | Status |
38
- |---------|---------|--------|
39
- | actors | 2 | PASS/FAIL |
40
- | useCases | 2 | PASS/FAIL |
41
- | functionalRequirements | 4 | PASS/FAIL |
42
- | permissionMatrix | 1 resource × 2 roles | PASS/FAIL |
43
- | entities | 1 | PASS/FAIL |
44
- | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | PASS/FAIL |
45
- | wireframes | 1 per section (BLOCKING) | PASS/FAIL |
46
- | wireframeSchema | All required fields present (BLOCKING) | PASS/FAIL |
47
- | gherkinScenarios | 2 per UC | PASS/FAIL |
48
- | validations | 1 | PASS/FAIL |
49
- | messages | 4 | PASS/FAIL |
50
- | lifeCycles | 1 (if entity has status) | PASS/FAIL |
51
- | seedDataCore | 7 sections present | PASS/FAIL (BLOCKING) |
37
+ > **CRITICAL:** Checks MUST count ACTUAL elements in arrays, NOT declarative values.
38
+ > A check that reports PASS when the array is empty is a LIE and BLOCKS downstream quality.
39
+
40
+ | Section | Minimum | How to verify | Status |
41
+ |---------|---------|---------------|--------|
42
+ | actors | 2 | `specification.actors.length >= 2` | PASS/FAIL |
43
+ | useCases | 2 | `specification.useCases.length >= 2` | PASS/FAIL |
44
+ | functionalRequirements | 4 | `specification.functionalRequirements.length >= 4` | PASS/FAIL |
45
+ | permissionMatrix | 1 resource × 2 roles | `specification.permissionMatrix.permissions.length >= 1 && specification.permissionMatrix.roleAssignments.length >= 2` | PASS/FAIL |
46
+ | entities | 1 | `analysis.entities.length >= 1` | PASS/FAIL |
47
+ | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | `analysis.entities.every(e => e.attributes?.length > 0)` | PASS/FAIL |
48
+ | entityAttributeTypes | ALL attributes have `type` field (BLOCKING) | `analysis.entities.every(e => e.attributes.every(a => a.type))` | PASS/FAIL |
49
+ | wireframes | 1 per section (BLOCKING) | `specification.wireframes.length >= specification.sections.length` (count REAL elements) | PASS/FAIL |
50
+ | wireframeSchema | All required fields present (BLOCKING) | `specification.wireframes.every(w => w.screen && w.section && (w.mockup \|\| w.content))` | PASS/FAIL |
51
+ | sections | 1 (BLOCKING) | `specification.sections.length >= 1` (EVERY module needs at least 1 section) | PASS/FAIL |
52
+ | gherkinScenarios | 1 array entry | `Array.isArray(specification.gherkinScenarios) && specification.gherkinScenarios.length >= 1` | PASS/FAIL |
53
+ | gherkinFormat | Array not object (BLOCKING) | `Array.isArray(specification.gherkinScenarios)` (NOT a single object) | PASS/FAIL |
54
+ | validations | 1 | `specification.validations.length >= 1` | PASS/FAIL |
55
+ | validationFormat | rules[] array (BLOCKING) | `specification.validations.every(v => Array.isArray(v.rules))` (NOT singular `rule`) | PASS/FAIL |
56
+ | messages | 4 | `specification.messages.length >= 4` | PASS/FAIL |
57
+ | messageFormat | `message` field present (BLOCKING) | `specification.messages.every(m => m.message)` | PASS/FAIL |
58
+ | lifeCycles | 1 (if entity has status) | `specification.lifeCycles.length >= 1` (if any entity has status/state field) | PASS/FAIL |
59
+ | seedDataCore | 7 arrays present with content | See detailed check below | PASS/FAIL (BLOCKING) |
60
+ | apiEndpoints | 1 | `specification.apiEndpoints.length >= 1` | PASS/FAIL |
61
+ | i18nKeys | present | `specification.i18nKeys !== undefined && specification.i18nKeys !== null` | PASS/FAIL |
62
+ | navigationIcons | non-null | `specification.seedDataCore.navigationModules.every(m => m.icon !== null)` | PASS/FAIL |
63
+
64
+ **seedDataCore detailed check (BLOCKING):**
65
+ ```javascript
66
+ const sdc = specification.seedDataCore;
67
+ const checks = [
68
+ { key: "navigationModules", actual: sdc.navigationModules?.length || 0, min: 1 },
69
+ { key: "navigationSections", actual: sdc.navigationSections?.length || 0, min: 1 }, // EVERY module needs ≥1 section
70
+ { key: "navigationResources", actual: sdc.navigationResources?.length || 0, min: 1 },
71
+ { key: "navigationTranslations", actual: sdc.navigationTranslations?.length || 0, min: 2 }, // min fr+en
72
+ { key: "permissions", actual: sdc.permissions?.length || 0, min: 1 },
73
+ { key: "rolePermissions", actual: sdc.rolePermissions?.length || 0, min: 1 },
74
+ { key: "permissionConstants", actual: sdc.permissionConstants?.length || 0, min: 1 }
75
+ ];
76
+ const failures = checks.filter(c => c.actual < c.min);
77
+ IF failures.length > 0:
78
+ BLOCKING ERROR: "seedDataCore incomplete — empty arrays: {failures.map(f => f.key).join(', ')}"
79
+ → Fix: Ensure specification.sections[] has ≥1 entry, then re-run 8f-bis transform
80
+ ```
52
81
 
53
82
  #### 9b. Consistency Checks
54
83
 
@@ -278,6 +307,44 @@ ba-writer.updateStatus({module_feature_id}, "specified")
278
307
  ba-writer.updateModuleStatus({feature_id}, {currentModule.code}, "specified")
279
308
  ```
280
309
 
310
+ #### 11-POST-CHECK: Verify Written Data (BLOCKING)
311
+
312
+ > **CRITICAL — Data loss prevention.** After writing, READ BACK the module feature.json and verify the following arrays are **non-empty**:
313
+
314
+ ```javascript
315
+ // READ BACK the written feature.json
316
+ const written = ba-reader.read({module_feature_id});
317
+
318
+ // BLOCKING checks — if ANY fails, the write was incomplete
319
+ const checks = [
320
+ { key: "specification.actors", actual: written.specification?.actors?.length, min: 2 },
321
+ { key: "specification.useCases", actual: written.specification?.useCases?.length, min: 2 },
322
+ { key: "specification.wireframes", actual: written.specification?.wireframes?.length, min: 1 },
323
+ { key: "specification.sections", actual: written.specification?.sections?.length, min: 1 },
324
+ { key: "specification.seedDataCore", actual: Object.keys(written.specification?.seedDataCore || {}).length, min: 7 },
325
+ { key: "specification.lifeCycles", actual: written.specification?.lifeCycles?.length, min: 0 },
326
+ { key: "specification.gherkinScenarios",actual: written.specification?.gherkinScenarios?.length,min: 1 },
327
+ { key: "specification.apiEndpoints", actual: written.specification?.apiEndpoints?.length, min: 1 }
328
+ ];
329
+
330
+ const failures = checks.filter(c => (c.actual || 0) < c.min);
331
+
332
+ IF failures.length > 0:
333
+ BLOCKING ERROR: "Feature.json write INCOMPLETE — missing data in: {failures.map(f => f.key).join(', ')}"
334
+ → Re-execute section 11 write with ALL specification data
335
+ → DO NOT proceed to next module until ALL checks pass
336
+
337
+ // SPECIAL CHECK: wireframes content verification
338
+ IF written.specification?.wireframes?.length > 0:
339
+ const emptyMockups = written.specification.wireframes.filter(wf => !wf.mockup && !wf.content);
340
+ IF emptyMockups.length > 0:
341
+ WARNING: "{emptyMockups.length} wireframes have empty mockup content — verify step-03b data"
342
+ ```
343
+
344
+ > **WHY:** Step-03b generates wireframes in memory, step-03c only writes seedDataCore explicitly.
345
+ > If the comprehensive write in section 11 is skipped or partial, wireframes/sections are LOST.
346
+ > This POST-CHECK catches the data loss BEFORE advancing to the next module.
347
+
281
348
  ---
282
349
 
283
350
  ### 11-bis. Deploy Incremental Interactive HTML (MANDATORY)