@atlashub/smartstack-cli 4.52.0 → 4.53.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.52.0",
3
+ "version": "4.53.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -0,0 +1,200 @@
1
+ # Canonical JSON Formats Reference
2
+
3
+ > **Purpose:** Single source of truth for all JSON file formats produced by the BA pipeline.
4
+ > **Used by:** `/business-analyse` (step-03), `/business-analyse-design` (step-01), `/business-analyse-html`, `/business-analyse-handoff`, `/business-analyse-develop`
5
+ > **Rule:** All producers MUST write canonical format. All consumers MUST accept canonical + deprecated fallbacks.
6
+
7
+ ---
8
+
9
+ ## entities.json
10
+
11
+ ```json
12
+ {
13
+ "entities": [
14
+ {
15
+ "name": "Employee",
16
+ "description": "...",
17
+ "personRoleConfig": { "variant": "mandatory", "userFields": ["firstName", "lastName", "email"] },
18
+ "attributes": [
19
+ { "name": "code", "type": "string", "required": true, "description": "..." },
20
+ { "name": "userId", "type": "string", "required": true, "description": "FK auth_Users (ASP.NET Identity)" }
21
+ ],
22
+ "versionedAttributes": [
23
+ { "entity": "Salary", "attributes": ["grossAmount", "effectiveDate"], "reason": "..." }
24
+ ],
25
+ "relationships": [
26
+ { "target": "Department", "type": "ManyToOne", "description": "..." }
27
+ ]
28
+ }
29
+ ]
30
+ }
31
+ ```
32
+
33
+ **Convention guards:**
34
+ - Person entities → `personRoleConfig` + `userId` (string), NO firstName/lastName/email
35
+ - Versioned data → `versionedAttributes[]`, NOT inline fields
36
+ - FK naming → always `{entity}Id` suffix
37
+ - FK to auth_Users → type `string` (ASP.NET Identity), NOT `guid`
38
+
39
+ ---
40
+
41
+ ## usecases.json
42
+
43
+ ```json
44
+ {
45
+ "useCases": [
46
+ {
47
+ "id": "UC-MODULE-001",
48
+ "name": "Créer un employé",
49
+ "sectionCode": "list",
50
+ "primaryActor": "Responsable RH",
51
+ "preconditions": ["..."],
52
+ "mainScenario": [
53
+ "L'utilisateur ouvre la page de création",
54
+ "Il remplit les champs obligatoires"
55
+ ],
56
+ "alternativeScenarios": [
57
+ { "name": "Données invalides", "steps": ["Le système affiche les erreurs"] }
58
+ ],
59
+ "businessRules": ["BR-VAL-MODULE-001"],
60
+ "result": "L'entité est créée"
61
+ }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ | Canonical key | Deprecated alternatives | Notes |
67
+ |---------------|------------------------|-------|
68
+ | `useCases` (root) | `usecases` | camelCase is canonical |
69
+ | `primaryActor` | `actor` | |
70
+ | `mainScenario` (string[]) | `steps` (object[] or string[]) | MUST be strings, never `{step, action}` objects |
71
+ | `alternativeScenarios` (object[]) | `alternativeFlows`, `alternative` (string) | Must be `[{name, steps}]` |
72
+
73
+ ---
74
+
75
+ ## rules.json
76
+
77
+ ```json
78
+ {
79
+ "rules": [
80
+ {
81
+ "id": "BR-VAL-MODULE-001",
82
+ "name": "Validation date",
83
+ "category": "validation",
84
+ "sectionCode": "list",
85
+ "statement": "La date ne peut pas être dans le futur",
86
+ "example": "Date = 2027-01-01 → erreur",
87
+ "entities": ["Employee"],
88
+ "severity": "blocking"
89
+ }
90
+ ]
91
+ }
92
+ ```
93
+
94
+ | Canonical key | Deprecated alternatives |
95
+ |---------------|------------------------|
96
+ | `rules` (root) | `businessRules` |
97
+ | `statement` | `description` |
98
+ | `id` | `name` (as identifier) |
99
+
100
+ Categories: `validation`, `calculation`, `workflow`, `security`, `data`, `notification`
101
+
102
+ ---
103
+
104
+ ## permissions.json
105
+
106
+ ```json
107
+ {
108
+ "roles": [
109
+ { "role": "RH Admin", "description": "..." }
110
+ ],
111
+ "permissionPaths": [
112
+ "HumanResources.Employees.Read",
113
+ "HumanResources.Employees.Create"
114
+ ],
115
+ "matrix": {
116
+ "roleAssignments": [
117
+ { "role": "RH Admin", "permissions": ["HumanResources.Employees.*"] }
118
+ ]
119
+ }
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## screens.json
126
+
127
+ ```json
128
+ {
129
+ "sections": [
130
+ {
131
+ "sectionCode": "list",
132
+ "sectionLabel": "Liste des employés",
133
+ "resources": [
134
+ {
135
+ "code": "employees-grid",
136
+ "type": "SmartTable",
137
+ "label": "Grille des employés",
138
+ "columns": [
139
+ { "field": "code", "label": "Code", "type": "text", "sortable": true }
140
+ ],
141
+ "filters": [
142
+ { "field": "status", "type": "select", "label": "Statut" }
143
+ ],
144
+ "actions": ["create", "export"],
145
+ "permission": "HumanResources.Employees.Read"
146
+ }
147
+ ]
148
+ }
149
+ ]
150
+ }
151
+ ```
152
+
153
+ | Canonical key | Deprecated alternatives | Notes |
154
+ |---------------|------------------------|-------|
155
+ | `sections` (root) | `screens` (flat array) | `sections[]` with `resources[]` is canonical |
156
+ | `sectionCode` | `id`, `code` | |
157
+ | `sectionLabel` | `displayName`, `label` | |
158
+ | resource `type` | `layout`, `componentType` | Must be: SmartTable, SmartForm, SmartDashboard, SmartKanban, SmartCard, SmartFilter |
159
+ | column `field` | `code`, `name`, `id`, `fieldCode` | |
160
+ | column `label` | `displayName` | |
161
+
162
+ **DEPRECATED format (do NOT produce):**
163
+ ```json
164
+ {
165
+ "screens": [
166
+ { "screen": "employees-list", "section": "list", "componentType": "SmartTable", "columns": [...] }
167
+ ]
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Consumer normalization pattern
174
+
175
+ All downstream consumers should normalize input using this priority chain:
176
+
177
+ ```javascript
178
+ // usecases
179
+ const rawUCs = data.useCases || data.usecases || [];
180
+ const uc = {
181
+ name: raw.name || raw.title || raw.id,
182
+ actor: raw.primaryActor || raw.actor,
183
+ steps: (raw.mainScenario || raw.steps || []).map(s =>
184
+ typeof s === 'string' ? s : (s.action || s.description || "")
185
+ ),
186
+ alternatives: raw.alternativeScenarios || raw.alternativeFlows || []
187
+ };
188
+
189
+ // screens
190
+ const sections = data.sections || [];
191
+ const flatScreens = data.screens || [];
192
+ // If flat format: wrap each screen as a section with 1 resource
193
+
194
+ // columns
195
+ const col = {
196
+ field: raw.field || raw.code || raw.name || raw.id,
197
+ label: raw.label || raw.displayName || raw.code,
198
+ type: raw.renderAs === "badge" ? "badge" : (raw.type || raw.dataType || "text")
199
+ };
200
+ ```
@@ -54,25 +54,56 @@ For each entity identified in step 02:
54
54
  {
55
55
  "name": "Employee",
56
56
  "description": "Représente un employé de l'entreprise",
57
+ "personRoleConfig": { "variant": "mandatory", "userFields": ["firstName", "lastName", "email"] },
57
58
  "attributes": [
58
- { "name": "code", "type": "string", "required": true, "description": "Identifiant unique" },
59
- { "name": "userId", "type": "guid", "required": true, "description": "Référence vers l'utilisateur" },
59
+ { "name": "code", "type": "string", "required": true, "description": "Identifiant unique auto-généré" },
60
+ { "name": "userId", "type": "string", "required": true, "description": "FK vers auth_Users (ASP.NET Identity — type string, NOT guid)" },
60
61
  { "name": "departmentId", "type": "guid", "required": true, "description": "Département d'affectation" },
61
62
  { "name": "hireDate", "type": "date", "required": true, "description": "Date d'embauche" },
62
63
  { "name": "position", "type": "string", "description": "Poste occupé" },
63
- { "name": "status", "type": "enum", "options": ["Active", "Inactive", "OnLeave", "Terminated"], "defaultValue": "Active", "description": "Statut de l'employé" },
64
- { "name": "salary", "type": "decimal", "validation": { "min": 0 }, "description": "Salaire mensuel" }
64
+ { "name": "status", "type": "enum", "options": ["Active", "Inactive", "OnLeave", "Terminated"], "defaultValue": "Active", "description": "Statut de l'employé" }
65
+ ],
66
+ "versionedAttributes": [
67
+ { "entity": "Salary", "attributes": ["grossAmount", "netAmount", "effectiveDate", "currency"], "reason": "Historique salarial versionné" }
65
68
  ],
66
69
  "estimatedVolume": { "monthly": 50, "total2y": 1200 },
67
70
  "searchableFields": ["code", "position", "status"],
68
71
  "defaultFilters": ["status"],
69
72
  "relationships": [
70
73
  { "target": "Department", "type": "ManyToOne", "description": "Appartient à un département" },
71
- { "target": "Contract", "type": "OneToMany", "description": "Possède plusieurs contrats" }
74
+ { "target": "Contract", "type": "OneToMany", "description": "Possède plusieurs contrats" },
75
+ { "target": "Salary", "type": "OneToMany", "description": "Historique de salaires versionnés" }
72
76
  ]
73
77
  }
74
78
  ```
75
79
 
80
+ ### B-bis. SmartStack Entity Convention Guards (MANDATORY)
81
+
82
+ Before finalizing each entity, apply these rules:
83
+
84
+ **B-bis-1. Person Extension Pattern** (ref: `entity-architecture-decision.md` section 0)
85
+ IF the entity matches a person role (Employee, Customer, Manager, Consultant, etc.):
86
+ - **DO NOT** add firstName, lastName, email, phoneNumber as direct attributes
87
+ - **DO** add `userId` (type: `string`, FK to auth_Users — ASP.NET Identity uses string IDs)
88
+ - **DO** add `personRoleConfig` metadata with variant (mandatory/optional)
89
+ - Personal fields come from User, not from the domain entity
90
+
91
+ **B-bis-2. Versioned Sensitive Data**
92
+ IF the entity has attributes that change over time with audit requirements (salary, rate, grade):
93
+ - **DO NOT** put them directly on the entity as single fields
94
+ - **DO** extract into a versioned satellite table (e.g., Employee → Salary with effectiveDate)
95
+ - Mark with `"versionedAttributes"` in entity spec
96
+
97
+ **B-bis-3. SmartStack Socle Entities (NEVER redefine)**
98
+ - Users/Identity → `auth_Users` (managed by SmartStack Identity)
99
+ - Tenants → `tenant_Tenants` (managed by SmartStack Core)
100
+ - Departments → `ref_Departments` or `rh_Departments` (check if exists in target DB)
101
+
102
+ **B-bis-4. Foreign Key Conventions**
103
+ - All FK attributes MUST end with `Id` suffix (e.g., `departmentId`, `userId`)
104
+ - FK to Identity users MUST use type `string` (ASP.NET Identity convention, NOT guid)
105
+ - FK to domain entities MUST use type `guid`
106
+
76
107
  ### C. Business Rules
77
108
 
78
109
  For each entity/process, identify rules. **Each rule MUST specify `sectionCode`** matching a code from `anticipatedSections[]`:
@@ -111,23 +142,39 @@ Exemple de règle de calcul :
111
142
 
112
143
  For each stakeholder action. **Each use case MUST specify `sectionCode`** matching a code from `anticipatedSections[]` — this links the UC to the screen/page where it happens:
113
144
 
145
+ > **CANONICAL FORMAT (MANDATORY):** The usecases.json file MUST use these exact keys:
146
+ > - Root key: `"useCases"` (camelCase, NOT "usecases")
147
+ > - Actor field: `"primaryActor"` (NOT "actor")
148
+ > - Steps field: `"mainScenario"` (string[], NOT "steps" with objects)
149
+ > - Alternatives: `"alternativeScenarios"` (object[] with `{name, steps}`, NOT "alternative" as flat string)
150
+ > - This matches specification-schema.json. Deviation causes normalization overhead in 4+ downstream skills.
151
+
114
152
  ```json
115
153
  {
116
- "id": "UC-EMPLOYEES-001",
117
- "name": "Créer un employé",
118
- "sectionCode": "list",
119
- "actor": "Responsable RH",
120
- "preconditions": ["L'utilisateur a la permission HumanResources.Employees.Create"],
121
- "steps": [
122
- "L'utilisateur ouvre la page de création",
123
- "Il remplit les champs obligatoires (nom, département, date embauche)",
124
- "Il valide le formulaire",
125
- "Le système vérifie les règles métier (BR-VAL-EMPLOYEES-001)",
126
- "Le système crée l'employé et affiche la fiche"
127
- ],
128
- "alternative": "Si les données sont invalides, le système affiche les erreurs",
129
- "businessRules": ["BR-VAL-EMPLOYEES-001"],
130
- "result": "L'employé est créé avec le statut 'Actif'"
154
+ "useCases": [
155
+ {
156
+ "id": "UC-EMPLOYEES-001",
157
+ "name": "Créer un employé",
158
+ "sectionCode": "list",
159
+ "primaryActor": "Responsable RH",
160
+ "preconditions": ["L'utilisateur a la permission HumanResources.Employees.Create"],
161
+ "mainScenario": [
162
+ "L'utilisateur ouvre la page de création",
163
+ "Il remplit les champs obligatoires (nom, département, date embauche)",
164
+ "Il valide le formulaire",
165
+ "Le système vérifie les règles métier (BR-VAL-EMPLOYEES-001)",
166
+ "Le système crée l'employé et affiche la fiche"
167
+ ],
168
+ "alternativeScenarios": [
169
+ {
170
+ "name": "Données invalides",
171
+ "steps": ["Le système affiche les erreurs de validation", "L'utilisateur corrige et re-soumet"]
172
+ }
173
+ ],
174
+ "businessRules": ["BR-VAL-EMPLOYEES-001"],
175
+ "result": "L'employé est créé avec le statut 'Actif'"
176
+ }
177
+ ]
131
178
  }
132
179
  ```
133
180
 
@@ -250,6 +297,33 @@ for (const mod of modules) {
250
297
  for (const a of (e.attributes || [])) {
251
298
  if (!a.type) warnings.push(mod.code + ": entity " + e.name + " attr '" + a.name + "' missing type");
252
299
  if (a.type === 'enum' && !a.defaultValue) errors.push(mod.code + ": enum attr '" + a.name + "' missing defaultValue");
300
+ // FK naming convention: must end with "Id"
301
+ if ((a.type === 'guid' || a.type === 'string') && a.foreignKey && !a.name.endsWith('Id'))
302
+ warnings.push(mod.code + ": entity " + e.name + " FK attr '" + a.name + "' should end with 'Id'");
303
+ }
304
+ // Person Extension Pattern check
305
+ const personFields = ['firstName', 'lastName', 'email', 'phoneNumber', 'phone'];
306
+ const foundPersonFields = (e.attributes || []).filter(a => personFields.includes(a.name));
307
+ if (foundPersonFields.length >= 3 && !e.personRoleConfig) {
308
+ errors.push(mod.code + ": entity '" + e.name + "' has " + foundPersonFields.length +
309
+ " person fields (" + foundPersonFields.map(f => f.name).join(", ") +
310
+ ") but no personRoleConfig — use Person Extension Pattern (entity-architecture-decision.md section 0). " +
311
+ "Person fields (firstName, lastName, email) belong on auth_Users, not on the domain entity.");
312
+ }
313
+ }
314
+
315
+ // Canonical usecases key check
316
+ const rawUCFile = READ(mod.dir + '/usecases.json');
317
+ if (rawUCFile && !rawUCFile.useCases && rawUCFile.usecases) {
318
+ errors.push(mod.code + ": usecases.json uses 'usecases' key instead of canonical 'useCases' — fix before proceeding");
319
+ }
320
+ // Check steps serialization format
321
+ for (const uc of ucs) {
322
+ if (uc.steps && Array.isArray(uc.steps) && uc.steps.length > 0 && typeof uc.steps[0] === 'object') {
323
+ errors.push(mod.code + ": UC '" + uc.id + "' uses steps[] with objects — must use mainScenario[] with strings");
324
+ }
325
+ if (uc.actor && !uc.primaryActor) {
326
+ warnings.push(mod.code + ": UC '" + uc.id + "' uses 'actor' instead of canonical 'primaryActor'");
253
327
  }
254
328
  }
255
329
 
@@ -149,28 +149,65 @@ Map entity attribute types to UI component types:
149
149
 
150
150
  For each module, write via ba-writer:
151
151
 
152
+ > **CANONICAL FORMAT (MANDATORY):** screens.json MUST use the `sections[]` wrapper format below.
153
+ > Keys MUST be `sectionCode` and `sectionLabel` (not `id`/`displayName`/`code`/`label`).
154
+ > Each section MUST have a `resources[]` array with typed resource objects (SmartTable, SmartForm, etc.).
155
+ > The flat `screens[]` format (one object per screen with componentType at top level) is **DEPRECATED**.
156
+ > All downstream consumers (business-analyse-html, business-analyse-handoff, business-analyse-develop) expect `sections[]`.
157
+
152
158
  ```json
153
159
  {
154
160
  "sections": [
155
161
  {
156
162
  "sectionCode": "list",
157
163
  "sectionLabel": "Liste des employes",
158
- "resources": [ /* SmartTable, filters */ ]
164
+ "resources": [
165
+ {
166
+ "code": "employees-grid",
167
+ "type": "SmartTable",
168
+ "label": "Grille des employes",
169
+ "columns": [ /* ... */ ],
170
+ "filters": [ /* ... */ ],
171
+ "actions": ["create", "export"],
172
+ "permission": "HumanResources.Employees.Read"
173
+ }
174
+ ]
159
175
  },
160
176
  {
161
- "sectionCode": "form",
177
+ "sectionCode": "detail",
162
178
  "sectionLabel": "Fiche employe",
163
- "resources": [ /* SmartForm with tabs */ ]
179
+ "resources": [
180
+ {
181
+ "code": "employee-form",
182
+ "type": "SmartForm",
183
+ "label": "Fiche employe",
184
+ "tabs": [ /* ... */ ],
185
+ "actions": ["save", "cancel"],
186
+ "permission": "HumanResources.Employees.Update"
187
+ }
188
+ ]
164
189
  },
165
190
  {
166
191
  "sectionCode": "dashboard",
167
192
  "sectionLabel": "Tableau de bord",
168
- "resources": [ /* SmartDashboard */ ]
193
+ "resources": [
194
+ {
195
+ "code": "employees-dashboard",
196
+ "type": "SmartDashboard",
197
+ "label": "Tableau de bord RH",
198
+ "kpis": [ /* ... */ ],
199
+ "charts": [ /* ... */ ],
200
+ "permission": "HumanResources.Employees.Read"
201
+ }
202
+ ]
169
203
  }
170
204
  ]
171
205
  }
172
206
  ```
173
207
 
208
+ > **Key naming:** Use `sectionCode` (not `id`, `code`). Use `sectionLabel` (not `displayName`, `label`).
209
+ > Resource `type` must be one of: `SmartTable`, `SmartForm`, `SmartDashboard`, `SmartKanban`, `SmartCard`, `SmartFilter`.
210
+
174
211
  Update `index.json` with screens.json hash and status.
175
212
 
176
213
  ## Validation
@@ -72,10 +72,19 @@ Transform module "{moduleCode}" for handoff.
72
72
  ## Instructions
73
73
  Read the following 5 files from {moduleDir}:
74
74
  1. entities.json → entities[]
75
- 2. usecases.json → useCases[]
76
- 3. rules.json → rules[]
75
+ 2. usecases.json → useCases[] (canonical key: "useCases", fallback: "usecases")
76
+ 3. rules.json → rules[] (canonical key: "rules", fallback: "businessRules")
77
77
  4. permissions.json → roles[], matrix[], permissionPaths[]
78
- 5. screens.json → screens[]
78
+ 5. screens.json → sections[] (canonical key: "sections", fallback: "screens")
79
+
80
+ ### Normalization Safety Net (BACKWARD COMPAT)
81
+ When reading flat files, prefer canonical keys but fall back to alternatives:
82
+ - useCases: `data.useCases || data.usecases || []`
83
+ - primaryActor: `uc.primaryActor || uc.actor`
84
+ - mainScenario: `uc.mainScenario || uc.steps` (if steps[] contains objects, extract `.action`)
85
+ - rules: `data.rules || data.businessRules || []`
86
+ - screens: `data.sections || data.screens` (if screens[] exists, treat each screen as a section with 1 resource)
87
+ This safety net handles pre-4.52 BA outputs. It should become unnecessary once step-03-specify enforces canonical keys.
79
88
 
80
89
  Then build the handoff data following these rules:
81
90