@atlashub/smartstack-cli 3.16.0 → 3.18.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 +74 -42
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +752 -53
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/gitflow/finish.md +21 -3
  7. package/templates/agents/gitflow/start.md +14 -4
  8. package/templates/skills/application/templates-backend.md +12 -1
  9. package/templates/skills/business-analyse/SKILL.md +4 -4
  10. package/templates/skills/business-analyse/html/ba-interactive.html +11 -5
  11. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +11 -5
  12. package/templates/skills/business-analyse/references/deploy-data-build.md +25 -9
  13. package/templates/skills/business-analyse/references/validation-checklist.md +29 -2
  14. package/templates/skills/business-analyse/steps/step-00-init.md +23 -5
  15. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +21 -3
  16. package/templates/skills/business-analyse/steps/step-03b-ui.md +31 -1
  17. package/templates/skills/business-analyse/steps/step-03d-validate.md +41 -4
  18. package/templates/skills/business-analyse/steps/step-05b-deploy.md +9 -7
  19. package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +222 -40
  20. package/templates/skills/ralph-loop/SKILL.md +41 -1
  21. package/templates/skills/ralph-loop/references/category-rules.md +106 -1
  22. package/templates/skills/ralph-loop/references/compact-loop.md +85 -24
  23. package/templates/skills/ralph-loop/references/core-seed-data.md +48 -0
  24. package/templates/skills/ralph-loop/steps/step-00-init.md +30 -54
  25. package/templates/skills/ralph-loop/steps/step-01-task.md +102 -1
  26. package/templates/skills/ralph-loop/steps/step-04-check.md +87 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -77,10 +77,26 @@ git fetch origin develop:refs/remotes/origin/develop --force --quiet
77
77
 
78
78
  ### 4. Version Bump (Release/Hotfix)
79
79
 
80
- For npm projects:
80
+ After merge-back to develop, invoke `/version-bump` to set the next development version:
81
+
82
+ | Finished type | Next version | Example |
83
+ |---------------|-------------|---------|
84
+ | Release X.Y.Z | X.(Y+1).0 | 2.8.0 → 2.9.0 |
85
+ | Hotfix X.Y.Z | X.Y.(Z+1) | 2.8.1 → 2.8.2 |
86
+
81
87
  ```bash
82
- npm version $NEXT_VERSION --no-git-tag-version
83
- git add package.json package-lock.json
88
+ # Calculate NEXT_VERSION from the released version
89
+ # Then call: /version-bump $NEXT_VERSION
90
+ ```
91
+
92
+ This updates **all** version sources in sync:
93
+ - `Directory.Build.props` → `<VersionPrefix>X.Y.Z</VersionPrefix>`
94
+ - `web/smartstack-web/package.json` → `"version": "X.Y.Z"`
95
+ - `.claude/gitflow/config.json` → `versioning.current`
96
+
97
+ Then commit and push:
98
+ ```bash
99
+ git add Directory.Build.props web/smartstack-web/package.json .claude/gitflow/config.json
84
100
  git commit -m "$(cat <<'EOF'
85
101
  chore: bump version to $NEXT_VERSION for development
86
102
 
@@ -90,6 +106,8 @@ EOF
90
106
  git push origin develop
91
107
  ```
92
108
 
109
+ > **IMPORTANT**: Do NOT use `npm version` directly. Always use `/version-bump` for unified versioning.
110
+
93
111
  ### 5. Cleanup Worktree + Branch
94
112
 
95
113
  ```bash
@@ -75,14 +75,24 @@ git fetch origin $FULL_BRANCH:refs/remotes/origin/$FULL_BRANCH --force --quiet
75
75
 
76
76
  **Port allocation:** Hash branch name for unique ports (API: 5200+, Web: 5300+)
77
77
 
78
- ### 7. Version Bump (Release only)
78
+ ### 7. Version Bump (Release/Hotfix only)
79
+
80
+ For `release/*` and `hotfix/*` branches, invoke the `/version-bump` skill to synchronize all version files:
79
81
 
80
82
  ```bash
81
- # For release: bump version in source files
82
- npm version $VERSION --no-git-tag-version # if package.json
83
- # OR update .csproj <Version> tag
83
+ # Extract version from branch name: release/2.8.0 2.8.0, hotfix/2.8.1 → 2.8.1
84
+ VERSION=$(echo "$FULL_BRANCH" | sed 's|^[^/]*/||')
84
85
  ```
85
86
 
87
+ Then call: `/version-bump $VERSION`
88
+
89
+ This updates **all** version sources in sync:
90
+ - `Directory.Build.props` → `<VersionPrefix>X.Y.Z</VersionPrefix>`
91
+ - `web/smartstack-web/package.json` → `"version": "X.Y.Z"`
92
+ - `.claude/gitflow/config.json` → `versioning.current`
93
+
94
+ > **IMPORTANT**: Do NOT use `npm version` or edit `.csproj` directly. Always use `/version-bump` for unified versioning.
95
+
86
96
  ### 8. Summary
87
97
 
88
98
  ```
