@atlashub/smartstack-cli 3.20.0 → 3.22.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 (41) hide show
  1. package/dist/index.js +70 -6
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +69 -3
  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/application/references/application-roles-template.md +2 -2
  9. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  10. package/templates/skills/application/templates-frontend.md +64 -36
  11. package/templates/skills/business-analyse/html/ba-interactive.html +642 -156
  12. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  13. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  14. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  15. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +95 -8
  16. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  17. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  18. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  19. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  20. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  21. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  22. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  23. package/templates/skills/business-analyse/html/src/template.html +8 -76
  24. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  25. package/templates/skills/business-analyse/references/deploy-data-build.md +13 -9
  26. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  27. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  28. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  29. package/templates/skills/business-analyse/references/validate-incremental-html.md +28 -5
  30. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  31. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  32. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  33. package/templates/skills/business-analyse/steps/step-03c-compile.md +169 -2
  34. package/templates/skills/business-analyse/steps/step-03d-validate.md +217 -28
  35. package/templates/skills/business-analyse/steps/step-05a-handoff.md +189 -3
  36. package/templates/skills/business-analyse/steps/step-05b-deploy.md +55 -0
  37. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  38. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  39. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  40. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  41. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -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
+ }
@@ -61,6 +61,48 @@
61
61
  .svg-wireframe svg {
62
62
  max-width: 100%;
63
63
  height: auto;
64
+ display: block;
65
+ }
66
+
67
+ /* ============================================
68
+ WIREFRAME VIEW TOGGLE (SVG / ASCII)
69
+ ============================================ */
70
+ .wireframe-toggle {
71
+ display: flex;
72
+ gap: 2px;
73
+ background: var(--bg-dark);
74
+ border-radius: 6px;
75
+ padding: 2px;
76
+ border: 1px solid var(--border);
77
+ margin-left: auto;
78
+ }
79
+
80
+ .wireframe-toggle-btn {
81
+ padding: 0.25rem 0.6rem;
82
+ font-size: 0.7rem;
83
+ font-weight: 500;
84
+ letter-spacing: 0.02em;
85
+ border: none;
86
+ border-radius: 4px;
87
+ cursor: pointer;
88
+ background: transparent;
89
+ color: var(--text-muted);
90
+ transition: all 0.15s ease;
91
+ font-family: inherit;
92
+ }
93
+
94
+ .wireframe-toggle-btn:hover {
95
+ color: var(--text-bright);
96
+ background: var(--bg-hover);
97
+ }
98
+
99
+ .wireframe-toggle-btn.active {
100
+ background: var(--primary);
101
+ color: #ffffff;
102
+ }
103
+
104
+ .wireframe-view:not(.active) {
105
+ display: none;
64
106
  }
65
107
 
