@atlashub/smartstack-cli 3.15.0 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +74 -42
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +752 -53
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/gitflow/finish.md +21 -3
- package/templates/agents/gitflow/start.md +14 -4
- package/templates/skills/application/templates-backend.md +12 -1
- package/templates/skills/business-analyse/html/ba-interactive.html +11 -5
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +11 -5
- package/templates/skills/business-analyse/references/deploy-data-build.md +25 -9
- package/templates/skills/business-analyse/references/validation-checklist.md +29 -2
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +21 -3
- package/templates/skills/business-analyse/steps/step-03b-ui.md +31 -1
- package/templates/skills/business-analyse/steps/step-03d-validate.md +7 -3
- package/templates/skills/ralph-loop/references/category-rules.md +11 -0
- package/templates/skills/ralph-loop/references/core-seed-data.md +48 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
#
|
|
82
|
-
|
|
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)
|
|
@@ -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
|
-
${(
|
|
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
|
-
${
|
|
3086
|
-
|
|
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
|
-
${(
|
|
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
|
-
${
|
|
404
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
section: wf.section,
|
|
90
|
-
format: wf.mockupFormat || "ascii",
|
|
91
|
-
content: wf.mockup,
|
|
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 || [],
|
|
94
|
-
actions: wf.actions || [],
|
|
95
|
-
|
|
96
|
-
|
|
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 |
|
|
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 |
|
|
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} |
|
|
@@ -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. **
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
|
-
- Data Model (
|
|
137
|
-
- Permissions (3 checks) | UI & Navigation (
|
|
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
|
|
@@ -141,6 +141,13 @@ Rules:
|
|
|
141
141
|
- DTOs separate from domain entities
|
|
142
142
|
- Service interfaces in Application, implementations in Infrastructure
|
|
143
143
|
|
|
144
|
+
**Tenant isolation (BLOCKING):**
|
|
145
|
+
- ALL queries on tenant entities MUST include `.Where(x => x.TenantId == _currentUser.TenantId)`
|
|
146
|
+
- ALL entity creation MUST pass `_currentUser.TenantId` as first parameter to `Entity.Create(tenantId, ...)`
|
|
147
|
+
- NEVER use `new Entity { }` without `TenantId =` — always prefer factory method `Entity.Create()`
|
|
148
|
+
- NEVER use `Guid.Empty` as a placeholder for userId, tenantId, or any business identifier
|
|
149
|
+
- Service constructor MUST inject `ICurrentUserService _currentUser` to access TenantId
|
|
150
|
+
|
|
144
151
|
**Lifecycle-aware services:**
|
|
145
152
|
- Services operating on entities with a `lifeCycle` (status field) MUST validate entity state before mutations
|
|
146
153
|
- Example: `if (entity.Status == EmployeeStatus.Terminated) throw new BusinessException("Cannot update terminated employee")`
|
|
@@ -158,6 +165,10 @@ Rules:
|
|
|
158
165
|
- Empty DependencyInjection.cs with `// TODO` placeholder
|
|
159
166
|
- CreateValidator without matching UpdateValidator
|
|
160
167
|
- Validators not registered in DI container
|
|
168
|
+
- Service query without TenantId filter (cross-tenant data leak)
|
|
169
|
+
- `new Entity { }` without TenantId assignment
|
|
170
|
+
- `Guid.Empty` as a business value in services or controllers
|
|
171
|
+
- Entity.Create() without tenantId as first parameter
|
|
161
172
|
|
|
162
173
|
---
|
|
163
174
|
|
|
@@ -385,6 +385,52 @@ public class NavigationResourceSeedEntry
|
|
|
385
385
|
|
|
386
386
|
## 3. PermissionsSeedData.cs — MCP-First
|
|
387
387
|
|
|
388
|
+
### CRITICAL: PermissionAction Safety Rules
|
|
389
|
+
|
|
390
|
+
> **NEVER use `Enum.Parse<PermissionAction>("...")` — this causes runtime crashes if the string is invalid.**
|
|
391
|
+
> The error only manifests at application startup, not at compile time.
|
|
392
|
+
|
|
393
|
+
**Valid PermissionAction values (from SmartStack.Domain.Authorization):**
|
|
394
|
+
|
|
395
|
+
| Enum Value | Int | Description |
|
|
396
|
+
|------------|-----|-------------|
|
|
397
|
+
| `PermissionAction.Access` | 0 | Wildcard permissions only (IsWildcard = true) |
|
|
398
|
+
| `PermissionAction.Read` | 1 | GET/HEAD — View data |
|
|
399
|
+
| `PermissionAction.Create` | 2 | POST — Create new records |
|
|
400
|
+
| `PermissionAction.Update` | 3 | PUT/PATCH — Modify existing records |
|
|
401
|
+
| `PermissionAction.Delete` | 4 | DELETE — Remove records |
|
|
402
|
+
| `PermissionAction.Export` | 5 | Export data (CSV, Excel, etc.) |
|
|
403
|
+
| `PermissionAction.Import` | 6 | Import data |
|
|
404
|
+
| `PermissionAction.Approve` | 7 | Approve workflow items |
|
|
405
|
+
| `PermissionAction.Reject` | 8 | Reject workflow items |
|
|
406
|
+
| `PermissionAction.Assign` | 9 | Assign items to users |
|
|
407
|
+
| `PermissionAction.Execute` | 10 | Execute actions (sync, run, etc.) |
|
|
408
|
+
|
|
409
|
+
**Anti-patterns (FORBIDDEN):**
|
|
410
|
+
|
|
411
|
+
```csharp
|
|
412
|
+
// FORBIDDEN — Runtime crash if string is not a valid enum value
|
|
413
|
+
Enum.Parse<PermissionAction>("Validate"); // ArgumentException at startup
|
|
414
|
+
(PermissionAction)Enum.Parse(typeof(PermissionAction), "Validate"); // Same crash
|
|
415
|
+
|
|
416
|
+
// FORBIDDEN — String-based action in anonymous objects
|
|
417
|
+
new { Action = "read" }; // Not type-safe, silent mismatch possible
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**Correct patterns (MANDATORY):**
|
|
421
|
+
|
|
422
|
+
```csharp
|
|
423
|
+
// ALWAYS use the typed enum directly — compile-time safe
|
|
424
|
+
Action = PermissionAction.Read // Compile-time checked
|
|
425
|
+
Action = PermissionAction.Create // Compile-time checked
|
|
426
|
+
Action = PermissionAction.Approve // Compile-time checked
|
|
427
|
+
|
|
428
|
+
// For custom actions beyond standard CRUD, pick from the enum:
|
|
429
|
+
// Export, Import, Approve, Reject, Assign, Execute
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**MCP validation:** `validate_conventions` with `checks: ["permissions"]` will detect and flag these anti-patterns.
|
|
433
|
+
|
|
388
434
|
### Step A: Call MCP (PRIMARY)
|
|
389
435
|
|
|
390
436
|
```
|
|
@@ -932,6 +978,8 @@ Before marking the task as completed, verify ALL:
|
|
|
932
978
|
- [ ] NavigationResources seeded (if `seedDataCore.navigationResources` present in feature.json)
|
|
933
979
|
- [ ] Section/Resource translations created (4 languages each, EntityType = Section/Resource)
|
|
934
980
|
- [ ] `dotnet build` passes after generation
|
|
981
|
+
- [ ] NO `Enum.Parse<PermissionAction>` usage anywhere in seeding code (use typed enum directly)
|
|
982
|
+
- [ ] ALL PermissionAction values are from the valid enum: Access, Read, Create, Update, Delete, Export, Import, Approve, Reject, Assign, Execute
|
|
935
983
|
|
|
936
984
|
**If ANY check fails, the task status = 'failed'.**
|
|
937
985
|
|