@@ -82,6 +82,12 @@ public interface I$MODULE_PASCALService
82
82
 
83
83
  ## TEMPLATE: SERVICE IMPLEMENTATION
84
84
 
85
+ > **TENANT ISOLATION RULES (BLOCKING):**
86
+ > - ALL queries MUST include `.Where(x => x.TenantId == _currentUser.TenantId)`
87
+ > - ALL entity creation MUST pass `_currentUser.TenantId` as first parameter to `Entity.Create()`
88
+ > - NEVER use `new Entity { }` without `TenantId =` — always prefer the factory method
89
+ > - NEVER use `Guid.Empty` as a placeholder — resolve the actual value from `_currentUser`
90
+
85
91
  ```csharp
86
92
  // src/SmartStack.Infrastructure/Services/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/$MODULE_PASCALService.cs
87
93
 
@@ -114,7 +120,8 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
114
120
  CancellationToken cancellationToken = default)
115
121
  {
116
122
  var query = _context.$ENTITY_PLURAL
117
- .AsNoTracking();
123
+ .AsNoTracking()
124
+ .Where(x => x.TenantId == _currentUser.TenantId); // MANDATORY: tenant isolation
118
125
 
119
126
  // Apply filters
120
127
  if (!string.IsNullOrWhiteSpace(parameters.SearchTerm))
@@ -167,6 +174,7 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
167
174
  {
168
175
  var entity = await _context.$ENTITY_PLURAL
169
176
  .AsNoTracking()
177
+ .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
170
178
  .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
171
179
 
172
180
  if (entity == null)
@@ -188,6 +196,7 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
188
196
  CancellationToken cancellationToken = default)
189
197
  {
190
198
  var entity = $ENTITY_PASCAL.Create(
199
+ _currentUser.TenantId, // MANDATORY: tenant isolation — always first parameter
191
200
  request.Name,
192
201
  request.Description);
193
202
 
@@ -216,6 +225,7 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
216
225
  CancellationToken cancellationToken = default)
217
226
  {
218
227
  var entity = await _context.$ENTITY_PLURAL
228
+ .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
219
229
  .FirstOrDefaultAsync(x => x.Id == id, cancellationToken)
220
230
  ?? throw new NotFoundException("$ENTITY_PASCAL", id);
221
231
 
@@ -245,6 +255,7 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
245
255
  CancellationToken cancellationToken = default)
246
256
  {
247
257
  var entity = await _context.$ENTITY_PLURAL
258
+ .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
248
259
  .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
249
260
 
250
261
  if (entity == null)
@@ -209,7 +209,7 @@ When step-00 detects that the description matches an existing application:
209
209
  | 04c | `steps/step-04c-decide.md` | Opus | Final approval, write consolidation, proceed to handoff |
210
210
  | 05a | `steps/step-05a-handoff.md` | Sonnet | Handoff: file mapping (7 categories), BR-to-code mapping, API summary, write handoff |
211
211
  | 05b | `steps/step-05b-deploy.md` | Sonnet | Deploy: prd.json, progress.txt, manifest, ba-interactive.html |
212
- | 05c | `steps/step-05c-ralph-readiness.md` | Sonnet | Ralph readiness validation gate (optional but recommended) |
212
+ | 05c | `steps/step-05c-ralph-readiness.md` | Opus | ULTRATHINK quality review + ralph readiness gate (MANDATORY) |
213
213
  | 06 | `steps/step-06-review.md` | Opus | Apply review corrections, create new version, regenerate all artifacts |
214
214
 
215
215
  </step_files>
@@ -252,10 +252,10 @@ Load ONLY relevant categories based on feature type:
252
252
  | -------- | ----------------------------------- | ------------ |
253
253
  | schema | `schemas/feature-schema.json` | All steps |
254
254
  | spec | `templates/tpl-frd.md` | 02 |
255
- | handoff | `templates/tpl-handoff.md` | 04 |
255
+ | handoff | `templates/tpl-handoff.md` | 05a |
256
256
  | suggestions | `patterns/suggestion-catalog.md` | 01 |
257
- | interactive | `html/ba-interactive.html` | 03d (incremental), 05d (final deploy) |
258
- | mapping | `references/html-data-mapping.md` | 03d, 05d (FEATURE_DATA + EMBEDDED_ARTIFACTS mapping) |
257
+ | interactive | `html/ba-interactive.html` | 03d (incremental), 05b (final deploy) |
258
+ | mapping | `references/html-data-mapping.md` | 03d, 05b (FEATURE_DATA + EMBEDDED_ARTIFACTS mapping) |
259
259
 
260
260
  </template_files>
261
261
 
@@ -3074,20 +3074,26 @@ function renderModuleMockups(code) {
3074
3074
  </div>` : ''}