66
108
  .wireframe-description {
@@ -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
 
@@ -0,0 +1,169 @@
1
+ # Module Acceptance Criteria (Shared Reference)
2
+
3
+ > **Loaded by:** step-03d-validate (section 11-POST-CHECK-BASH), step-05a-handoff (pre-handoff gate), ralph-loop step-01-task (input validation)
4
+ > **Purpose:** Define measurable, bash-verifiable acceptance criteria that a module MUST pass before being marked "specified".
5
+ > **Key principle:** These checks read the REAL feature.json file on disk — NOT in-memory data the model "thinks" it wrote.
6
+
7
+ ---
8
+
9
+ ## Why Bash-Verifiable?
10
+
11
+ All previous checks were **pseudocode interpreted by the model**. The model can "decide" a check passes even when it fails (hallucination of validation). Bash checks are **objective** — they read the actual file and count real elements.
12
+
13
+ ---
14
+
15
+ ## Acceptance Criteria Table
16
+
17
+ | # | Criterion | Minimum | Field Path | Blocking | Category |
18
+ |---|-----------|---------|------------|----------|----------|
19
+ | AC-01 | Entities present | 1 | `analysis.entities[]` | YES | Data Model |
20
+ | AC-02 | Entity attributes have `type` field | ALL | `analysis.entities[].attributes[].type` | YES | Data Model |
21
+ | AC-03 | Use cases present | 2 | `specification.useCases[]` | YES | Requirements |
22
+ | AC-04 | Functional requirements present | 4 | `specification.functionalRequirements[]` | YES | Requirements |
23
+ | AC-05 | Wireframes present | 1 | `specification.uiWireframes[] \|\| specification.wireframes[]` | YES | UI |
24
+ | AC-06 | Wireframes >= sections | count match | wireframes.length >= sections.length | YES | UI |
25
+ | AC-07 | Wireframe content non-empty | ALL | `wireframe.mockup \|\| wireframe.ascii \|\| wireframe.content` | YES | UI |
26
+ | AC-08 | Sections present | 1 | `specification.sections[]` | YES | UI |
27
+ | AC-09 | SeedDataCore 7 arrays non-empty | 7/7 | `specification.seedDataCore.*` | YES | Seed Data |
28
+ | AC-10 | Gherkin scenarios is array | `Array.isArray` | `specification.gherkinScenarios` | YES | Format |
29
+ | AC-11 | API endpoints present | 1 | `specification.apiEndpoints[]` | YES | API |
30
+ | AC-12 | Messages present | 4 | `specification.messages[]` | YES | Messages |
31
+ | AC-13 | Validations present | 1 | `specification.validations[]` | YES | Validation |
32
+ | AC-14 | Wireframe field naming | canonical | `screen` not `title`, `mockup` not `ascii` | WARNING | Convention |
33
+ | AC-15 | Validation rules format | ALL array | `specification.validations[].rules` is `Array` | YES | Format |
34
+ | AC-16 | Messages have `message` field | ALL present | `specification.messages[].message` truthy | YES | Format |
35
+ | AC-17 | Gherkin content structure | ALL valid | Each element has `feature` (string) + `scenarios` (array) | YES | Format |
36
+
37
+ ---
38
+
39
+ ## Bash Verification Script (node -e)
40
+
41
+ > **Usage:** Called from step-03d section 11-POST-CHECK-BASH after writing module feature.json.
42
+ > Also callable standalone for debugging: `node -e "..." path/to/feature.json`
43
+
44
+ ```bash
45
+ MODULE_JSON="{module_feature_json_path}"
46
+ node -e "
47
+ const fs = require('fs');
48
+ const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
49
+ const spec = data.specification || {};
50
+ const analysis = data.analysis || {};
51
+ const wf = spec.uiWireframes || spec.wireframes || [];
52
+ const sections = spec.sections || [];
53
+ const sdc = spec.seedDataCore || {};
54
+
55
+ const checks = [
56
+ ['AC-01: entities >= 1', (analysis.entities||[]).length, 1],
57
+ ['AC-03: useCases >= 2', (spec.useCases||[]).length, 2],
58
+ ['AC-04: FRs >= 4', (spec.functionalRequirements||[]).length, 4],
59
+ ['AC-05: wireframes >= 1', wf.length, 1],
60
+ ['AC-06: wireframes >= sections', wf.length, sections.length],
61
+ ['AC-08: sections >= 1', sections.length, 1],
62
+ ['AC-09: seedDataCore 7 arrays', Object.keys(sdc).filter(k => Array.isArray(sdc[k]) && sdc[k].length > 0).length, 7],
63
+ ['AC-10: gherkin is array', Array.isArray(spec.gherkinScenarios) ? 1 : 0, 1],
64
+ ['AC-11: apiEndpoints >= 1', (spec.apiEndpoints||[]).length, 1],
65
+ ['AC-12: messages >= 4', (spec.messages||[]).length, 4],
66
+ ['AC-13: validations >= 1', (spec.validations||[]).length, 1]
67
+ ];
68
+
69
+ const fails = [];
70
+ checks.forEach(c => {
71
+ if (c[1] < c[2]) {
72
+ fails.push(c);
73
+ console.error('FAIL: ' + c[0] + ' = ' + c[1] + ' (min: ' + c[2] + ')');
74
+ }
75
+ });
76
+
77
+ // AC-02: Entity attribute types
78
+ const badAttrs = (analysis.entities||[]).flatMap(e =>
79
+ (e.attributes||[]).filter(a => !a.type).map(a => e.name + '.' + a.name)
80
+ );
81
+ if (badAttrs.length > 0) {
82
+ fails.push(['AC-02: attr.type missing', badAttrs.length, 0]);
83
+ console.error('FAIL: AC-02: ' + badAttrs.length + ' attributes without type: ' + badAttrs.slice(0, 5).join(', ') + (badAttrs.length > 5 ? '...' : ''));
84
+ }
85
+
86
+ // AC-07: Wireframe content non-empty
87
+ const emptyWf = wf.filter(w => !w.mockup && !w.ascii && !w.content);
88
+ if (emptyWf.length > 0) {
89
+ fails.push(['AC-07: wireframe content empty', emptyWf.length, 0]);
90
+ console.error('FAIL: AC-07: ' + emptyWf.length + ' wireframes have EMPTY content');
91
+ }
92
+
93
+ // AC-14: Wireframe field naming (WARNING only)
94
+ const badFields = wf.filter(w => w.title || w.ascii || (w.name && !w.screen));
95
+ if (badFields.length > 0) {
96
+ console.warn('WARNING: AC-14: ' + badFields.length + ' wireframes use non-canonical field names (title/ascii/name)');
97
+ }
98
+
99
+ // AC-15: Validation rules format (rules must be array, not string)
100
+ const badRules = (spec.validations||[]).filter(v => v.rules && !Array.isArray(v.rules));
101
+ if (badRules.length > 0) {
102
+ fails.push(['AC-15: validations[].rules not array', badRules.length, 0]);
103
+ console.error('FAIL: AC-15: ' + badRules.length + ' validations have rules as string instead of array');
104
+ }
105
+
106
+ // AC-16: Messages must have 'message' field
107
+ const noMsg = (spec.messages||[]).filter(m => !m.message);
108
+ if (noMsg.length > 0) {
109
+ fails.push(['AC-16: messages[].message missing', noMsg.length, 0]);
110
+ console.error('FAIL: AC-16: ' + noMsg.length + ' messages missing "message" field');
111
+ }
112
+
113
+ // AC-17: Gherkin content structure (each element needs feature + scenarios[])
114
+ if (Array.isArray(spec.gherkinScenarios)) {
115
+ const badGherkin = spec.gherkinScenarios.filter(g => !g.feature || !Array.isArray(g.scenarios));
116
+ if (badGherkin.length > 0) {
117
+ fails.push(['AC-17: gherkin content invalid', badGherkin.length, 0]);
118
+ console.error('FAIL: AC-17: ' + badGherkin.length + ' gherkin entries missing feature or scenarios[]');
119
+ }
120
+ }
121
+
122
+ // Summary
123
+ if (fails.length > 0) {
124
+ console.error('\\nBLOCKING: ' + fails.length + ' acceptance criteria FAILED');
125
+ process.exit(1);
126
+ }
127
+ console.log('PASS: All acceptance criteria met (AC-01 to AC-17)');
128
+ " "$MODULE_JSON"
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Integration Points
134
+
135
+ ### 1. step-03d-validate.md (section 11-POST-CHECK-BASH)
136
+
137
+ After writing module feature.json, run the bash script above.
138
+ IF FAIL → re-execute section 11 write with corrected data → re-run until PASS.
139
+
140
+ ### 2. step-05a-handoff.md (pre-handoff gate)
141
+
142
+ Before writing handoff for each module (section 7a step 3), run the bash script on the module feature.json.
143
+ IF FAIL → STOP handoff for this module, report which criteria failed.
144
+
145
+ ### 3. ralph-loop step-01-task.md (input validation)
146
+
147
+ At PRD load time, run the bash script on each module feature.json.
148
+ IF FAIL → BLOCKING: "Module {name} does not meet acceptance criteria — run /business-analyse to fix".
149
+ This prevents ralph-loop from generating code from incomplete specifications.
150
+
151
+ ---
152
+
153
+ ## Recovery Actions per Criterion
154
+
155
+ | Criterion | Recovery |
156
+ |-----------|----------|
157
+ | AC-01/02 | Re-run step-03a2-analysis (entity definition) |
158
+ | AC-03/04 | Re-run step-03c-compile sections 8b/8c |
159
+ | AC-05/06/07 | Re-run step-03b-ui (wireframe generation) — step-03b now writes intermediately |
160
+ | AC-08 | Re-run step-03b section 3a-bis (section definition) |
161
+ | AC-09 | Re-run step-03c section 8f-bis (seedDataCore transform) |
162
+ | AC-10 | Auto-fix: wrap in array `[gherkinScenarios]` |
163
+ | AC-11 | Re-run step-03c section 8k |
164
+ | AC-12 | Re-run step-03c section 8i |
165
+ | AC-13 | Re-run step-03c section 8h |
166
+ | AC-14 | Auto-fix: rename title→screen, ascii→mockup (done by step-03c/03d auto-fix) |
167
+ | AC-15 | Auto-fix: wrap string in array `[rules]` (done by step-03c ABSOLUTE FORMAT CHECKS) |
168
+ | AC-16 | Auto-fix: copy `description` → `message` (done by step-03c ABSOLUTE FORMAT CHECKS) |
169
+ | AC-17 | Re-run step-03c section 8g — ensure each gherkin entry has `feature` string + `scenarios` array |
@@ -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,12 +124,14 @@ 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 => ({
128
- screen: wf.screen || wf.name || wf.id || "", // SAFETY NET: fallback name/id → screen
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 => ({
130
+ screen: wf.screen || wf.name || wf.title || wf.id || "", // SAFETY NET: fallback name/title/id → screen
129
131
  section: wf.section || "", // e.g. "list"
130
132
  format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
131
- content: wf.mockup, // RENAME: mockup → content (ASCII art string)
133
+ content: wf.mockup || wf.ascii || wf.content || "", // SAFETY NET: mockup/ascii/content → content
134
+ svgContent: null, // Populated by SVG generation step (see wireframe-svg-style-guide.md)
132
135
  description: wf.description || "",
133
136
  elements: wf.elements || [], // [{ id, type, label }] or ["DataGrid", ...]
134
137
  actions: wf.actions || [], // [{ trigger, action }] or ["filter", ...]
@@ -161,10 +164,11 @@ const EMBEDDED_ARTIFACTS = {
161
164
 
162
165
  ### Artifact Gathering
163
166
 
164
- 1. For EACH module: read `specification.uiWireframes[]`, **rename fields** (`mockupFormat`→`format`, `mockup`→`content`), store under `wireframes[moduleCode]`
167
+ 1. For EACH module: read `specification.uiWireframes[]` OR `specification.wireframes[]` (check BOTH keys — agent may use either), **rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`, `screen`/`name`/`title`→`screen`), store under `wireframes[moduleCode]`
165
168
  2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
