@atlashub/smartstack-cli 4.49.0 → 4.51.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 +1 -1
- package/templates/skills/apex/SKILL.md +30 -0
- package/templates/skills/apex/steps/step-03-execute.md +7 -0
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +4 -1
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +4 -1
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +4 -1
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +4 -1
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +2 -0
- package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +2 -0
- package/templates/skills/business-analyse/schemas/application-schema.json +36 -1
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +19 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +64 -14
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +49 -2
- package/templates/skills/business-analyse/steps/step-02-structure.md +41 -17
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +171 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +23 -6
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +11 -2
- package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +3 -1
- package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +9 -3
- package/templates/skills/business-analyse-html/references/data-build.md +4 -1
- package/templates/skills/business-analyse-html/references/data-mapping.md +4 -1
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +126 -1
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +35 -1
package/package.json
CHANGED
|
@@ -171,6 +171,36 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
171
171
|
|
|
172
172
|
</execution_rules>
|
|
173
173
|
|
|
174
|
+
<execution_guarantee>
|
|
175
|
+
|
|
176
|
+
## Execution Guarantee (CRITICAL — read before ANY step)
|
|
177
|
+
|
|
178
|
+
> **The user is NOT watching. There is NOBODY to answer your questions. Stopping is a FAILURE.**
|
|
179
|
+
|
|
180
|
+
When APEX runs (especially in `-d` delegate mode or `-a` auto mode), the execution is **fully autonomous**. The user has launched the command and walked away. Any pause, question, or numbered option list is a **dead end** — nobody will respond.
|
|
181
|
+
|
|
182
|
+
**YOU MUST NOT:**
|
|
183
|
+
- Stop to ask the user a question between layers
|
|
184
|
+
- Present numbered options ("Would you like to: 1. Continue... 2. Generate... 3. Stop...")
|
|
185
|
+
- Pause between layers to summarize progress and wait for confirmation
|
|
186
|
+
- Stop because the work seems "large" or "complex" — that is expected
|
|
187
|
+
- Add "Let me know if you want me to continue" at any point
|
|
188
|
+
|
|
189
|
+
**YOU MUST:**
|
|
190
|
+
- After Layer N completes, **IMMEDIATELY** load the next sub-step file. No pause, no summary, no question.
|
|
191
|
+
- Execute the full pipeline: Layer 0 → Layer 1 → Layer 2 → Layer 3 → Layer 4 → step-04, with **ZERO pauses** between layers
|
|
192
|
+
- Treat each "Load `steps/step-03X-...md`" instruction as a **mandatory immediate action**, not a suggestion
|
|
193
|
+
- Continue even if a layer produced warnings (only a build gate failure after max retries is a valid stop)
|
|
194
|
+
|
|
195
|
+
**The ONLY valid reasons to stop:**
|
|
196
|
+
1. A build gate fails after maximum retries (3 attempts)
|
|
197
|
+
2. ALL layers are completed and step-04 (eXamine) is done
|
|
198
|
+
3. The user presses Ctrl+C
|
|
199
|
+
|
|
200
|
+
**Anything else — including "being helpful" by asking — is a VIOLATION of the execution guarantee.**
|
|
201
|
+
|
|
202
|
+
</execution_guarantee>
|
|
203
|
+
|
|
174
204
|
<error_handling>
|
|
175
205
|
|
|
176
206
|
## Error Handling Strategy
|
|
@@ -65,6 +65,13 @@ BEFORE starting Layer N:
|
|
|
65
65
|
|
|
66
66
|
## Layer Dispatch
|
|
67
67
|
|
|
68
|
+
> **CRITICAL RULES — LAYER EXECUTION GUARANTEE**
|
|
69
|
+
>
|
|
70
|
+
> - **NEVER** stop between layers. NEVER ask the user. NEVER present numbered options between layers.
|
|
71
|
+
> - The pipeline is sequential and mandatory: **0 → 1 → 2 → 3 → 4 → step-04**, with **ZERO pauses**.
|
|
72
|
+
> - After each sub-step completes, IMMEDIATELY load the next one. No summary, no confirmation request.
|
|
73
|
+
> - Stopping between layers is a **VIOLATION** of the execution guarantee defined in SKILL.md.
|
|
74
|
+
|
|
68
75
|
Load each sub-step sequentially:
|
|
69
76
|
|
|
70
77
|
| Layer | Sub-step | Content |
|
|
@@ -136,6 +136,9 @@ feat({module}): [domain+infra] {short description}
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
## NEXT SUB-STEP
|
|
139
|
+
## NEXT SUB-STEP (MANDATORY — IMMEDIATE)
|
|
140
|
+
|
|
141
|
+
> **DO NOT STOP.** DO NOT ask the user. DO NOT present options. IMMEDIATELY load the next sub-step.
|
|
142
|
+
> Stopping here is a VIOLATION of the execution guarantee.
|
|
140
143
|
|
|
141
144
|
Load `steps/step-03b-layer1-seed.md`
|
|
@@ -254,6 +254,9 @@ feat({module}): [seed] navigation, permissions, roles
|
|
|
254
254
|
|
|
255
255
|
---
|
|
256
256
|
|
|
257
|
-
## NEXT SUB-STEP
|
|
257
|
+
## NEXT SUB-STEP (MANDATORY — IMMEDIATE)
|
|
258
|
+
|
|
259
|
+
> **DO NOT STOP.** DO NOT ask the user. DO NOT present options. IMMEDIATELY load the next sub-step.
|
|
260
|
+
> Stopping here is a VIOLATION of the execution guarantee.
|
|
258
261
|
|
|
259
262
|
Load `steps/step-03c-layer2-backend.md`
|
|
@@ -348,6 +348,9 @@ test({module}): backend unit and integration tests
|
|
|
348
348
|
|
|
349
349
|
---
|
|
350
350
|
|
|
351
|
-
## NEXT SUB-STEP
|
|
351
|
+
## NEXT SUB-STEP (MANDATORY — IMMEDIATE)
|
|
352
|
+
|
|
353
|
+
> **DO NOT STOP.** DO NOT ask the user. DO NOT present options. IMMEDIATELY load the next sub-step.
|
|
354
|
+
> Stopping here is a VIOLATION of the execution guarantee.
|
|
352
355
|
|
|
353
356
|
Load `steps/step-03d-layer3-frontend.md`
|
|
@@ -329,6 +329,9 @@ test({module}): frontend form tests
|
|
|
329
329
|
|
|
330
330
|
---
|
|
331
331
|
|
|
332
|
-
## NEXT SUB-STEP
|
|
332
|
+
## NEXT SUB-STEP (MANDATORY — IMMEDIATE)
|
|
333
|
+
|
|
334
|
+
> **DO NOT STOP.** DO NOT ask the user. DO NOT present options. IMMEDIATELY load the next sub-step.
|
|
335
|
+
> Stopping here is a VIOLATION of the execution guarantee.
|
|
333
336
|
|
|
334
337
|
Load `steps/step-03e-layer4-devdata.md`
|
|
@@ -27,6 +27,7 @@ Suggests companion modules based on primary module type:
|
|
|
27
27
|
| **Notifications** | notification, alert, email, message, broadcast | Templates, Channels, Scheduling, Preferences | Notification systems need template management, multi-channel support, scheduling |
|
|
28
28
|
| **Reporting** | report, dashboard, analytics, BI, metrics | Dashboards, Exports, Scheduling, AlertRules | Reporting needs visualization, scheduled distribution, and data export |
|
|
29
29
|
| **API Management / Integrations** | api, external, integration, webhook, export, data-export, machine-to-machine | ExternalApps, DataExportEndpoints, ExportAccess, AuditLogs, ApiKeys | API platforms need app registration, key management, granular endpoint access, rate limiting, and audit logging |
|
|
30
|
+
| **Client Portal** | portal, external, client, fournisseur, partenaire, tiers | MyRequests, ClientDashboard, PortalNotifications | Les utilisateurs externes ont besoin de vues self-service, suivi de statut et soumission limitée |
|
|
30
31
|
|
|
31
32
|
---
|
|
32
33
|
|
|
@@ -48,6 +49,7 @@ Suggests standard sections based on detected conditions:
|
|
|
48
49
|
| **Report generation** | Reporting Engine | Automated business reporting | Add section with template library, scheduling, distribution |
|
|
49
50
|
| **External integrations** | Integration Layer | System-to-system communication | Add section with API specifications, webhook handling, sync strategy |
|
|
50
51
|
| **Regulatory compliance** | Compliance & Audit | Legal/industry requirements | Add section with audit logging, data retention, retention periods |
|
|
52
|
+
| **Utilisateurs externes / portail détecté** | Sections portail client | Les utilisateurs externes ont besoin d'un espace dédié simplifié | Pour chaque module "shared" : suggérer section portal-specific (ex: `my-requests`, `portal-dashboard`) avec permissions read-only ou write limitée. Pour modules "external" : s'assurer que les sections ont une UX adaptée portail (navigation simplifiée, pas de fonctions admin). |
|
|
51
53
|
|
|
52
54
|
---
|
|
53
55
|
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
| Question | Si la réponse est vague | Relance recommandée |
|
|
89
89
|
|----------|------------------------|---------------------|
|
|
90
90
|
| Q2.1 (utilisateurs) | Un seul type mentionné | "Pensez aux différents moments de la journée : qui saisit ? Qui consulte les rapports ? Qui gère les cas particuliers ?" |
|
|
91
|
+
| Q2.1 (utilisateurs) | Utilisateurs hors entreprise mentionnés (clients, fournisseurs, partenaires) | "Ces utilisateurs externes accéderont-ils via un espace dédié (portail) ou la même interface que vos employés ?" |
|
|
91
92
|
| Q2.5 (tâches) | Tâches génériques | "Quand il arrive le matin et ouvre le système, quelle est sa première action ?" |
|
|
92
93
|
| Q2.9 (restrictions) | Réponse ambiguë | "Un employé voit-il les données salariales ? Un manager voit-il uniquement son équipe ou toute l'entreprise ?" |
|
|
93
94
|
| Q2.13 (indispensable) | Tout est indispensable | "Si vous ne pouviez garder que 3 fonctionnalités pour un premier lancement, lesquelles ?" |
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
| Tout est vital (> 10 vitaux) | Classification non réfléchie | Appliquer le test de classification : "Si on enlevait X, le système aurait-il encore de la valeur ?" |
|
|
104
105
|
| Aucune exclusion identifiée | Périmètre non borné | "Y a-t-il des aspects qui relèvent d'un autre projet ou d'une version future ?" |
|
|
105
106
|
| Parcours linéaire sans alternative | Seul le cas idéal est décrit | "Que se passe-t-il à l'étape X si la condition Y n'est pas remplie ?" |
|
|
107
|
+
| "Nos clients doivent pouvoir..." ou "Les fournisseurs voient..." | Utilisateurs externes accédant au système | Déclencher détection portail : classifier les profils en interne/externe. SmartStack gère le déploiement multi-tenant automatiquement. |
|
|
106
108
|
|
|
107
109
|
---
|
|
108
110
|
|
|
@@ -66,6 +66,12 @@
|
|
|
66
66
|
"type": "string",
|
|
67
67
|
"description": "Lucide icon name for application navigation entry (e.g., users, shopping-cart). Confirmed in step-02."
|
|
68
68
|
},
|
|
69
|
+
"portalMode": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": ["internal-only", "portal-only", "dual"],
|
|
72
|
+
"default": "internal-only",
|
|
73
|
+
"description": "Whether the app serves internal users, external portal users, or both"
|
|
74
|
+
},
|
|
69
75
|
"workflow": {
|
|
70
76
|
"type": "object",
|
|
71
77
|
"description": "Iterative module loop state",
|
|
@@ -126,7 +132,12 @@
|
|
|
126
132
|
"involvement": { "type": "string", "enum": ["approver", "decision-maker", "consulted", "informed", "end-user"] },
|
|
127
133
|
"tasks": { "type": "array", "items": { "type": "string" } },
|
|
128
134
|
"frequency": { "type": "string" },
|
|
129
|
-
"painPoints": { "type": "array", "items": { "type": "string" } }
|
|
135
|
+
"painPoints": { "type": "array", "items": { "type": "string" } },
|
|
136
|
+
"audience": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"enum": ["internal", "external", "shared"],
|
|
139
|
+
"description": "Whether this stakeholder is internal, external portal user, or both"
|
|
140
|
+
}
|
|
130
141
|
}
|
|
131
142
|
}
|
|
132
143
|
},
|
|
@@ -204,6 +215,11 @@
|
|
|
204
215
|
"type": "array",
|
|
205
216
|
"items": { "type": "string" },
|
|
206
217
|
"description": "Anticipated resources (Level 5) for this requirement — e.g., user-grid, user-form, user-card"
|
|
218
|
+
},
|
|
219
|
+
"audience": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"enum": ["internal", "external", "shared"],
|
|
222
|
+
"description": "Target audience. Only set when portalMode is 'dual'."
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
}
|
|
@@ -257,6 +273,11 @@
|
|
|
257
273
|
"type": "string",
|
|
258
274
|
"enum": ["simple", "medium", "complex"]
|
|
259
275
|
},
|
|
276
|
+
"audience": {
|
|
277
|
+
"type": "string",
|
|
278
|
+
"enum": ["internal", "external", "shared"],
|
|
279
|
+
"description": "Target audience. Derived from coverage matrix."
|
|
280
|
+
},
|
|
260
281
|
"anticipatedSections": {
|
|
261
282
|
"type": "array",
|
|
262
283
|
"description": "Anticipated sections (Level 4) with their resources (Level 5), from cadrage coverage matrix",
|
|
@@ -266,6 +287,20 @@
|
|
|
266
287
|
"properties": {
|
|
267
288
|
"code": { "type": "string", "description": "Section code (e.g., list, detail, create, edit, dashboard)" },
|
|
268
289
|
"description": { "type": "string" },
|
|
290
|
+
"sectionType": {
|
|
291
|
+
"type": "string",
|
|
292
|
+
"enum": ["primary", "functional", "view", "embedded"],
|
|
293
|
+
"description": "Section classification: primary (menu entry), functional (independent), view (sub-page of parent), embedded (widget in parent)"
|
|
294
|
+
},
|
|
295
|
+
"permissionMode": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"enum": ["crud", "custom", "read-only", "inherit"],
|
|
298
|
+
"description": "Permission model: crud (standard CRUD), custom (section-specific), read-only, inherit (from parent)"
|
|
299
|
+
},
|
|
300
|
+
"parentSectionCode": {
|
|
301
|
+
"type": ["string", "null"],
|
|
302
|
+
"description": "Parent section code for view/embedded sections (e.g., 'list'). Null for primary/functional sections."
|
|
303
|
+
},
|
|
269
304
|
"resources": {
|
|
270
305
|
"type": "array",
|
|
271
306
|
"items": { "type": "string" },
|
|
@@ -516,6 +516,20 @@
|
|
|
516
516
|
"required": ["code", "labels", "route", "permission", "wireframe", "useCases", "resources"],
|
|
517
517
|
"properties": {
|
|
518
518
|
"code": { "type": "string", "description": "Section code (kebab-case: list, detail, dashboard, approve, import, etc.)" },
|
|
519
|
+
"sectionType": {
|
|
520
|
+
"type": "string",
|
|
521
|
+
"enum": ["primary", "functional", "view", "embedded"],
|
|
522
|
+
"description": "Section classification: primary (menu entry), functional (independent), view (sub-page of parent), embedded (widget in parent)"
|
|
523
|
+
},
|
|
524
|
+
"permissionMode": {
|
|
525
|
+
"type": "string",
|
|
526
|
+
"enum": ["crud", "custom", "read-only", "inherit"],
|
|
527
|
+
"description": "Permission model: crud (standard CRUD), custom (section-specific), read-only, inherit (from parent)"
|
|
528
|
+
},
|
|
529
|
+
"parentSectionCode": {
|
|
530
|
+
"type": ["string", "null"],
|
|
531
|
+
"description": "Parent section code for view/embedded sections (e.g., 'list'). Null for primary/functional sections."
|
|
532
|
+
},
|
|
519
533
|
"labels": {
|
|
520
534
|
"type": "object",
|
|
521
535
|
"properties": {
|
|
@@ -531,6 +545,11 @@
|
|
|
531
545
|
"wireframe": { "type": "string", "description": "Reference to uiWireframes[].screen" },
|
|
532
546
|
"useCases": { "type": "array", "items": { "type": "string" } },
|
|
533
547
|
"businessRules": { "type": "array", "items": { "type": "string" } },
|
|
548
|
+
"audience": {
|
|
549
|
+
"type": "string",
|
|
550
|
+
"enum": ["internal", "external", "shared"],
|
|
551
|
+
"description": "Target audience. Inherited from module unless overridden."
|
|
552
|
+
},
|
|
534
553
|
"resources": {
|
|
535
554
|
"type": "array",
|
|
536
555
|
"description": "Level 5 (Resource) components within this section",
|
|
@@ -122,10 +122,10 @@ existing_projects: array of { projectName, projectId, applications[], version }
|
|
|
122
122
|
## Step 3b: Early Multi-Application Detection from Prompt (NEW)
|
|
123
123
|
|
|
124
124
|
> **Detect multi-app structure BEFORE asking for a single application name.**
|
|
125
|
-
> When the user's prompt
|
|
126
|
-
> and set `workflow.mode = "project"` to avoid doing cadrage for a single app.
|
|
125
|
+
> When the user's prompt describes multiple applications or spans multiple business domains,
|
|
126
|
+
> we must recognize this immediately and set `workflow.mode = "project"` to avoid doing cadrage for a single app.
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
### Tier 1 — Explicit patterns (ANY match = multi-app detected)
|
|
129
129
|
|
|
130
130
|
French patterns:
|
|
131
131
|
- "une application X ... une application Y" (two occurrences of "une application")
|
|
@@ -138,30 +138,65 @@ English patterns:
|
|
|
138
138
|
- "application #1: ... application #2: ..." (numbered)
|
|
139
139
|
- "first app ... second app"
|
|
140
140
|
|
|
141
|
-
If ANY of these patterns is found in `{feature_description}
|
|
141
|
+
If ANY of these patterns is found in `{feature_description}`:
|
|
142
|
+
→ `isMultiApp = true`, `detectionTier = "explicit"`
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
### Tier 2 — Domain diversity analysis (when Tier 1 does not match)
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
ULTRATHINK: Analyze `{feature_description}` to extract distinct business domains.
|
|
146
147
|
|
|
147
|
-
**
|
|
148
|
+
**Definition:** A "domain" is an autonomous business perimeter with its own entities and processes (e.g., HR, CRM, Finance, Projects, Sales, Inventory).
|
|
149
|
+
|
|
150
|
+
**Algorithm:**
|
|
151
|
+
1. List all business domains mentioned or implied in the description
|
|
152
|
+
2. Classify each domain:
|
|
153
|
+
- **DISTINCT** — has its own entities, processes, and lifecycle (e.g., HR vs CRM)
|
|
154
|
+
- **RELATED** — is a sub-domain of another (e.g., Payroll is a sub-domain of HR)
|
|
155
|
+
3. Count DISTINCT domains only
|
|
156
|
+
|
|
157
|
+
**Threshold:** 2+ DISTINCT domains → `isMultiApp = true`, `detectionTier = "domain-diversity"`
|
|
158
|
+
|
|
159
|
+
**False positive protection:**
|
|
160
|
+
- "RH complet avec employés, congés, paie, pointage" = 1 domain (HR) with 4 modules, NOT 4 domains
|
|
161
|
+
- "HR and Payroll management" = 1 domain (HR) with 2 modules — Payroll is RELATED to HR
|
|
162
|
+
- "gestion commerciale avec devis, commandes et facturation" = 1 domain (Sales) with 3 modules
|
|
163
|
+
- Only count as DISTINCT when domains have genuinely independent entity models and processes
|
|
164
|
+
|
|
165
|
+
### Extraction algorithm (depends on detection tier)
|
|
166
|
+
|
|
167
|
+
**IF Tier 1 (explicit):**
|
|
148
168
|
|
|
149
169
|
1. **Identify boundaries:** Split the prompt at each "une application" / "an application" occurrence. Each block = one candidate.
|
|
150
170
|
2. **Extract per candidate:** For each block, derive `name` (application name), `description` (summary of purpose), and `modules[]` (listed modules/features).
|
|
151
|
-
- Example
|
|
171
|
+
- Example: `[{ name: "RH", description: "gestion des employés, congés, temps", modules: ["Employés", "Congés", "Temps"] }, ...]`
|
|
152
172
|
3. **Detect shared modules:** Collect all module names across all candidates. Any module appearing in more than one candidate is a shared module.
|
|
153
173
|
- Example: `sharedModules = ["Temps"]` (appears in both RH and Projet)
|
|
154
174
|
|
|
155
|
-
|
|
175
|
+
**IF Tier 2 (domain-diversity):**
|
|
176
|
+
|
|
177
|
+
1. Each DISTINCT domain = 1 candidate application:
|
|
178
|
+
- `name` = short domain label (e.g., "RH", "CRM", "Finance")
|
|
179
|
+
- `description` = user's expressed need for that domain
|
|
180
|
+
- `modules[]` = sub-functionalities mentioned (can be empty → "(à définir au cadrage)")
|
|
181
|
+
2. Group RELATED sub-domains under their parent DISTINCT domain as modules
|
|
182
|
+
3. **Detect shared modules:** Same logic as Tier 1 — modules appearing in multiple candidates
|
|
183
|
+
|
|
184
|
+
### Confirmation dialogue
|
|
185
|
+
|
|
186
|
+
Display detection result:
|
|
156
187
|
|
|
157
188
|
```
|
|
158
|
-
{
|
|
159
|
-
?
|
|
160
|
-
|
|
189
|
+
{detectionTier == "explicit"
|
|
190
|
+
? (language == "fr"
|
|
191
|
+
? "### Détection multi-application\n\nJ'ai détecté **{candidates.length} applications** dans votre description :"
|
|
192
|
+
: "### Multi-application detection\n\nI detected **{candidates.length} applications** in your description:")
|
|
193
|
+
: (language == "fr"
|
|
194
|
+
? "### Détection multi-application\n\nVotre description couvre **{candidates.length} domaines métier distincts**. Chaque domaine pourrait constituer une application séparée :"
|
|
195
|
+
: "### Multi-application detection\n\nYour description covers **{candidates.length} distinct business domains**. Each domain could be a separate application:")}
|
|
161
196
|
|
|
162
197
|
| # | Application | Modules identifiés |
|
|
163
198
|
|---|-------------|-------------------|
|
|
164
|
-
{for each candidate: index | name | modules.join(", ")}
|
|
199
|
+
{for each candidate: index | name | modules.join(", ") || "(à définir au cadrage)"}
|
|
165
200
|
|
|
166
201
|
{sharedModules.length > 0
|
|
167
202
|
? "⚠️ **Modules partagés détectés :** {sharedModules.join(', ')} — ces modules apparaissent dans plusieurs applications. Ils pourraient constituer une application transversale dédiée."
|
|
@@ -177,6 +212,8 @@ options:
|
|
|
177
212
|
description: "{language == 'fr' ? 'Créer un projet avec les applications identifiées' : 'Create a project with the identified applications'}"
|
|
178
213
|
- label: "{language == 'fr' ? 'Extraire les modules partagés' : 'Extract shared modules'}" (only if sharedModules.length > 0)
|
|
179
214
|
description: "{language == 'fr' ? 'Créer une application dédiée pour {sharedModules.join(', ')} ({candidates.length + 1} applications au total)' : 'Create a dedicated app for {sharedModules.join(', ')} ({candidates.length + 1} total)'}"
|
|
215
|
+
- label: "{language == 'fr' ? 'Regrouper certains domaines' : 'Regroup some domains'}" (only if detectionTier == "domain-diversity")
|
|
216
|
+
description: "{language == 'fr' ? 'Fusionner des domaines en moins d\\'applications' : 'Merge domains into fewer applications'}"
|
|
180
217
|
- label: "{language == 'fr' ? 'Application unique' : 'Single application'}"
|
|
181
218
|
description: "{language == 'fr' ? 'Tout regrouper en une seule application avec plusieurs modules' : 'Group everything into one application with multiple modules'}"
|
|
182
219
|
```
|
|
@@ -193,6 +230,19 @@ shared_modules_extracted: boolean # true if user chose extraction
|
|
|
193
230
|
→ Skip step 4 (application name) — applications will be confirmed in step-01-cadrage
|
|
194
231
|
→ Continue to step 5 (language selection)
|
|
195
232
|
|
|
233
|
+
**IF "Regrouper certains domaines" (Tier 2 only):**
|
|
234
|
+
|
|
235
|
+
Ask via AskUserQuestion:
|
|
236
|
+
```
|
|
237
|
+
question: "{language == 'fr' ? 'Comment souhaitez-vous regrouper ces domaines ? (ex: \"RH + Paie ensemble, CRM seul\")' : 'How would you like to regroup these domains? (e.g., \"HR + Payroll together, CRM alone\")'}"
|
|
238
|
+
header: "Regroupement"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Process user response:
|
|
242
|
+
1. Parse the grouping instructions (natural language)
|
|
243
|
+
2. Rebuild `candidate_applications` based on the specified groups
|
|
244
|
+
3. Re-display the confirmation dialogue with the updated candidates
|
|
245
|
+
|
|
196
246
|
**IF "Application unique":**
|
|
197
247
|
|
|
198
248
|
```yaml
|
|
@@ -201,7 +251,7 @@ workflow_mode: "application"
|
|
|
201
251
|
|
|
202
252
|
→ Continue to step 4 normally
|
|
203
253
|
|
|
204
|
-
**IF no multi-app
|
|
254
|
+
**IF no multi-app detected (neither Tier 1 nor Tier 2):**
|
|
205
255
|
→ Continue to step 4 normally
|
|
206
256
|
|
|
207
257
|
## Step 4: Determine Application Name
|
|
@@ -239,6 +239,44 @@ Ask in 1-2 batches. After each batch:
|
|
|
239
239
|
- If "no restrictions" → probe: "Are there sensitive data (salary, contracts, personal info) that should be restricted to specific roles?"
|
|
240
240
|
- If tasks are generic → ask for a concrete scenario: "Walk me through a typical day"
|
|
241
241
|
|
|
242
|
+
#### 4b-bis. Détection Portail Client (ULTRATHINK — après batch stakeholders)
|
|
243
|
+
|
|
244
|
+
Analyser les réponses stakeholders pour signaux d'utilisateurs externes :
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
EXTERNAL_SIGNALS = ["client", "customer", "fournisseur", "supplier", "partenaire",
|
|
248
|
+
"partner", "portail", "portal", "externe", "external", "tiers", "third-party",
|
|
249
|
+
"organisation cliente"]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
SI un profil stakeholder match EXTERNAL_SIGNALS :
|
|
253
|
+
→ Stocker dans `{pre_analysis}`: `_portalDetected: true`
|
|
254
|
+
→ Poser la question via AskUserQuestion :
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
question: "{language == 'fr'
|
|
258
|
+
? 'J\'ai détecté des utilisateurs externes ({profiles}). Votre application comportera-t-elle un portail client en plus de la partie interne ?'
|
|
259
|
+
: 'I detected external users ({profiles}). Will your application include a client portal in addition to the internal part?'}"
|
|
260
|
+
header: "Portail client"
|
|
261
|
+
options:
|
|
262
|
+
- label: "{language == 'fr' ? 'Oui, portail + interne' : 'Yes, portal + internal'}"
|
|
263
|
+
description: "{language == 'fr' ? 'L\'application servira des utilisateurs internes ET externes via un portail dédié' : 'The app will serve internal AND external users via a dedicated portal'}"
|
|
264
|
+
- label: "{language == 'fr' ? 'Non, interne uniquement' : 'No, internal only'}"
|
|
265
|
+
description: "{language == 'fr' ? 'Seuls les employés internes utiliseront l\'application' : 'Only internal employees will use the application'}"
|
|
266
|
+
- label: "{language == 'fr' ? 'Non, portail uniquement' : 'No, portal only'}"
|
|
267
|
+
description: "{language == 'fr' ? 'L\'application est exclusivement destinée aux utilisateurs externes' : 'The application is exclusively for external users'}"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
| Réponse | Action |
|
|
271
|
+
|---------|--------|
|
|
272
|
+
| **Oui, portail + interne** | `_portalMode = "dual"` → Follow-up : classifier chaque stakeholder comme `audience: "internal" \| "external" \| "shared"` |
|
|
273
|
+
| **Portail uniquement** | `_portalMode = "portal-only"` |
|
|
274
|
+
| **Interne uniquement** | `_portalMode = "internal-only"` (défaut, pas d'action supplémentaire) |
|
|
275
|
+
|
|
276
|
+
> **Note SmartStack :** Le déploiement multi-tenant est géré automatiquement par SmartStack. La partie interne et la partie portail seront déployées sur des tenants séparés.
|
|
277
|
+
|
|
278
|
+
SI aucun profil ne match EXTERNAL_SIGNALS → `_portalMode = "internal-only"` (défaut silencieux, rien à demander).
|
|
279
|
+
|
|
242
280
|
#### 4c. Functional Scope (ALWAYS — from `questionnaire/02-stakeholders-scope.md`)
|
|
243
281
|
|
|
244
282
|
**Mandatory minimum:** Q2.13 (in-scope), Q2.15 (exclusions), Q2.16 (main journey).
|
|
@@ -596,6 +634,14 @@ BEFORE transitioning to step-02:
|
|
|
596
634
|
- List anticipated resources (Level 5) for each section
|
|
597
635
|
- List detail page tabs — for entities accessible via click from `list`
|
|
598
636
|
|
|
637
|
+
**SI `_portalMode === "dual"` :**
|
|
638
|
+
- Chaque entrée `coverageMatrix` reçoit `audience: "internal" | "external" | "shared"`
|
|
639
|
+
- Règles de dérivation :
|
|
640
|
+
- Items mentionnés uniquement par stakeholders internes → `"internal"`
|
|
641
|
+
- Items mentionnés uniquement par stakeholders externes → `"external"`
|
|
642
|
+
- Items mentionnés par les deux ou cross-cutting → `"shared"`
|
|
643
|
+
- Afficher colonne "Audience" dans le tableau de la matrice
|
|
644
|
+
|
|
599
645
|
4. **RECONCILIATION CHECK (MANDATORY — BLOCKING):**
|
|
600
646
|
|
|
601
647
|
> **Every explicitly requested item MUST appear in the coverage matrix.**
|
|
@@ -641,12 +687,13 @@ ba-writer.enrichSection({
|
|
|
641
687
|
subsection: "cadrage.json",
|
|
642
688
|
data: {
|
|
643
689
|
metadata: {
|
|
644
|
-
tablePrefix: "{from Phase 5, section 6b — validated prefix, e.g., rh_}"
|
|
690
|
+
tablePrefix: "{from Phase 5, section 6b — validated prefix, e.g., rh_}",
|
|
691
|
+
portalMode: "{_portalMode || 'internal-only'}"
|
|
645
692
|
},
|
|
646
693
|
problem: {from Phase 3, section 4a — Q1.1 answer or refined problem},
|
|
647
694
|
asIs: {from Phase 3, section 4a — Q1.4 answer},
|
|
648
695
|
toBe: {from Phase 3, section 4a — Q1.8 answer},
|
|
649
|
-
stakeholders: [{from Phase 3, section 4b}],
|
|
696
|
+
stakeholders: [{from Phase 3, section 4b — each with audience: "internal"|"external"|"shared" if portalMode is "dual"}],
|
|
650
697
|
globalScope: {
|
|
651
698
|
inScope: [{from Phase 3, section 4c + Phase 4 accepted suggestions + coverage matrix}],
|
|
652
699
|
outOfScope: [{from Phase 3, section 4c — Q2.15 exclusions}]
|
|
@@ -70,6 +70,9 @@ For each module:
|
|
|
70
70
|
- Description
|
|
71
71
|
- FeatureType (data-centric | workflow | reporting | integration | full-module)
|
|
72
72
|
- Entities (preliminary list)
|
|
73
|
+
- Audience ("internal" | "external" | "shared") — if portalMode = "dual"
|
|
74
|
+
→ Derived from coverageMatrix: if ALL items of the module are internal → "internal",
|
|
75
|
+
if ALL external → "external", else → "shared"
|
|
73
76
|
```
|
|
74
77
|
|
|
75
78
|
### 3. Section & Resource Anticipation
|
|
@@ -89,18 +92,20 @@ For each section:
|
|
|
89
92
|
|
|
90
93
|
**Section classification rules:**
|
|
91
94
|
|
|
92
|
-
| sectionType | permissionMode | When to use | Examples |
|
|
93
|
-
|
|
94
|
-
| `primary` | `crud` | Main entry point of the module, visible in menu | list |
|
|
95
|
-
| `functional` | `crud` or `custom` | Independent functional section with own access control | approve, import, planning |
|
|
96
|
-
| `view` | `inherit` |
|
|
97
|
-
| `embedded` | `read-only` | Widget or tab embedded in another section | dashboard (when embedded in module) |
|
|
95
|
+
| sectionType | permissionMode | When to use | Parent | Examples |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| `primary` | `crud` | Main entry point of the module, visible in menu | none | list |
|
|
98
|
+
| `functional` | `crud` or `custom` | Independent functional section with own access control | none | approve, import, planning |
|
|
99
|
+
| `view` | `inherit` | Sub-page reached from a primary/functional section (e.g., click a row) | `parentSectionCode` | detail, edit |
|
|
100
|
+
| `embedded` | `read-only` | Widget or tab embedded in another section | `parentSectionCode` | dashboard (when embedded in module) |
|
|
101
|
+
|
|
102
|
+
**Parent-child rule:** `view` and `embedded` sections MUST specify `parentSectionCode` — the code of the section they are reached from. A detail page is NOT a sibling of list; it is a **child** of list (user clicks a row in the list to reach the detail).
|
|
98
103
|
|
|
99
104
|
Common patterns:
|
|
100
|
-
- **Data-centric
|
|
101
|
-
- **Workflow
|
|
102
|
-
- **Reporting
|
|
103
|
-
- **Full module**: list (`primary`/`crud`)
|
|
105
|
+
- **Data-centric**: list (`primary`/`crud`) → detail (`view`/`inherit`, parent: list)
|
|
106
|
+
- **Workflow**: list (`primary`/`crud`) → detail (`view`/`inherit`, parent: list) + approve (`functional`/`custom`)
|
|
107
|
+
- **Reporting**: dashboard (`primary`/`read-only`) → detail (`view`/`inherit`, parent: dashboard)
|
|
108
|
+
- **Full module**: list (`primary`/`crud`) → detail (`view`/`inherit`, parent: list) + dashboard (`embedded`/`read-only`, parent: list)
|
|
104
109
|
|
|
105
110
|
### 4. Dependency Graph
|
|
106
111
|
|
|
@@ -139,21 +144,40 @@ For EACH identified element, ask yourself:
|
|
|
139
144
|
- Does the form need tabs?
|
|
140
145
|
- Are there missing filter components?
|
|
141
146
|
|
|
147
|
+
**Split audience (if portalMode = "dual"):**
|
|
148
|
+
- Pour chaque module "shared" : le portail voit-il les MÊMES données ou un sous-ensemble filtré ?
|
|
149
|
+
- Y a-t-il des sections qui ne doivent apparaître QUE dans le portail ? (ex: "Mes demandes")
|
|
150
|
+
- Y a-t-il des sections qui ne doivent apparaître QUE en interne ? (ex: reporting admin)
|
|
151
|
+
|
|
142
152
|
### 6. Present to User
|
|
143
153
|
|
|
144
154
|
Display the complete hierarchy for validation:
|
|
145
155
|
|
|
156
|
+
**Tree nesting rules:**
|
|
157
|
+
- `view` sections are **indented under their parent** (the section they are reached from)
|
|
158
|
+
- `functional` and `primary` sections stay at module level
|
|
159
|
+
- `embedded` sections are indented under their parent section
|
|
160
|
+
- Tabs are indented under detail
|
|
161
|
+
|
|
146
162
|
```
|
|
147
163
|
[App: HumanResources]
|
|
148
164
|
├── [Module: Employees]
|
|
149
|
-
│ ├── list →
|
|
150
|
-
│ └── detail →
|
|
165
|
+
│ ├── list (primary/crud) → employees-grid, employees-filters
|
|
166
|
+
│ │ └── detail (view/inherit) → employee-form
|
|
167
|
+
│ │ └── Tabs: Infos, Contrats
|
|
151
168
|
├── [Module: Absences]
|
|
152
|
-
│ ├── list →
|
|
153
|
-
│
|
|
154
|
-
│ └── calendar →
|
|
169
|
+
│ ├── list (primary/crud) → absences-grid, absences-filters
|
|
170
|
+
│ │ └── detail (view/inherit) → absence-form
|
|
171
|
+
│ └── calendar (functional) → absences-calendar
|
|
155
172
|
└── [Module: Reports]
|
|
156
|
-
└── dashboard →
|
|
173
|
+
└── dashboard (primary/read-only) → hr-dashboard
|
|
174
|
+
|
|
175
|
+
IF portalMode = "dual", annotate each module with its audience:
|
|
176
|
+
|
|
177
|
+
[App: ServiceManagement]
|
|
178
|
+
├── [Module: Projects] [partagé]
|
|
179
|
+
├── [Module: TimeTracking] [interne]
|
|
180
|
+
└── [Module: ClientDashboard] [portail]
|
|
157
181
|
```
|
|
158
182
|
|
|
159
183
|
Ask: "Does this structure match your vision? Any missing modules, sections, or resources?"
|
|
@@ -181,7 +205,7 @@ Write via ba-writer:
|
|
|
181
205
|
"entities": ["Employee", "Contract"],
|
|
182
206
|
"anticipatedSections": [
|
|
183
207
|
{ "code": "list", "label": "Liste", "sectionType": "primary", "permissionMode": "crud", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
|
|
184
|
-
{ "code": "detail", "label": "Fiche", "sectionType": "view", "permissionMode": "inherit", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
|
|
208
|
+
{ "code": "detail", "label": "Fiche", "sectionType": "view", "permissionMode": "inherit", "parentSectionCode": "list", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
|
|
185
209
|
]
|
|
186
210
|
}
|
|
187
211
|
],
|
|
@@ -330,6 +330,175 @@ Les types possibles : `functional`, `business-rule`, `performance`, `security`.
|
|
|
330
330
|
|
|
331
331
|
Les critères d'acceptation sont écrits dans `validation.json` au même niveau que `globalRiskAssessment`.
|
|
332
332
|
|
|
333
|
+
### 7ter. Naming Audit (MANDATORY)
|
|
334
|
+
|
|
335
|
+
> **Challenge all names before final approval.** Every code, label, and route generated
|
|
336
|
+
> during the BA must be reviewed for coherence, convention compliance, and clarity.
|
|
337
|
+
|
|
338
|
+
**7ter-a. Collect all names from the BA:**
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
const namingRegistry = {
|
|
342
|
+
application: {
|
|
343
|
+
label: application_name,
|
|
344
|
+
code: applicationCode, // PascalCase
|
|
345
|
+
route: toKebabCase(applicationCode) // kebab-case
|
|
346
|
+
},
|
|
347
|
+
modules: completedModules.map(m => ({
|
|
348
|
+
label: m.name,
|
|
349
|
+
code: m.code, // PascalCase
|
|
350
|
+
route: toKebabCase(m.code) // kebab-case
|
|
351
|
+
})),
|
|
352
|
+
entities: completedModules.flatMap(m =>
|
|
353
|
+
m.entities.map(e => ({
|
|
354
|
+
name: e.name, // PascalCase (C# class name)
|
|
355
|
+
module: m.code,
|
|
356
|
+
tableName: e.tableName || pluralize(e.name) // Plural convention
|
|
357
|
+
}))
|
|
358
|
+
),
|
|
359
|
+
permissionRoots: [...new Set(
|
|
360
|
+
permissionPaths.map(p => p.path.split('.').slice(0, 2).join('.'))
|
|
361
|
+
)]
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**7ter-b. Display naming recap table:**
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
═══════════════════════════════════════════════════════════════
|
|
369
|
+
NAMING AUDIT — {application_name}
|
|
370
|
+
═══════════════════════════════════════════════════════════════
|
|
371
|
+
|
|
372
|
+
APPLICATION
|
|
373
|
+
| Label | Code (PascalCase) | Route (kebab-case) |
|
|
374
|
+
|-------|-------------------|--------------------|
|
|
375
|
+
| {application_name} | {applicationCode} | /{route} |
|
|
376
|
+
|
|
377
|
+
MODULES
|
|
378
|
+
| # | Label | Code (PascalCase) | Route (kebab-case) |
|
|
379
|
+
|---|-------|-------------------|--------------------|
|
|
380
|
+
{for each module: index | label | code | /app-route/module-route}
|
|
381
|
+
|
|
382
|
+
ENTITIES
|
|
383
|
+
| Entity (PascalCase) | Module | Table (plural) |
|
|
384
|
+
|----------------------|--------|----------------|
|
|
385
|
+
{for each entity: name | module | tableName}
|
|
386
|
+
|
|
387
|
+
PERMISSION ROOTS
|
|
388
|
+
{for each root: path}
|
|
389
|
+
═══════════════════════════════════════════════════════════════
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**7ter-c. Coherence checks:**
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
const namingIssues = [];
|
|
396
|
+
|
|
397
|
+
// 1. Duplicate entity names across modules
|
|
398
|
+
const entityNames = namingRegistry.entities.map(e => e.name);
|
|
399
|
+
const duplicates = entityNames.filter((n, i) => entityNames.indexOf(n) !== i);
|
|
400
|
+
if (duplicates.length > 0) {
|
|
401
|
+
namingIssues.push({ severity: "ERROR", issue: `Duplicate entity names: ${[...new Set(duplicates)].join(', ')}` });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 2. Module code vs label coherence (code should derive logically from label)
|
|
405
|
+
for (const mod of namingRegistry.modules) {
|
|
406
|
+
if (!mod.code || mod.code.length < 2) {
|
|
407
|
+
namingIssues.push({ severity: "ERROR", issue: `Module "${mod.label}" has invalid code: "${mod.code}"` });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 3. Permission root alignment with module codes
|
|
412
|
+
for (const root of namingRegistry.permissionRoots) {
|
|
413
|
+
const [appSegment, moduleSegment] = root.split('.');
|
|
414
|
+
const matchingModule = namingRegistry.modules.find(m =>
|
|
415
|
+
m.code.toLowerCase() === moduleSegment.toLowerCase()
|
|
416
|
+
);
|
|
417
|
+
if (!matchingModule) {
|
|
418
|
+
namingIssues.push({ severity: "WARNING", issue: `Permission root "${root}" has no matching module code` });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 4. Route collision detection
|
|
423
|
+
const allRoutes = namingRegistry.modules.map(m =>
|
|
424
|
+
`/${namingRegistry.application.route}/${m.route}`
|
|
425
|
+
);
|
|
426
|
+
const routeDuplicates = allRoutes.filter((r, i) => allRoutes.indexOf(r) !== i);
|
|
427
|
+
if (routeDuplicates.length > 0) {
|
|
428
|
+
namingIssues.push({ severity: "ERROR", issue: `Route collisions: ${routeDuplicates.join(', ')}` });
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**7ter-d. MCP validation:**
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
mcp__smartstack__validate_conventions({
|
|
436
|
+
checks: ["tables"],
|
|
437
|
+
context: {
|
|
438
|
+
applicationCode: applicationCode,
|
|
439
|
+
modules: namingRegistry.modules.map(m => m.code),
|
|
440
|
+
entities: namingRegistry.entities.map(e => e.name)
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
→ Merge MCP findings into namingIssues[]
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**7ter-e. Present findings and confirm:**
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
IF namingIssues.length > 0:
|
|
451
|
+
Display issues table:
|
|
452
|
+
| # | Severity | Issue |
|
|
453
|
+
|---|----------|-------|
|
|
454
|
+
{for each issue}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Ask via AskUserQuestion:
|
|
458
|
+
```
|
|
459
|
+
question: "{language == 'fr'
|
|
460
|
+
? 'Validez-vous les noms ci-dessus pour l\\'ensemble de l\\'application ?'
|
|
461
|
+
: 'Do you approve all the names above for the entire application?'}"
|
|
462
|
+
header: "Naming Audit"
|
|
463
|
+
options:
|
|
464
|
+
- label: "{language == 'fr' ? 'Approuvé' : 'Approved'}"
|
|
465
|
+
description: "{language == 'fr' ? 'Tous les noms sont corrects' : 'All names are correct'}"
|
|
466
|
+
- label: "{language == 'fr' ? 'Renommer certains éléments' : 'Rename some elements'}"
|
|
467
|
+
description: "{language == 'fr' ? 'Corriger des noms avant de finaliser' : 'Fix names before finalizing'}"
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**IF "Renommer certains éléments":**
|
|
471
|
+
|
|
472
|
+
Ask via AskUserQuestion (open-ended):
|
|
473
|
+
```
|
|
474
|
+
question: "{language == 'fr'
|
|
475
|
+
? 'Quels éléments souhaitez-vous renommer ? (ex: \"Module Ventes → Commerce\", \"Entity Invoice → BillingDocument\")'
|
|
476
|
+
: 'Which elements do you want to rename? (e.g., \"Module Sales → Commerce\", \"Entity Invoice → BillingDocument\")'}"
|
|
477
|
+
header: "Renaming"
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Process user response:
|
|
481
|
+
1. Parse rename instructions
|
|
482
|
+
2. Update `applicationCode`, module codes, entity names, permission paths accordingly in JSON files via ba-writer
|
|
483
|
+
3. Re-run 7ter-c and 7ter-d checks on updated names
|
|
484
|
+
4. Re-display the naming recap table for final confirmation
|
|
485
|
+
|
|
486
|
+
**IF "Approuvé":**
|
|
487
|
+
→ Store naming audit result in `validation.json`:
|
|
488
|
+
```javascript
|
|
489
|
+
ba-writer.enrichSection({
|
|
490
|
+
featureId: {feature_id},
|
|
491
|
+
section: "namingAudit",
|
|
492
|
+
data: {
|
|
493
|
+
auditedAt: now(),
|
|
494
|
+
issues: namingIssues,
|
|
495
|
+
approved: true,
|
|
496
|
+
renames: [] // or list of applied renames
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
→ Continue to section 8
|
|
501
|
+
|
|
333
502
|
### 8. Consolidation Summary Display
|
|
334
503
|
|
|
335
504
|
```
|
|
@@ -449,6 +618,7 @@ ba-writer.enrichSection({
|
|
|
449
618
|
`E2E flows: ${e2eFlows.length} identified`,
|
|
450
619
|
`Global risk: ${risks.length > 0 ? 'MEDIUM' : 'LOW'}`,
|
|
451
620
|
`Semantic checks: PASSED`,
|
|
621
|
+
`Naming audit: ${namingIssues.length} issues found, APPROVED`,
|
|
452
622
|
`Client approval: APPROVED`
|
|
453
623
|
]
|
|
454
624
|
}
|
|
@@ -508,6 +678,7 @@ BA workflow complete. Next steps:
|
|
|
508
678
|
- ✓ No circular dependencies
|
|
509
679
|
- ✓ Permission coherence validated
|
|
510
680
|
- ✓ Semantic checks: 0 errors
|
|
681
|
+
- ✓ Naming audit completed and approved
|
|
511
682
|
- ✓ Client approval obtained (or auto-approved for single module)
|
|
512
683
|
- ✓ Consolidation section written to validation.json
|
|
513
684
|
- ✓ Status updated to "consolidated"
|
|
@@ -2394,6 +2394,13 @@ let data = {{FEATURE_DATA}};
|
|
|
2394
2394
|
const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
|
|
2395
2395
|
const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
|
|
2396
2396
|
|
|
2397
|
+
// CRITICAL: Initialize top-level structures FIRST to prevent TypeError crashes
|
|
2398
|
+
data.cadrage = data.cadrage || {};
|
|
2399
|
+
data.modules = data.modules || [];
|
|
2400
|
+
data.dependencies = data.dependencies || [];
|
|
2401
|
+
data.handoff = data.handoff || {};
|
|
2402
|
+
data.cadrage.stakeholders = data.cadrage.stakeholders || [];
|
|
2403
|
+
|
|
2397
2404
|
// Initialize applications array (multi-app support)
|
|
2398
2405
|
data.applications = data.applications || [];
|
|
2399
2406
|
|
|
@@ -2454,8 +2461,10 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
2454
2461
|
if (!data.moduleSpecs[m.code]) {
|
|
2455
2462
|
data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
2456
2463
|
}
|
|
2457
|
-
// Ensure anticipatedSections array exists
|
|
2458
|
-
m.anticipatedSections = m.anticipatedSections || []
|
|
2464
|
+
// Ensure anticipatedSections array exists and normalize strings to objects
|
|
2465
|
+
m.anticipatedSections = (m.anticipatedSections || []).map(function(s) {
|
|
2466
|
+
return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
|
|
2467
|
+
});
|
|
2459
2468
|
m.applicationCode = m.applicationCode || '';
|
|
2460
2469
|
// Initialize screens array for interface specs
|
|
2461
2470
|
if (!data.moduleSpecs[m.code].screens) {
|
|
@@ -2767,7 +2776,9 @@ function renderModuleNavItem(mod) {
|
|
|
2767
2776
|
var ucCount = (spec.useCases || []).length;
|
|
2768
2777
|
var brCount = (spec.businessRules || []).length;
|
|
2769
2778
|
var entCount = (spec.entities || []).length;
|
|
2770
|
-
var sections = mod.anticipatedSections || []
|
|
2779
|
+
var sections = (mod.anticipatedSections || []).map(function(s) {
|
|
2780
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
2781
|
+
});
|
|
2771
2782
|
var groupId = 'mod-' + code;
|
|
2772
2783
|
var collapsed = navCollapseState[groupId] === true;
|
|
2773
2784
|
|
|
@@ -3957,7 +3968,9 @@ function refreshAllPermGrids() {
|
|
|
3957
3968
|
|
|
3958
3969
|
function renderModuleStructure(code) {
|
|
3959
3970
|
const mod = data.modules.find(m => m.code === code);
|
|
3960
|
-
const sections = mod ? (mod.anticipatedSections || [])
|
|
3971
|
+
const sections = mod ? (mod.anticipatedSections || []).map(function(s) {
|
|
3972
|
+
return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
|
|
3973
|
+
}) : [];
|
|
3961
3974
|
|
|
3962
3975
|
if (sections.length === 0) {
|
|
3963
3976
|
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
|
|
@@ -4036,7 +4049,9 @@ function renderModuleStructure(code) {
|
|
|
4036
4049
|
/* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
|
|
4037
4050
|
|
|
4038
4051
|
function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
|
|
4039
|
-
var sections = mod.anticipatedSections || []
|
|
4052
|
+
var sections = (mod.anticipatedSections || []).map(function(s) {
|
|
4053
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
4054
|
+
});
|
|
4040
4055
|
var html = '';
|
|
4041
4056
|
sections.forEach(function(section) {
|
|
4042
4057
|
var items = section[itemsKey] || [];
|
|
@@ -4059,7 +4074,9 @@ function renderModuleMockups(code) {
|
|
|
4059
4074
|
var screens = spec.screens || [];
|
|
4060
4075
|
var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
4061
4076
|
var mod = data.modules.find(function(m) { return m.code === code; });
|
|
4062
|
-
var sections = mod ? (mod.anticipatedSections || [])
|
|
4077
|
+
var sections = mod ? (mod.anticipatedSections || []).map(function(s) {
|
|
4078
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
4079
|
+
}) : [];
|
|
4063
4080
|
|
|
4064
4081
|
// Priority 1: HTML mockups from screens[] specs
|
|
4065
4082
|
if (screens.length > 0) {
|
|
@@ -6,6 +6,13 @@ let data = {{FEATURE_DATA}};
|
|
|
6
6
|
const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
|
|
7
7
|
const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
|
|
8
8
|
|
|
9
|
+
// CRITICAL: Initialize top-level structures FIRST to prevent TypeError crashes
|
|
10
|
+
data.cadrage = data.cadrage || {};
|
|
11
|
+
data.modules = data.modules || [];
|
|
12
|
+
data.dependencies = data.dependencies || [];
|
|
13
|
+
data.handoff = data.handoff || {};
|
|
14
|
+
data.cadrage.stakeholders = data.cadrage.stakeholders || [];
|
|
15
|
+
|
|
9
16
|
// Initialize applications array (multi-app support)
|
|
10
17
|
data.applications = data.applications || [];
|
|
11
18
|
|
|
@@ -66,8 +73,10 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
66
73
|
if (!data.moduleSpecs[m.code]) {
|
|
67
74
|
data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
68
75
|
}
|
|
69
|
-
// Ensure anticipatedSections array exists
|
|
70
|
-
m.anticipatedSections = m.anticipatedSections || []
|
|
76
|
+
// Ensure anticipatedSections array exists and normalize strings to objects
|
|
77
|
+
m.anticipatedSections = (m.anticipatedSections || []).map(function(s) {
|
|
78
|
+
return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
|
|
79
|
+
});
|
|
71
80
|
m.applicationCode = m.applicationCode || '';
|
|
72
81
|
// Initialize screens array for interface specs
|
|
73
82
|
if (!data.moduleSpecs[m.code].screens) {
|
|
@@ -149,7 +149,9 @@ function renderModuleNavItem(mod) {
|
|
|
149
149
|
var ucCount = (spec.useCases || []).length;
|
|
150
150
|
var brCount = (spec.businessRules || []).length;
|
|
151
151
|
var entCount = (spec.entities || []).length;
|
|
152
|
-
var sections = mod.anticipatedSections || []
|
|
152
|
+
var sections = (mod.anticipatedSections || []).map(function(s) {
|
|
153
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
154
|
+
});
|
|
153
155
|
var groupId = 'mod-' + code;
|
|
154
156
|
var collapsed = navCollapseState[groupId] === true;
|
|
155
157
|
|
|
@@ -611,7 +611,9 @@ function refreshAllPermGrids() {
|
|
|
611
611
|
|
|
612
612
|
function renderModuleStructure(code) {
|
|
613
613
|
const mod = data.modules.find(m => m.code === code);
|
|
614
|
-
const sections = mod ? (mod.anticipatedSections || [])
|
|
614
|
+
const sections = mod ? (mod.anticipatedSections || []).map(function(s) {
|
|
615
|
+
return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
|
|
616
|
+
}) : [];
|
|
615
617
|
|
|
616
618
|
if (sections.length === 0) {
|
|
617
619
|
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
|
|
@@ -690,7 +692,9 @@ function renderModuleStructure(code) {
|
|
|
690
692
|
/* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
|
|
691
693
|
|
|
692
694
|
function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
|
|
693
|
-
var sections = mod.anticipatedSections || []
|
|
695
|
+
var sections = (mod.anticipatedSections || []).map(function(s) {
|
|
696
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
697
|
+
});
|
|
694
698
|
var html = '';
|
|
695
699
|
sections.forEach(function(section) {
|
|
696
700
|
var items = section[itemsKey] || [];
|
|
@@ -713,7 +717,9 @@ function renderModuleMockups(code) {
|
|
|
713
717
|
var screens = spec.screens || [];
|
|
714
718
|
var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
|
|
715
719
|
var mod = data.modules.find(function(m) { return m.code === code; });
|
|
716
|
-
var sections = mod ? (mod.anticipatedSections || [])
|
|
720
|
+
var sections = mod ? (mod.anticipatedSections || []).map(function(s) {
|
|
721
|
+
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
722
|
+
}) : [];
|
|
717
723
|
|
|
718
724
|
// Priority 1: HTML mockups from screens[] specs
|
|
719
725
|
if (screens.length > 0) {
|
|
@@ -51,7 +51,10 @@ const FEATURE_DATA = {
|
|
|
51
51
|
featureType: module.featureType || "data-centric",
|
|
52
52
|
estimatedComplexity: module.estimatedComplexity || "medium",
|
|
53
53
|
entities: module.entities || [],
|
|
54
|
-
|
|
54
|
+
// NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
|
|
55
|
+
anticipatedSections: (module.anticipatedSections || []).map(s =>
|
|
56
|
+
typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
|
|
57
|
+
), // ALWAYS [{code, name, description, resources[]}]
|
|
55
58
|
dependencies: module.dependencies || [],
|
|
56
59
|
dependents: module.dependents || []
|
|
57
60
|
}
|
|
@@ -57,7 +57,10 @@ Build a JSON object following this **exact mapping** from index.json to the HTML
|
|
|
57
57
|
description: m.description || "",
|
|
58
58
|
featureType: m.featureType || "data-centric",
|
|
59
59
|
entities: m.entities || [],
|
|
60
|
-
|
|
60
|
+
// NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
|
|
61
|
+
anticipatedSections: (m.anticipatedSections || []).map(s =>
|
|
62
|
+
typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
|
|
63
|
+
), // ALWAYS [{code, name, description, resources[]}]
|
|
61
64
|
dependencies: m.dependencies || [],
|
|
62
65
|
dependents: m.dependents || [],
|
|
63
66
|
estimatedComplexity: m.estimatedComplexity || "medium",
|
|
@@ -89,7 +89,11 @@ modules: master.modules.map(m => ({
|
|
|
89
89
|
description: m.description || "",
|
|
90
90
|
featureType: m.featureType || "data-centric",
|
|
91
91
|
entities: m.entities || [],
|
|
92
|
-
anticipatedSections: m.anticipatedSections || []
|
|
92
|
+
anticipatedSections: (m.anticipatedSections || []).map(s =>
|
|
93
|
+
typeof s === 'string'
|
|
94
|
+
? { code: s, name: s, description: "", resources: [] }
|
|
95
|
+
: { code: s.code || s.name || "", name: s.label || s.name || s.code || "", description: s.description || "", resources: s.resources || [], route: s.route || "", permission: s.permission || "" }
|
|
96
|
+
),
|
|
93
97
|
dependencies: m.dependencies || [],
|
|
94
98
|
dependents: m.dependents || [],
|
|
95
99
|
estimatedComplexity: m.estimatedComplexity || "medium",
|
|
@@ -135,6 +139,50 @@ moduleSpecs[moduleCode] = {
|
|
|
135
139
|
permissions: buildPermissionKeys(mod.permissions),
|
|
136
140
|
apiEndpoints: mod.usecases?.apiEndpoints || []
|
|
137
141
|
}
|
|
142
|
+
|
|
143
|
+
// BUILD screens[] for HTML interactive mockups (SmartTable/SmartForm rendering)
|
|
144
|
+
const flatScr = mod.screens?.screens || [];
|
|
145
|
+
let screens = [];
|
|
146
|
+
if (flatScr.length > 0) {
|
|
147
|
+
const bySec = {};
|
|
148
|
+
flatScr.forEach(s => {
|
|
149
|
+
const sec = s.section || "default";
|
|
150
|
+
if (!bySec[sec]) bySec[sec] = {
|
|
151
|
+
sectionCode: sec,
|
|
152
|
+
sectionLabel: s.sectionLabel || s.sectionDescription || sec,
|
|
153
|
+
resources: []
|
|
154
|
+
};
|
|
155
|
+
bySec[sec].resources.push({
|
|
156
|
+
code: s.screen || s.name || "",
|
|
157
|
+
label: s.sectionLabel || s.description || s.screen || "",
|
|
158
|
+
type: s.componentType || "unknown",
|
|
159
|
+
columns: (s.columns || []).map(c => ({
|
|
160
|
+
field: c.code || c.field || "", label: c.label || c.code || "",
|
|
161
|
+
type: c.type || c.dataType || "text", sortable: !!c.sortable
|
|
162
|
+
})),
|
|
163
|
+
filters: (s.filters || []).map(f =>
|
|
164
|
+
typeof f === 'string' ? f : (f.label || f.filterLabel || f.code || f.filterCode || "")),
|
|
165
|
+
fields: [],
|
|
166
|
+
tabs: (s.tabs || []).map(t => ({
|
|
167
|
+
label: t.label || t.tabLabel || t.code || "",
|
|
168
|
+
fields: (t.fields || []).map(f => ({
|
|
169
|
+
field: f.code || f.fieldCode || f.field || "",
|
|
170
|
+
label: f.label || f.code || "",
|
|
171
|
+
type: f.type || f.dataType || "text",
|
|
172
|
+
required: !!f.required
|
|
173
|
+
}))
|
|
174
|
+
})),
|
|
175
|
+
actions: (s.actions || []).map(a =>
|
|
176
|
+
typeof a === 'string' ? a : (a.code || a.label || a.actionCode || "")),
|
|
177
|
+
kpis: s.kpis || [],
|
|
178
|
+
options: s.options || [],
|
|
179
|
+
permission: s.permission || "",
|
|
180
|
+
notes: s.description || ""
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
screens = Object.values(bySec);
|
|
184
|
+
}
|
|
185
|
+
moduleSpecs[moduleCode].screens = screens;
|
|
138
186
|
```
|
|
139
187
|
|
|
140
188
|
> See `references/data-mapping.md` for `buildPermissionKeys()` implementation.
|
|
@@ -156,6 +204,57 @@ FEATURE_DATA.modules.forEach(m => {
|
|
|
156
204
|
|
|
157
205
|
> This step is CRITICAL for the HTML viewer to display UC/BR grouped by section.
|
|
158
206
|
|
|
207
|
+
### Post-Build Self-Check (MANDATORY — BLOCKING)
|
|
208
|
+
|
|
209
|
+
After enriching anticipatedSections, run this self-check to detect data loss:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
// SELF-CHECK: compare source file counts vs FEATURE_DATA counts
|
|
213
|
+
const errors = [];
|
|
214
|
+
FEATURE_DATA.modules.forEach(m => {
|
|
215
|
+
const spec = FEATURE_DATA.moduleSpecs[m.code];
|
|
216
|
+
const source = collected_data.modules[m.code];
|
|
217
|
+
if (!spec) { errors.push(`MISSING moduleSpecs[${m.code}]`); return; }
|
|
218
|
+
|
|
219
|
+
const srcUC = (source?.usecases?.useCases || []).length;
|
|
220
|
+
const bltUC = (spec.useCases || []).length;
|
|
221
|
+
if (srcUC > 0 && bltUC === 0)
|
|
222
|
+
errors.push(`${m.code}: useCases EMPTY but source has ${srcUC}`);
|
|
223
|
+
|
|
224
|
+
const srcBR = (source?.rules?.rules || []).length;
|
|
225
|
+
const bltBR = (spec.businessRules || []).length;
|
|
226
|
+
if (srcBR > 0 && bltBR === 0)
|
|
227
|
+
errors.push(`${m.code}: businessRules EMPTY but source has ${srcBR}`);
|
|
228
|
+
|
|
229
|
+
const srcEnt = (source?.entities?.entities || []).length;
|
|
230
|
+
const bltEnt = (spec.entities || []).length;
|
|
231
|
+
if (srcEnt > 0 && bltEnt === 0)
|
|
232
|
+
errors.push(`${m.code}: entities EMPTY but source has ${srcEnt}`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (errors.length > 0) {
|
|
236
|
+
Display("⛔ SELF-CHECK FAILED — data loss detected:");
|
|
237
|
+
errors.forEach(e => Display(" - " + e));
|
|
238
|
+
// FIX: re-map the failing modules from collected_data before continuing
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
> If self-check detects errors, you MUST re-read the source data and re-build the failing moduleSpecs entries.
|
|
243
|
+
> Do NOT proceed with empty arrays when source data exists.
|
|
244
|
+
|
|
245
|
+
**dependencies[] (TOP-LEVEL — CRITICAL for HTML navigation):**
|
|
246
|
+
```javascript
|
|
247
|
+
dependencies: (master.consolidation?.crossModuleInteractions || master.dependencyGraph?.edges || []).map(i => ({
|
|
248
|
+
from: i.fromModule || i.from || "",
|
|
249
|
+
to: i.toModule || i.to || "",
|
|
250
|
+
description: i.description || ""
|
|
251
|
+
}))
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
> **WARNING:** `data.dependencies` is used by the sidebar (navigation), module rendering, consolidation views,
|
|
255
|
+
> handoff summary, and export. If this array is missing, the entire HTML page crashes with a TypeError.
|
|
256
|
+
> ALWAYS include it, even if empty (`dependencies: []`).
|
|
257
|
+
|
|
159
258
|
**consolidation:**
|
|
160
259
|
```javascript
|
|
161
260
|
consolidation: {
|
|
@@ -223,6 +322,31 @@ for (const section of sections) {
|
|
|
223
322
|
}
|
|
224
323
|
}
|
|
225
324
|
|
|
325
|
+
// Source C: screens without mockup → auto-generate text description as wireframe fallback
|
|
326
|
+
const screensWithoutMockup = flatScreens.filter(s => !s.mockup && !s.mockupFormat);
|
|
327
|
+
for (const screen of screensWithoutMockup) {
|
|
328
|
+
const type = screen.componentType || "";
|
|
329
|
+
let desc = "";
|
|
330
|
+
if (type.includes("Table") || type.includes("Grid")) {
|
|
331
|
+
const cols = screen.columns || [];
|
|
332
|
+
desc = "Tableau avec " + cols.length + " colonnes : " + cols.map(c => c.label || c.code).join(", ");
|
|
333
|
+
} else if (type.includes("Form")) {
|
|
334
|
+
const tabs = screen.tabs || [];
|
|
335
|
+
desc = "Formulaire " + (tabs.length > 0 ? "avec " + tabs.length + " onglet(s)" : "");
|
|
336
|
+
} else if (type.includes("Dashboard")) {
|
|
337
|
+
desc = "Tableau de bord avec KPIs";
|
|
338
|
+
} else if (type.includes("Kanban")) {
|
|
339
|
+
desc = "Vue Kanban";
|
|
340
|
+
}
|
|
341
|
+
if (desc) {
|
|
342
|
+
rawWireframes.push({
|
|
343
|
+
screen: screen.screen || "", section: screen.section || "",
|
|
344
|
+
mockupFormat: "text", mockup: desc,
|
|
345
|
+
description: screen.sectionDescription || "", elements: [], componentMapping: []
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
226
350
|
// STEP 2: Map to HTML format (RENAME: mockupFormat → format, mockup → content)
|
|
227
351
|
wireframes: {
|
|
228
352
|
[moduleCode]: rawWireframes.map(wf => ({
|
|
@@ -287,6 +411,7 @@ dependencyGraph: {
|
|
|
287
411
|
- FEATURE_DATA.moduleSpecs must have ONE entry per module
|
|
288
412
|
- cadrage.scope uses HTML keys (inscope/outofscope)
|
|
289
413
|
- Wireframe fields use format/content (NOT mockupFormat/mockup)
|
|
414
|
+
- Per-module: useCases/businessRules/entities count must match source (empty when source has data = BUG)
|
|
290
415
|
|
|
291
416
|
## NEXT STEP
|
|
292
417
|
|
|
@@ -8,7 +8,7 @@ model: opus
|
|
|
8
8
|
|
|
9
9
|
## YOUR TASK
|
|
10
10
|
|
|
11
|
-
Run
|
|
11
|
+
Run 6 blocking validations on the generated HTML file and display the completion summary.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -53,6 +53,40 @@ FOR each module in FEATURE_DATA.modules:
|
|
|
53
53
|
BLOCKING_ERROR("Module {module.code} not in wireframes")
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
**Check 6 — Per-module data completeness (source vs HTML):**
|
|
57
|
+
```
|
|
58
|
+
FOR each module in FEATURE_DATA.modules:
|
|
59
|
+
Read source: docs/{app}/{module.code}/business-analyse/v{version}/usecases.json
|
|
60
|
+
sourceUCCount = count of useCases in source file
|
|
61
|
+
htmlUCCount = count in FEATURE_DATA.moduleSpecs[module.code].useCases
|
|
62
|
+
IF sourceUCCount > 0 AND htmlUCCount === 0:
|
|
63
|
+
BLOCKING_ERROR("Module {module.code}: 0 useCases in HTML but {sourceUCCount} in source")
|
|
64
|
+
|
|
65
|
+
Read source: docs/{app}/{module.code}/business-analyse/v{version}/rules.json
|
|
66
|
+
sourceBRCount = count of rules in source file
|
|
67
|
+
htmlBRCount = count in FEATURE_DATA.moduleSpecs[module.code].businessRules
|
|
68
|
+
IF sourceBRCount > 0 AND htmlBRCount === 0:
|
|
69
|
+
BLOCKING_ERROR("Module {module.code}: 0 businessRules in HTML but {sourceBRCount} in source")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Check 7 — No runtime crash (CRITICAL — validates page won't be blank):**
|
|
73
|
+
```
|
|
74
|
+
Open ba-interactive.html and search for the FEATURE_DATA JSON block.
|
|
75
|
+
Verify these MANDATORY top-level fields exist:
|
|
76
|
+
- "modules": [...] (array, can be empty)
|
|
77
|
+
- "dependencies": [...] (array, can be empty)
|
|
78
|
+
- "cadrage": {...} (object with stakeholders[], scope{})
|
|
79
|
+
- "moduleSpecs": {...} (object, one key per module)
|
|
80
|
+
- "consolidation": {...} (object)
|
|
81
|
+
|
|
82
|
+
IF "dependencies" is missing from FEATURE_DATA:
|
|
83
|
+
Add: "dependencies": [] to the FEATURE_DATA object in the HTML
|
|
84
|
+
(This is the #1 cause of blank HTML pages — TypeError on data.dependencies.length crashes all JS)
|
|
85
|
+
|
|
86
|
+
IF "modules" is missing:
|
|
87
|
+
BLOCKING_ERROR("FEATURE_DATA.modules missing — page will crash")
|
|
88
|
+
```
|
|
89
|
+
|
|
56
90
|
> **IF any check fails:** fix the issue and re-run the failing step before completing.
|
|
57
91
|
|
|
58
92
|
---
|