3075
3075
  ${(wf.elements || []).length > 0 ? `
3076
3076
  <div class="wireframe-metadata">
3077
- <div><strong>Elements:</strong> ${wf.elements.join(', ')}</div>
3077
+ <div><strong>Elements:</strong> ${wf.elements.map(e => typeof e === 'string' ? e : (e.type || e.label || e.id || '')).filter(Boolean).join(', ')}</div>
3078
3078
  </div>` : ''}
3079
- ${(wf.componentMapping || []).length > 0 ? `
3079
+ ${(() => {
3080
+ const cm = wf.componentMapping;
3081
+ const mappings = Array.isArray(cm) ? cm
3082
+ : (typeof cm === 'object' && cm !== null) ? Object.entries(cm).map(([k,v]) => ({wireframeElement: k, reactComponent: v}))
3083
+ : [];
3084
+ return mappings.length > 0 ? `
3080
3085
  <details class="wireframe-details">
3081
3086
  <summary>Mapping composants SmartStack</summary>
3082
3087
  <table class="mapping-table">
3083
3088
  <thead><tr><th>Element maquette</th><th>Composant React</th></tr></thead>
3084
3089
  <tbody>
3085
- ${wf.componentMapping.map(m =>
3086
- `<tr><td>${m.wireframeElement}</td><td><code>${m.reactComponent}</code></td></tr>`
3090
+ ${mappings.map(m =>
3091
+ '<tr><td>' + (m.wireframeElement || '') + '</td><td><code>' + (m.reactComponent || '') + '</code></td></tr>'
3087
3092
  ).join('')}
3088
3093
  </tbody>
3089
3094
  </table>
3090
- </details>` : ''}
3095
+ </details>` : '';
3096
+ })()}
3091
3097
  <div class="wireframe-comment">
3092
3098
  <label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:0.3rem;">Commentaire / Feedback :</label>
3093
3099
  <textarea class="form-textarea"