166
169
  3. Read master's `dependencyGraph` and build nodes/edges
167
170
  4. Serialize as JSON with 2-space indentation
171
+ 5. **SVG Wireframe Enrichment** (after wireframe extraction): For EACH wireframe with `content` (ASCII) and `svgContent === null`, generate SVG using parallel Task(sonnet) agents. See [wireframe-svg-style-guide.md](wireframe-svg-style-guide.md) for the complete prompt template and orchestration process. If generation fails for any wireframe → leave `svgContent` as null (graceful fallback to ASCII).
168
172
 
169
173
  ## Placeholder Replacement
170
174
 
@@ -82,10 +82,11 @@ From `specification.uiWireframes[]`, `specification.dashboards[]` and `analysis.
82
82
 
83
83
  ## 4.6 SeedData Files
84
84
 
85
- **OBLIGATORY: 5 CORE + business per module:**
85
+ **OBLIGATORY: 6 CORE (1 app-level + 5 per module) + business per module:**
86
86
 
87
87
  ```json
88
88
  "seedData": [
89
+ { "path": "src/Infrastructure/Persistence/Seeding/Data/NavigationApplicationSeedData.cs", "type": "SeedData", "category": "core", "source": "specification.seedDataCore.navigationApplications", "module": "shared", "description": "Application-level navigation seed data. Created ONCE per application (FIRST). Provides ApplicationId for modules and roles." },
89
90
  { "path": "src/Infrastructure/Persistence/Seeding/Data/{ModuleName}/NavigationModuleSeedData.cs", "type": "SeedData", "category": "core", "source": "specification.seedDataCore.navigationModules", "module": "{moduleCode}" },
90
91
  { "path": "src/Infrastructure/Persistence/Seeding/Data/{ModuleName}/PermissionsSeedData.cs", "type": "SeedData", "category": "core", "source": "specification.seedDataCore.permissions", "module": "{moduleCode}" },
91
92
  { "path": "src/Infrastructure/Persistence/Seeding/Data/{ModuleName}/RolesSeedData.cs", "type": "SeedData", "category": "core", "source": "specification.seedDataCore.roles", "module": "{moduleCode}" },