@@ -392,20 +392,26 @@ function renderModuleMockups(code) {
392
392
  </div>` : ''}
393
393
  ${(wf.elements || []).length > 0 ? `
394
394
  <div class="wireframe-metadata">
395
- <div><strong>Elements:</strong> ${wf.elements.join(', ')}</div>
395
+ <div><strong>Elements:</strong> ${wf.elements.map(e => typeof e === 'string' ? e : (e.type || e.label || e.id || '')).filter(Boolean).join(', ')}</div>
396
396
  </div>` : ''}
397
- ${(wf.componentMapping || []).length > 0 ? `
397
+ ${(() => {
398
+ const cm = wf.componentMapping;
399
+ const mappings = Array.isArray(cm) ? cm
400
+ : (typeof cm === 'object' && cm !== null) ? Object.entries(cm).map(([k,v]) => ({wireframeElement: k, reactComponent: v}))
401
+ : [];
402
+ return mappings.length > 0 ? `
398
403
  <details class="wireframe-details">
399
404
  <summary>Mapping composants SmartStack</summary>
400
405
  <table class="mapping-table">
401
406
  <thead><tr><th>Element maquette</th><th>Composant React</th></tr></thead>
402
407
  <tbody>
403
- ${wf.componentMapping.map(m =>
404
- `<tr><td>${m.wireframeElement}</td><td><code>${m.reactComponent}</code></td></tr>`
408
+ ${mappings.map(m =>
409
+ '<tr><td>' + (m.wireframeElement || '') + '</td><td><code>' + (m.reactComponent || '') + '</code></td></tr>'
405
410
  ).join('')}
406
411
  </tbody>
407
412
  </table>
408
- </details>` : ''}
413
+ </details>` : '';
414
+ })()}
409
415
  <div class="wireframe-comment">
410
416
  <label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:0.3rem;">Commentaire / Feedback :</label>
411
417
  <textarea class="form-textarea"
@@ -42,7 +42,19 @@ const FEATURE_DATA = {
42
42
  "{moduleCode}": {
43
43
  useCases: module.specification.useCases || [],
44
44
  businessRules: module.analysis.businessRules || [],
45
- entities: module.analysis.entities || [],
45
+ // ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
46
+ entities: (module.analysis.entities || []).map(ent => ({
47
+ name: ent.name,
48
+ description: ent.description || "",
49
+ attributes: (ent.attributes || []).length > 0
50
+ ? ent.attributes.map(a => ({ name: a.name, description: a.description || "" }))
51
+ : (ent.fields || []).map(f => ({ name: f.name, description: f.description || f.type || "" })),
52
+ relationships: (ent.relationships || []).length > 0
53
+ ? ent.relationships.map(r =>
54
+ typeof r === 'string' ? r : `${r.target} (${r.type}) - ${r.description || ""}`)
55
+ : (ent.fields || []).filter(f => f.foreignKey)
56
+ .map(f => `${f.foreignKey.table} (N:1) - ${f.description || f.name}`)
57
+ })),
46
58
  permissions: module.specification.permissions || [],
47
59
  apiEndpoints: module.specification.apiEndpoints || []
48
60
  }
@@ -85,15 +97,19 @@ const EMBEDDED_ARTIFACTS = {
85
97
  // PER-MODULE keyed object (NOT a flat array)
86
98
  // FOR EACH module: extract from specification.uiWireframes[]
87
99
  [moduleCode]: moduleFeature.specification.uiWireframes.map(wf => ({
88
- screen: wf.screen, // e.g. "employees-list"
89
- section: wf.section, // e.g. "list"
90
- format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
91
- content: wf.mockup, // RENAME: mockup → content (ASCII art string)
100
+ screen: wf.screen || wf.name || wf.id || "", // SAFETY NET: fallback name/id → screen
101
+ section: wf.section || "", // e.g. "list"
102
+ format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
103
+ content: wf.mockup, // RENAME: mockup → content (ASCII art string)
92
104
  description: wf.description || "",
93
- elements: wf.elements || [], // ["DataGrid", "FilterBar", ...]
94
- actions: wf.actions || [], // ["filter", "sort", "create", ...]
95
- componentMapping: wf.componentMapping || [], // [{ wireframeElement, reactComponent }]
96
- layout: wf.layout || null, // { type, regions: [...] }
105
+ elements: wf.elements || [], // [{ id, type, label }] or ["DataGrid", ...]
106
+ actions: wf.actions || [], // [{ trigger, action }] or ["filter", ...]
107
+ // SAFETY NET: convert object array format if agent used { "Header": "PageHeader" } instead of [{ wireframeElement, reactComponent }]
108
+ componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
109
+ : typeof wf.componentMapping === 'object' && wf.componentMapping !== null
110
+ ? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
111
+ : [],
112
+ layout: typeof wf.layout === 'object' ? wf.layout : null, // SAFETY NET: ignore string layout ("grid")
97
113
  permissionsRequired: wf.permissionsRequired || []
98
114
  }))
99
115
  },
@@ -32,6 +32,18 @@ const checklist = {
32
32
  details: "Standalone entities should be rare in business modules"
33
33
  },
34
34
 
35
+ entitySchemaCompliance: {
36
+ check: "All entities use canonical format: attributes[] (not fields[]), relationships[] array present",
37
+ status: specification.entities.every(e =>
38
+ Array.isArray(e.attributes) && e.attributes.length > 0 &&
39
+ !e.fields && !e.tableName && !e.primaryKey
40
+ ) ? "PASS" : "FAIL",
41
+ blocking: true,
42
+ details: "Entities with 'fields' instead of 'attributes' break HTML data model display and PRD extraction. " +
43
+ "Check: every entity must have attributes[] (not fields[]), every entity must have relationships[], " +
44
+ "no entity should have tableName or primaryKey (technical fields belong in implementation, not analysis)"
45
+ },
46
+
35
47
  // SECTION 2: BUSINESS RULES (BLOCKING)
36
48
  businessRules: {
37
49
  minimum: 4,
@@ -133,6 +145,21 @@ const checklist = {
133
145
  details: "EVERY section MUST have a wireframe (ASCII/SVG)"
134
146
  },
135
147
 
148
+ wireframeSchemaCompliance: {
149
+ check: "All wireframes have required fields: screen, section, mockupFormat, elements[], componentMapping[], layout (object), permissionsRequired[]",
150
+ status: specification.uiWireframes.every(wf =>
151
+ wf.screen && wf.section && wf.mockupFormat &&
152
+ Array.isArray(wf.elements) && wf.elements.length > 0 &&
153
+ Array.isArray(wf.componentMapping) && wf.componentMapping.length > 0 &&
154
+ typeof wf.layout === 'object' && wf.layout !== null &&
155
+ Array.isArray(wf.permissionsRequired)
156
+ ) ? "PASS" : "FAIL",
157
+ blocking: true,
158
+ details: "Wireframes missing metadata render as empty frames in HTML documentation. " +
159
+ "Check: screen (not name/id), section, mockupFormat, elements[] non-empty, " +
160
+ "componentMapping[] as array of {wireframeElement, reactComponent}, layout as object (not string)"
161
+ },
162
+
136
163
  navigation: {
137
164
  check: "Module has ≥1 navigation entry",
138
165
  status: specification.navigation.entries.length >= 1 ? "PASS" : "FAIL",
@@ -261,11 +288,11 @@ ELSE:
261
288
 
262
289
  | Category | Checks | Passed | Failed | Warnings |
263
290
  |----------|--------|--------|--------|----------|
264
- | Data Model | 3 | {n} | {n} | {n} |
291
+ | Data Model | 4 | {n} | {n} | {n} |
265
292
  | Business Rules | 3 | {n} | {n} | {n} |
266
293
  | Use Cases & FRs | 4 | {n} | {n} | {n} |
267
294
  | Permissions | 3 | {n} | {n} | {n} |
268
- | UI & Navigation | 3 | {n} | {n} | {n} |
295
+ | UI & Navigation | 4 | {n} | {n} | {n} |
269
296
  | I18N & Messages | 3 | {n} | {n} | {n} |
270
297
  | Seed Data | 2 | {n} | {n} | {n} |
271
298
  | API Endpoints | 2 | {n} | {n} | {n} |
@@ -374,12 +374,30 @@ If initialization was interrupted:
374
374
 
375
375
  1. Check `.business-analyse/config.json` for currentFeature
376
376
  2. If feature ID exists, search for feature.json in `docs/business/`
377
- 3. If found, check `metadata.workflow.lastCompletedStep`:
378
- - If `"step-04-consolidation"` AND status = `"consolidated"`:
379
- Resume directly at `step-05a-handoff.md` (skip steps 00-04)
380
- → Display: "Resuming from consolidation — proceeding to handoff..."
377
+ 3. If found, check status and `metadata.workflow.lastCompletedStep`:
378
+
379
+ **Status-based resume routing (check in this order):**
380
+
381
+ - If status = `"handed-off"` AND `ba-interactive.html` missing:
382
+ → Resume at `step-05b-deploy.md` (deploy artifacts + HTML)
383
+ → Display: "Handoff complete but HTML/artifacts missing — deploying..."
384
+
381
385
  - If status = `"handed-off"` AND `.ralph/prd-*.json` files missing:
382
386
  → Resume at `step-05b-deploy.md` (only deploy artifacts needed)
383
- → Display: "Handoff complete but artifacts missing — deploying..."
387
+ → Display: "Handoff complete but PRD files missing — deploying..."
388
+
389
+ - If status = `"consolidated"` OR lastCompletedStep = `"step-04-consolidation"`:
390
+ → Resume directly at `step-05a-handoff.md` (skip steps 00-04)
391
+ → Display: "Resuming from consolidation — proceeding to handoff..."
392
+
393
+ - If status = `"specified"` AND `metadata.workflow.allModulesSpecified === true`:
394
+ → Resume at `step-04a-collect.md` (consolidation phase)
395
+ → Display: "All modules specified — resuming at consolidation..."
396
+
397
+ - If status = `"specified"` AND `metadata.workflow.completedModules.length > 0` AND `metadata.workflow.completedModules.length < metadata.workflow.moduleOrder.length`:
398
+ → Resume at `step-03a1-setup.md` (continue specifying remaining modules)
399
+ → Display: "Resuming module specification ({completedCount}/{totalCount})..."
400
+
384
401
  - Otherwise: offer to resume from last completed step
402
+
385
403
  4. If not found, create fresh feature.json
@@ -60,6 +60,20 @@ Define entities for this module (business attributes, not technical):
60
60
  > ```
61
61
  > **FORBIDDEN fields in attributes:** Do NOT use `type`, `values`, `rules`.
62
62
  > Use `description` to explain what the attribute is, `validation` for format/constraint rules, `unique` as boolean.
63
+ >
64
+ > **FORBIDDEN FORMAT — DO NOT USE (technical/database schema):**
65
+ > ```json
66
+ > {
67
+ > "name": "Project",
68
+ > "tableName": "rh_projects",
69
+ > "primaryKey": "id",
70
+ > "fields": [
71
+ > { "name": "code", "type": "UUID", "required": true }
72
+ > ]
73
+ > }
74
+ > ```
75
+ > **Why forbidden:** `tableName`, `primaryKey` are technical fields that do NOT belong in analysis. `fields[]` is NOT recognized by the build pipeline — only `attributes[]` is. `type` with SQL types (UUID, string, timestamp) belongs in implementation, not analysis. Using this format causes **empty entity data in the HTML documentation** and **broken PRD extraction**.
76
+ > The canonical format uses `attributes[]` (not `fields[]`), `relationships[]` (not FK inside fields), and NO technical fields (tableName, primaryKey).
63
77
 
64
78
  #### 6c. Process Flow
65
79
 
@@ -130,9 +144,13 @@ Before proceeding to step-03b-ui.md, VERIFY:
130
144
 
131
145
  1. **Module feature.json exists** at expected path (`docs/business/{app}/{module}/business-analyse/v{X.Y}/feature.json`)
132
146
  2. **Module feature.json has analysis section** with `entities[]` (at least 1) and `businessRules[]` (at least 1)
133
- 3. **Module status in master** = "in-progress" (set by section 2 above)
134
- 4. **Master modules[].featureJsonPath** for this module null (set by ba-writer.create)
135
- 5. **Sections identified** with clear roles and entities assigned
147
+ 3. **Entity schema compliance (BLOCKING)** EVERY entity has `attributes[]` (NOT `fields[]`) and `relationships[]` arrays.
148
+ Quick check: `analysis.entities.every(e => Array.isArray(e.attributes) && e.attributes.length >= 1)`
149
+ IF any entity has `fields[]` instead of `attributes[]` → STRUCTURAL ERROR, fix before proceeding.
150
+ IF any entity has `tableName` or `primaryKey` → STRUCTURAL ERROR, these are technical fields that do not belong in analysis.
151
+ 4. **Module status in master** = "in-progress" (set by section 2 above)
152
+ 5. **Master modules[].featureJsonPath** for this module ≠ null (set by ba-writer.create)
153
+ 6. **Sections identified** with clear roles and entities assigned
136
154
 
137
155
  **IF any check fails → FIX before proceeding.** Do NOT load step-03b-ui with incomplete data.
138
156
 
@@ -167,9 +167,36 @@ Example for a list section:
167
167
  Store in specification.uiWireframes[] (**MANDATORY** for every section):
168
168
 
169
169
  See [references/ui-resource-cards.md](../references/ui-resource-cards.md) for exact JSON format of `specification.uiWireframes[]`.
170
- **REQUIRED fields:** `screen`, `mockup`, `elements`, `section`, `componentMapping`, `layout` are ALL mandatory.
170
+ **REQUIRED fields:** `screen`, `mockup`, `mockupFormat`, `elements`, `section`, `actions`, `componentMapping`, `layout`, `permissionsRequired` are ALL mandatory.
171
171
  A wireframe without `componentMapping` or `layout` will FAIL validation in step 9.
172
172
 
173
+ > **STRUCTURE CARD: specification.uiWireframes[]** — ALL fields are MANDATORY. Do NOT omit any.
174
+ > ```json
175
+ > {
176
+ > "screen": "{module}-{section}", // MANDATORY — unique screen ID (NOT "name" or "id")
177
+ > "section": "list|detail|create|approve|...", // MANDATORY — section code
178
+ > "description": "Description en français",
179
+ > "mockupFormat": "ascii", // MANDATORY — "ascii" or "svg"
180
+ > "mockup": "┌───...┘", // MANDATORY — ASCII art string
181
+ > "elements": [ // MANDATORY — array of element objects
182
+ > { "id": "elem-grid", "type": "SmartTable", "label": "Grille" }
183
+ > ],
184
+ > "actions": [ // MANDATORY — user interactions
185
+ > { "trigger": "Click row", "action": "Navigate to detail" }
186
+ > ],
187
+ > "componentMapping": [ // MANDATORY — array of mapping objects
188
+ > { "wireframeElement": "DataGrid", "reactComponent": "SmartTable" }
189
+ > ],
190
+ > "layout": { // MANDATORY — object with regions (NOT a string)
191
+ > "type": "page",
192
+ > "regions": [{ "id": "main", "position": "main", "span": 12, "components": [...] }]
193
+ > },
194
+ > "permissionsRequired": ["read"] // MANDATORY — required permissions
195
+ > }
196
+ > ```
197
+ > **FORBIDDEN FORMAT:** wireframe with only `name` + `mockup` + `layout: "grid"` (string).
198
+ > Every wireframe MUST have ALL fields above. Missing metadata causes empty frames in the HTML documentation.
199
+
173
200
  > **IF client rejects a mockup:** Revise and re-propose until validated. Do NOT proceed without client approval on the layout.
174
201
 
175
202
  ### 3b-bis. Wireframe-to-Component Mapping
@@ -250,6 +277,9 @@ Before proceeding to step-03c-compile.md, VERIFY:
250
277
  6. **State machines defined** (if module has status fields) in `specification.lifeCycles[]`
251
278
  7. **Form field completeness** — SmartForm fields[] covers ALL entity attributes except system/audit fields
252
279
  8. **Navigation entries for all sections** — Every section (including dashboard) has a corresponding entry in `specification.navigation.entries[]`
280
+ 9. **Wireframe structural compliance (BLOCKING)** — ALL wireframes have: `screen` (not just `name`), `section`, `mockupFormat`, `elements[]` (non-empty array of objects), `actions[]`, `componentMapping[]` (array of {wireframeElement, reactComponent}), `layout` (object with regions, NOT a string), `permissionsRequired[]`
281
+ Quick check: `uiWireframes.every(wf => wf.screen && wf.section && wf.mockupFormat && Array.isArray(wf.elements) && wf.elements.length > 0 && typeof wf.layout === 'object' && wf.layout !== null)`
282
+ IF any wireframe fails → FIX before proceeding. DO NOT load step-03c with degraded wireframes.
253
283
 
254
284
  **IF any check fails → FIX before proceeding.** Do NOT load step-03c-compile with incomplete data.
255
285
 
@@ -41,7 +41,9 @@ Validate the module specification for completeness and consistency, write to fea
41
41
  | functionalRequirements | 4 | PASS/FAIL |
42
42
  | permissionMatrix | 1 resource × 2 roles | PASS/FAIL |
43
43
  | entities | 1 | PASS/FAIL |
44
+ | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | PASS/FAIL |
44
45
  | wireframes | 1 per section (BLOCKING) | PASS/FAIL |
46
+ | wireframeSchema | All required fields present (BLOCKING) | PASS/FAIL |
45
47
  | gherkinScenarios | 2 per UC | PASS/FAIL |
46
48
  | validations | 1 | PASS/FAIL |
47
49
  | messages | 4 | PASS/FAIL |
@@ -65,6 +67,8 @@ Validate the module specification for completeness and consistency, write to fea
65
67
  - BR-{CATEGORY}-NNN format
66
68
  - Entity names PascalCase
67
69
  - Field names camelCase
70
+ - Entity attribute format: `attributes[]` with {name, description}, NOT `fields[]` with {name, type} — entities must NOT have tableName or primaryKey
71
+ - Wireframe structure: `screen` (not `name`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
68
72
  - Permission paths dot-separated lowercase
69
73
 
70
74
  #### 9d. Decision
@@ -132,9 +136,9 @@ ba-writer.enrichSection({
132
136
 
133
137
  **Execute the comprehensive validation checklist:**
134
138
 
135
- Run the 25-check validation process across 10 categories:
136
- - Data Model (3 checks) | Business Rules (3 checks) | Use Cases & FRs (4 checks)
137
- - Permissions (3 checks) | UI & Navigation (3 checks) | I18N & Messages (3 checks)
139
+ Run the 27-check validation process across 10 categories:
140
+ - Data Model (4 checks) | Business Rules (3 checks) | Use Cases & FRs (4 checks)
141
+ - Permissions (3 checks) | UI & Navigation (4 checks) | I18N & Messages (3 checks)
138
142
  - Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (1 check)
139
143
 
140
144
  ```javascript
@@ -161,6 +165,24 @@ ELSE:
161
165
 
162
166
  ---
163
167
 
168
+ ### 9g. Anti-Premature-Completion Guard (MANDATORY)
169
+
170
+ > **CRITICAL — NEVER say "the analysis is complete" or "ready for /ralph-loop" at this point.**
171
+ > Step-03d is the END of SPECIFICATION, NOT the end of the BA workflow.
172
+ > Steps 04a → 04b → 04c (consolidation) + 05a → 05b → 05c (handoff) are STILL REQUIRED.
173
+ > Without handoff, /ralph-loop will generate an INCOMPLETE PRD (no frontend, no tests).
174
+
175
+ **FORBIDDEN phrases in step-03d output:**
176
+ - "L'analyse métier est complète" / "The business analysis is complete"
177
+ - "Prêt pour /ralph-loop" / "Ready for /ralph-loop"
178
+ - "Vous pouvez maintenant lancer /ralph-loop"
179
+ - Any variation suggesting the BA is finished or /ralph-loop can be invoked
180
+
181
+ **REQUIRED phrasing:**
182
+ - "Module {name} spécifié. {Remaining} modules restants." (if more modules)
183
+ - "Tous les modules spécifiés. Passage à la consolidation (étapes 04a-04c)..." (if last module)
184
+ - NEVER mention /ralph-loop — only step-05c (ralph readiness) validates the handoff
185
+
164
186
  ### 10. Module Summary with Roles & Permissions
165
187
 
166
188
  Display comprehensive summary:
@@ -282,8 +304,23 @@ IF currentModuleIndex < moduleOrder.length:
282
304
  Load: steps/step-03a1-setup.md
283
305
 
284
306
  IF currentModuleIndex >= moduleOrder.length:
285
- Display: "═══ Tous les modules spécifiés! Passage à la consolidation... ═══"
307
+ Display: "═══ Tous les modules spécifiés! Passage à la consolidation (04a→04c) puis handoff (05a→05c)... ═══"
308
+ Display: "⚠ NE PAS lancer /ralph-loop — la consolidation et le handoff sont encore nécessaires."
286
309
  ba-writer.updateStatus({feature_id}, "specified")
310
+
311
+ // CHECKPOINT: Save progress BEFORE transition (protects against context exhaustion)
312
+ ba-writer.enrichSection({
313
+ featureId: {feature_id},
314
+ section: "metadata.workflow",
315
+ data: {
316
+ lastCompletedStep: "step-03d-validate",
317
+ allModulesSpecified: true
318
+ }
319
+ })
320
+
321
+ // MANDATORY TRANSITION — DO NOT STOP HERE
322
+ // If context is near exhaustion, the checkpoint above ensures the user
323
+ // can resume with /business-analyse and it will route to step-04a automatically.
287
324
  Load: steps/step-04a-collect.md
288
325
  ```
289
326
 
@@ -360,10 +360,9 @@ Effort: {total_days} days ({total_hours} hours)
360
360
  1. Ouvrir ba-interactive.html dans le navigateur
361
361
  2. Partager avec les stakeholders pour validation
362
362
  3. Si retours --> relancer /business-analyse pour une nouvelle itération
363
- 4. Validation de la compatibilité ralph-loop (recommandé):
363
+ 4. Validation qualité ULTRATHINK (automatique):
364
364
 
365
- Continuez à step-05c-ralph-readiness.md pour valider
366
- la complétude et l'intégrité avant développement
365
+ Lancement automatique de step-05c-ralph-readiness.md...
367
366
 
368
367
  5. Une fois validé, lancer le développement:
369
368
 
@@ -376,16 +375,19 @@ Effort: {total_days} days ({total_hours} hours)
376
375
 
377
376
  ## NEXT STEP
378
377
 
379
- **RECOMMENDED:** Load `steps/step-05c-ralph-readiness.md` to run validation gate before /ralph-loop.
378
+ **MANDATORY:** Load `steps/step-05c-ralph-readiness.md` ULTRATHINK quality review & readiness gate.
379
+
380
+ > **This step is NON-NEGOTIABLE.** The BA skill MUST run step-05c before completing.
381
+ > DO NOT skip this step. DO NOT display a "done" message without running step-05c first.
380
382
 
381
383
  This validation ensures:
382
- - All module handoffs are complete
384
+ - ULTRATHINK deep quality review of the entire BA output
385
+ - All module handoffs are complete and content is coherent
383
386
  - PRD files are structurally valid
384
387
  - Cross-module references are resolvable
388
+ - Specification quality meets production standards
385
389
  - No blocking issues before development
386
390
 
387
- User can skip validation and proceed directly to /ralph-loop, but validation is strongly recommended to catch issues early.
388
-
389
391
  ---
390
392
 
391
393
  ## MODE SUPPORT & TROUBLESHOOTING