@atlashub/smartstack-cli 3.34.0 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.documentation/init.html +409 -0
  2. package/dist/index.js +32 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/mcp-entry.mjs +7 -24
  5. package/dist/mcp-entry.mjs.map +1 -1
  6. package/package.json +1 -2
  7. package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
  8. package/templates/skills/apex/SKILL.md +3 -3
  9. package/templates/skills/apex/references/post-checks.md +225 -0
  10. package/templates/skills/apex/references/smartstack-api.md +29 -1
  11. package/templates/skills/apex/references/smartstack-frontend.md +27 -0
  12. package/templates/skills/apex/references/smartstack-layers.md +18 -2
  13. package/templates/skills/apex/steps/step-00-init.md +73 -0
  14. package/templates/skills/apex/steps/step-01-analyze.md +21 -0
  15. package/templates/skills/apex/steps/step-03-execute.md +72 -5
  16. package/templates/skills/apex/steps/step-04-examine.md +7 -1
  17. package/templates/skills/business-analyse/SKILL.md +4 -3
  18. package/templates/skills/business-analyse/_shared.md +9 -0
  19. package/templates/skills/business-analyse/schemas/application-schema.json +13 -0
  20. package/templates/skills/business-analyse/steps/step-00-init.md +190 -34
  21. package/templates/skills/business-analyse/steps/step-01-cadrage.md +129 -10
  22. package/templates/skills/business-analyse/steps/step-01b-applications.md +184 -13
  23. package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
  24. package/templates/skills/business-analyse/steps/step-03d-validate.md +5 -1
  25. package/templates/skills/ralph-loop/SKILL.md +5 -0
  26. package/templates/skills/ralph-loop/references/category-rules.md +29 -0
  27. package/templates/skills/ralph-loop/references/compact-loop.md +85 -2
  28. package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
  29. package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
  30. package/templates/skills/ralph-loop/steps/step-02-execute.md +45 -1
  31. package/templates/skills/ralph-loop/steps/step-05-report.md +19 -0
  32. package/scripts/health-check.sh +0 -168
  33. package/scripts/postinstall.js +0 -18
@@ -262,7 +262,13 @@ AC2: {criterion} → PASS / FAIL (evidence: {file:line or test})
262
262
  | Security | TenantId filter, RequirePermission, no Guid.Empty, no !.Value | PASS / N/A |
263
263
  | Seed data | completeness, deterministic GUIDs, no ContextId/RoleId constants | PASS / N/A |
264
264
  | Code quality | PaginatedResult, EntityLookup (no FK select/input), CSS vars, search param | PASS / N/A |
265
- | POST-CHECKs | 43 checks from references/post-checks.md | PASS / N/A |
265
+ | Migration completeness | ModelSnapshot covers ALL DbSet entities (POST-CHECK 44, 49) | PASS / N/A |
266
+ | NavRoute kebab-case | NavRoute + permissions use kebab-case (POST-CHECK 48) | PASS / N/A |
267
+ | DateOnly DTOs | Date fields use DateOnly not string (POST-CHECK 47) | PASS / N/A |
268
+ | I18n registration | Namespaces registered in i18n config (POST-CHECK 45) | PASS / N/A |
269
+ | Validators DI | FluentValidation registered in DI (POST-CHECK 46) | PASS / N/A |
270
+ | Route/NavRoute conflict | No [Route] alongside [NavRoute] on controllers (POST-CHECK 50) | PASS / N/A |
271
+ | POST-CHECKs | 50 checks from references/post-checks.md | PASS / N/A |
266
272
  | Acceptance criteria | AC1..ACn | {X}/{Y} PASS |
267
273
  ```
268
274
 
@@ -43,6 +43,7 @@ The skill auto-detects which use case applies by scanning existing features in `
43
43
 
44
44
  Step-00 handles detection automatically:
45
45
  - **New vs Update**: scans `docs/business/` for existing applications
46
+ - **Single vs Multi-app**: multi-app detection in step-01, application identity confirmed in step-01b (always)
46
47
  - **Single vs Multi-module**: determined during step-02 decomposition
47
48
  - **Language**: detected from config or asked once
48
49
  - **Review mode**: if `-review` flag, routes directly to step-06
@@ -151,7 +152,7 @@ When step-00 detects that the description matches an existing application:
151
152
  **New application workflow (single or multi-app):**
152
153
  - **Step 00:** Detection, scan existing, create master feature.json (draft)
153
154
  - **Step 01:** Cadrage: context, stakeholders, scope, application roles (framed)
154
- - **Step 01b:** *(multi-app only)* Application decomposition: identify applications, contexts, prefixes, inter-app dependencies
155
+ - **Step 01b:** Application identity (always): confirm code, route, icon, seed data (lightweight single-app / full decomposition multi-app)
155
156
  - **Step 02:** Decomposition: modules, dependencies, client checkpoint (decomposed) — per application in multi-app mode
156
157
  - **Step 03a:** Per-module data: sections, entities, business rules, questionnaires
157
158
  - **Step 03b:** Per-module UI: state machines, wireframes, layouts, dashboards
@@ -207,12 +208,12 @@ When step-00 detects that the description matches an existing application:
207
208
  | ---- | --------------------------------- | ------ | ---------------------------------------------------- |
208
209
  | 00 | `steps/step-00-init.md` | Sonnet | Auto-detect new/update, scan existing, create master feature.json |
209
210
  | 01 | `steps/step-01-cadrage.md` | Opus | Framing: context, stakeholders, scope, application roles + multi-app detection |
210
- | 01b | `steps/step-01b-applications.md` | Opus | *(multi-app only)* Application decomposition, contexts, prefixes, inter-app dependencies |
211
+ | 01b | `steps/step-01b-applications.md` | Opus | Application identity: confirm code, route, icon, seedData (lightweight single-app, full decomposition multi-app) |
211
212
  | 02 | `steps/step-02-decomposition.md` | Opus | Module decomposition, dependency graph, client checkpoint (per application in multi-app) |
212
213
  | 03a1 | `steps/step-03a1-setup.md` | Opus | Per-module: setup, sections, questionnaires, cross-refs |
213
214
  | 03a2 | `steps/step-03a2-analysis.md` | Opus | Per-module: objectives, entities, BRs, process flow |
214
215
  | 03b | `steps/step-03b-ui.md` | Opus | Per-module: state machines, wireframes, layouts, dashboards |
215
- | 03c | `steps/step-03c-compile.md` | Opus | Per-module: actors, UCs, FRs, permissions, navigation, seedDataCore (7 arrays), i18n |
216
+ | 03c | `steps/step-03c-compile.md` | Opus | Per-module: actors, UCs, FRs, permissions, navigation, seedDataCore (9 arrays), i18n |
216
217
  | 03d | `steps/step-03d-validate.md` | Sonnet | Per-module: validation, write, incremental HTML, loop decision |
217
218
  | 04a | `steps/step-04a-collect.md` | Opus | Collect module summaries & cross-module interactions |
218
219
  | 04b | `steps/step-04b-analyze.md` | Opus | Analyze permission coherence, semantic validation, E2E flows |
@@ -98,6 +98,15 @@ Level 1: Context (business)
98
98
  Level 4: Section (approve) ← sidebar nav (workflow)
99
99
  ```
100
100
 
101
+ **CRITICAL — Application identification (Level 2):**
102
+ The application is formally identified in step-01b (ALWAYS, even for single-app mode):
103
+ - Code: PascalCase (e.g., "HumanResources")
104
+ - Route: kebab-case (e.g., "/business/human-resources")
105
+ - Icon: Lucide icon name
106
+ - Sort order: position in navigation
107
+ This creates the `seedDataCore.navigationApplications` entry that links modules to their parent application.
108
+ The `seedDataCore.applicationRoles` entries are also built from `cadrage.applicationRoles`.
109
+
101
110
  | BA Phase | Levels treated | What is defined |
102
111
  |----------|---------------|-----------------|
103
112
  | Cadrage (step-01) | Context + Application | Problem, stakeholders, scope, application-level roles |
@@ -54,6 +54,19 @@
54
54
  "pattern": "^[a-z]{2,5}_$",
55
55
  "description": "Application table prefix for database tables (e.g., rh_, fi_, crm_). Defined during cadrage, used by all entities in this application."
56
56
  },
57
+ "applicationCode": {
58
+ "type": "string",
59
+ "description": "PascalCase application code for navigation hierarchy (e.g., HumanResources). Confirmed in step-01b."
60
+ },
61
+ "applicationRoute": {
62
+ "type": "string",
63
+ "pattern": "^/[a-z-/]+$",
64
+ "description": "Kebab-case route path for navigation (e.g., /business/human-resources). Confirmed in step-01b."
65
+ },
66
+ "applicationIcon": {
67
+ "type": "string",
68
+ "description": "Lucide icon name for application navigation entry (e.g., users, shopping-cart). Confirmed in step-01b."
69
+ },
57
70
  "workflow": {
58
71
  "type": "object",
59
72
  "description": "Iterative module loop state",
@@ -129,10 +129,116 @@ existing_projects: array of { projectName, projectId, applications[], version }
129
129
 
130
130
  > **Project Resume Detection:** If a project-level feature.json is found at `docs/business-analyse/*/feature.json`, check its `metadata.workflow` to determine resume point. The project may have partially completed applications.
131
131
 
132
+ ## Step 3b: Early Multi-Application Detection from Prompt (NEW)
133
+
134
+ > **Detect multi-app structure BEFORE asking for a single application name.**
135
+ > When the user's prompt explicitly describes multiple applications, we must recognize this immediately
136
+ > and set `workflow.mode = "project"` to avoid doing cadrage for a single app.
137
+
138
+ **Detection patterns (ANY match = multi-app detected):**
139
+
140
+ ```javascript
141
+ const multiAppPatterns = [
142
+ // French patterns
143
+ /une\s+application\s+\w+.*une\s+application\s+\w+/is,
144
+ /application\s*\d?\s*[:]\s*\w+.*application\s*\d?\s*[:]\s*\w+/is,
145
+ /premier[e]?\s+application.*deuxième\s+application/is,
146
+ /app\s*1\s*[:.].*app\s*2\s*[:.]*/is,
147
+ // English patterns
148
+ /an?\s+application\s+(for\s+)?\w+.*an?\s+application\s+(for\s+)?\w+/is,
149
+ /application\s*#?\d\s*[:.].*application\s*#?\d\s*[:.]*/is,
150
+ /first\s+app.*second\s+app/is,
151
+ ];
152
+
153
+ const isMultiApp = multiAppPatterns.some(p => p.test(feature_description));
154
+ ```
155
+
156
+ **IF multi-app detected:**
157
+
158
+ 1. Extract candidate applications from the prompt:
159
+
160
+ ```javascript
161
+ // Parse the prompt to identify application boundaries
162
+ // Each "une application X" / "an application X" block = one candidate
163
+ const candidates = extractApplicationCandidates(feature_description);
164
+ // Result example:
165
+ // [
166
+ // { name: "RH", description: "gestion des employes, conges, temps", modules: ["Employes", "Conges", "Temps"] },
167
+ // { name: "Projet", description: "gestion des projets, saisie du temps", modules: ["Projets", "Temps"] }
168
+ // ]
169
+ ```
170
+
171
+ 2. Detect shared modules across candidates:
172
+
173
+ ```javascript
174
+ const allModules = candidates.flatMap(c => c.modules);
175
+ const sharedModules = allModules.filter((m, i) => allModules.indexOf(m) !== i);
176
+ // Example: sharedModules = ["Temps"] → appears in both RH and Projet
177
+ ```
178
+
179
+ 3. Display detection result and confirm:
180
+
181
+ ```
182
+ {language == "fr"
183
+ ? "### Détection multi-application\n\nJ'ai détecté **{candidates.length} applications** dans votre description :"
184
+ : "### Multi-application detection\n\nI detected **{candidates.length} applications** in your description:"}
185
+
186
+ | # | Application | Modules identifiés |
187
+ |---|-------------|-------------------|
188
+ {for each candidate: index | name | modules.join(", ")}
189
+
190
+ {sharedModules.length > 0
191
+ ? "⚠️ **Modules partagés détectés :** {sharedModules.join(', ')} — ces modules apparaissent dans plusieurs applications. Ils pourraient constituer une application transversale dédiée."
192
+ : ""}
193
+ ```
194
+
195
+ Ask via AskUserQuestion:
196
+ ```
197
+ question: "{language == 'fr' ? 'Confirmez-vous cette structure multi-application ?' : 'Do you confirm this multi-application structure?'}"
198
+ header: "Architecture"
199
+ options:
200
+ - label: "{language == 'fr' ? 'Oui, {candidates.length} applications' : 'Yes, {candidates.length} applications'}"
201
+ description: "{language == 'fr' ? 'Créer un projet avec les applications identifiées' : 'Create a project with the identified applications'}"
202
+ - label: "{language == 'fr' ? 'Extraire les modules partagés' : 'Extract shared modules'}" (only if sharedModules.length > 0)
203
+ 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)'}"
204
+ - label: "{language == 'fr' ? 'Application unique' : 'Single application'}"
205
+ description: "{language == 'fr' ? 'Tout regrouper en une seule application avec plusieurs modules' : 'Group everything into one application with multiple modules'}"
206
+ ```
207
+
208
+ **IF "Oui, N applications" or "Extraire les modules partagés":**
209
+
210
+ ```yaml
211
+ workflow_mode: "project"
212
+ project_name: derived from feature_description
213
+ candidate_applications: [{ name, description, modules, context }]
214
+ shared_modules_extracted: boolean # true if user chose extraction
215
+ ```
216
+
217
+ → Skip step 4 (application name) — applications will be confirmed in step-01b
218
+ → Continue to step 5 (language selection)
219
+
220
+ **IF "Application unique":**
221
+
222
+ ```yaml
223
+ workflow_mode: "application"
224
+ ```
225
+
226
+ → Continue to step 4 normally
227
+
228
+ **IF no multi-app patterns detected:**
229
+ → Continue to step 4 normally
230
+
132
231
  ## Step 4: Determine Application Name
133
232
 
233
+ > **This step is SKIPPED if `workflow_mode = "project"` (multi-app detected in step 3b).**
234
+
134
235
  ```
135
- IF workflow_type = "update":
236
+ IF workflow_mode = "project":
237
+ application_name = project_name // Project-level name, not a single app name
238
+ context = "business" // Default, each app can have its own context
239
+ → Skip to step 5
240
+
241
+ ELSE IF workflow_type = "update":
136
242
  application_name = existing app name (from step 3)
137
243
  ELSE:
138
244
  Analyze {feature_description} to extract application name
@@ -149,12 +255,13 @@ ELSE:
149
255
  **Validate business context:**
150
256
  ```
151
257
  validate_business_context("business")
152
- → BA restricted to 'business' context only
258
+ → BA restricted to 'business' context only (in standalone mode)
259
+ → In project mode, each application can target any context
153
260
  ```
154
261
 
155
262
  **Store:**
156
263
  ```yaml
157
- application_name: string
264
+ application_name: string # or project_name if project mode
158
265
  context: "business"
159
266
  ```
160
267
 
@@ -216,15 +323,22 @@ feature_id: string
216
323
  ## Step 7: Create Output Directory Structure
217
324
 
218
325
  ```
219
- IF workflow_type = "new":
326
+ IF workflow_mode = "project":
327
+ // Project mode: create project-level directory + per-app directories later (in step-01b)
328
+ mkdir -p docs/business-analyse/v1.0
329
+ docs_dir = "docs/business-analyse/v{version}"
330
+
331
+ ELSE IF workflow_type = "new":
220
332
  mkdir -p docs/business/{application_name}/business-analyse/v1.0
333
+ docs_dir = "docs/business/{app}/business-analyse/v{version}"
334
+
221
335
  ELSE:
222
336
  Directory already exists from ba-writer.createVersion()
223
337
  ```
224
338
 
225
339
  **Store:**
226
340
  ```yaml
227
- docs_dir: "docs/business/{app}/business-analyse/v{version}"
341
+ docs_dir: string
228
342
  ```
229
343
 
230
344
  ## Step 8: Deploy JSON Schemas to Project (MANDATORY)
@@ -277,34 +391,67 @@ See [references/cache-warming-strategy.md](../references/cache-warming-strategy.
277
391
  Create the master feature document using ba-writer agent.
278
392
 
279
393
  ```
280
- ba-writer.createApplicationFeature({
281
- id: {feature_id},
282
- version: {version},
283
- scope: "application",
284
- status: "draft",
285
- metadata: {
286
- application: {application_name},
287
- context: "business",
288
- language: {language},
289
- featureDescription: {feature_description},
290
- workflowType: {workflow_type},
291
- analysisMode: "interactive",
292
- mcpAvailable: true,
293
- workflow: {
294
- mode: "application",
295
- moduleOrder: [],
296
- currentModuleIndex: 0,
297
- completedModules: [],
298
- currentModule: null
394
+ IF workflow_mode = "project":
395
+ // PROJECT MODE: Create project-level feature.json
396
+ ba-writer.createProjectFeature({
397
+ id: generate_project_id(), // PROJ-NNN
398
+ version: {version},
399
+ scope: "project",
400
+ status: "draft",
401
+ metadata: {
402
+ projectName: {project_name},
403
+ language: {language},
404
+ featureDescription: {feature_description},
405
+ workflowType: {workflow_type},
406
+ analysisMode: "interactive",
407
+ mcpAvailable: true,
408
+ candidateApplications: {candidate_applications}, // From step 3b detection
409
+ sharedModulesExtracted: {shared_modules_extracted},
410
+ workflow: {
411
+ mode: "project",
412
+ applicationOrder: [],
413
+ currentApplicationIndex: 0,
414
+ completedApplications: []
415
+ }
299
416
  }
300
- }
301
- })
302
- ```
417
+ })
303
418
 
304
- **Output path:**
305
- `docs/business/{app}/business-analyse/v{version}/feature.json`
419
+ Output path: docs/business-analyse/v{version}/feature.json
306
420
 
307
- > **Note:** Always create the master feature.json first. Step-02 (decomposition) determines if it's single or multi-module.
421
+ Store:
422
+ project_id: string // PROJ-NNN
423
+ feature_id: project_id // In project mode, feature_id = project_id for step-01
424
+
425
+ ELSE:
426
+ // SINGLE-APP MODE: Create application-level feature.json
427
+ ba-writer.createApplicationFeature({
428
+ id: {feature_id},
429
+ version: {version},
430
+ scope: "application",
431
+ status: "draft",
432
+ metadata: {
433
+ application: {application_name},
434
+ context: "business",
435
+ language: {language},
436
+ featureDescription: {feature_description},
437
+ workflowType: {workflow_type},
438
+ analysisMode: "interactive",
439
+ mcpAvailable: true,
440
+ workflow: {
441
+ mode: "application",
442
+ moduleOrder: [],
443
+ currentModuleIndex: 0,
444
+ completedModules: [],
445
+ currentModule: null
446
+ }
447
+ }
448
+ })
449
+
450
+ Output path: docs/business/{app}/business-analyse/v{version}/feature.json
451
+ ```
452
+
453
+ > **Note:** In project mode, per-application feature.json files are created later in step-01b.
454
+ > In single-app mode, step-02 (decomposition) determines if it's single or multi-module.
308
455
 
309
456
  ## Step 10: Update Config
310
457
 
@@ -335,13 +482,18 @@ Update `.business-analyse/config.json` with new feature information.
335
482
  |--------------------|----------------------------------------------|
336
483
  | Feature ID | {feature_id} |
337
484
  | Workflow | {workflow_type} |
338
- | Application | business/{application_name} |
485
+ | Mode | {workflow_mode === "project" ? "Multi-application project" : "Single application"} |
486
+ | Application/Project| {workflow_mode === "project" ? project_name + " (" + candidate_applications.length + " apps)" : "business/" + application_name} |
339
487
  | Output Path | {docs_dir}/feature.json |
340
488
  | Language | {language} |
341
489
  | Version | {version} |
342
490
  | MCP Available | true |
343
491
  | Analysis Mode | Interactive (always) |
344
492
 
493
+ {workflow_mode === "project"
494
+ ? "Candidate applications: " + candidate_applications.map(a => a.name).join(", ")
495
+ : ""}
496
+
345
497
  NEXT STEP: step-01-cadrage
346
498
  ═══════════════════════════════════════════════════════════════
347
499
  ```
@@ -352,14 +504,18 @@ After showing initialization summary, proceed to `./step-01-cadrage.md`
352
504
 
353
505
  **Pass context variables:**
354
506
  ```yaml
355
- feature_id: string
507
+ feature_id: string # FEAT-NNN (single-app) or PROJ-NNN (project)
356
508
  feature_description: string
357
509
  workflow_type: "new" | "update"
358
- application_name: string
510
+ workflow_mode: "application" | "project"
511
+ application_name: string # Single-app: app name. Project: project name.
512
+ applicationCode: string # PascalCase derived from application_name (preliminary, confirmed in step-01b)
513
+ project_id: string | null # PROJ-NNN if project mode, null if single-app
514
+ candidate_applications: array | null # Pre-identified apps from prompt (project mode only)
515
+ shared_modules_extracted: boolean # True if user chose to extract shared modules as new app
359
516
  language: string
360
517
  docs_dir: string
361
518
  mcp_available: boolean
362
- workflow_mode: "application"
363
519
  analysisMode: "interactive"
364
520
  version: string
365
521
  ```
@@ -41,14 +41,31 @@ Phase 5: PERIMETRE → Bound scope with roles, coverage matrix (sections
41
41
 
42
42
  ---
43
43
 
44
+ ## PROJECT vs APPLICATION MODE
45
+
46
+ > **When `workflow_mode = "project"` (detected in step-00 from prompt structure):**
47
+ > The cadrage operates at the **PROJECT level**, not at a single application level.
48
+ > - Phase 1-3: Gather requirements for the ENTIRE project (all applications combined)
49
+ > - Phase 4: ANTICIPATION analyzes cross-application shared modules and suggests extracting them as dedicated applications
50
+ > - Phase 5: Coverage matrix maps requirements to APPLICATIONS first, then modules within each application
51
+ > - `candidate_applications` from step-00 are used as starting structure (can be modified during cadrage)
52
+ >
53
+ > **When `workflow_mode = "application"` (single-app):**
54
+ > The cadrage operates at the application level as before.
55
+
44
56
  ## PHASE 1: ECOUTE (Listen)
45
57
 
46
58
  ### 1. Read Current State
47
59
 
48
60
  ```
49
- ba-reader.findFeature({feature_id})
50
- → Read metadata: application, language, workflow.mode, useCase
51
- → Read cadrage section (if resuming)
61
+ IF workflow_mode === "project":
62
+ ba-reader.findProjectFeature({project_id})
63
+ → Read metadata: projectName, language, candidateApplications
64
+ → Read cadrage section (if resuming)
65
+ ELSE:
66
+ ba-reader.findFeature({feature_id})
67
+ → Read metadata: application, language, workflow.mode, useCase
68
+ → Read cadrage section (if resuming)
52
69
  ```
53
70
 
54
71
  IF cadrage already completed (status = "framed"):
@@ -507,6 +524,91 @@ options:
507
524
 
508
525
  6. Accepted suggestions enrich `coverageMatrix` and `globalScope`.
509
526
 
527
+ ### 5b. Cross-Application Shared Module Detection (PROJECT MODE ONLY)
528
+
529
+ > **When `workflow_mode = "project"`, analyze candidate applications for shared modules.**
530
+ > A module that appears in 2+ applications should be extracted as a dedicated transversal application.
531
+
532
+ **This section is SKIPPED if `workflow_mode !== "project"`.**
533
+
534
+ **Process:**
535
+
536
+ 1. Build module inventory per candidate application:
537
+
538
+ ```javascript
539
+ // From candidateApplications (step-00) + enriched by cadrage phases 1-4
540
+ const modulesByApp = {};
541
+ for (const app of candidate_applications) {
542
+ modulesByApp[app.name] = app.modules; // e.g., { "RH": ["Employes", "Conges", "Temps"], "Projet": ["Projets", "Temps"] }
543
+ }
544
+ ```
545
+
546
+ 2. Detect modules appearing in 2+ applications:
547
+
548
+ ```javascript
549
+ const allModules = Object.values(modulesByApp).flat();
550
+ const moduleCounts = {};
551
+ for (const mod of allModules) {
552
+ const normalized = normalizeModuleName(mod); // "Temps", "Saisie du temps", "Time Tracking" → same concept
553
+ moduleCounts[normalized] = (moduleCounts[normalized] || 0) + 1;
554
+ }
555
+ const sharedModules = Object.entries(moduleCounts)
556
+ .filter(([_, count]) => count >= 2)
557
+ .map(([name, count]) => ({ name, appearsIn: count, applications: findAppsContaining(name) }));
558
+ ```
559
+
560
+ 3. IF shared modules found, propose extraction:
561
+
562
+ ```
563
+ {language == "fr"
564
+ ? "### Modules partagés détectés\n\nCertains modules apparaissent dans plusieurs applications. Les extraire en application(s) dédiée(s) améliore la réutilisabilité et évite la duplication :"
565
+ : "### Shared modules detected\n\nSome modules appear in multiple applications. Extracting them as dedicated application(s) improves reusability and avoids duplication:"}
566
+
567
+ | Module | Présent dans | Suggestion |
568
+ |--------|-------------|-----------|
569
+ {for each shared: name | apps.join(", ") | "Extraire en application '{name}'" }
570
+
571
+ {language == "fr"
572
+ ? "**Exemple :** Si 'Gestion du temps' est dans RH et Projet, créer une application 'Temps' dédiée que les deux autres consomment."
573
+ : "**Example:** If 'Time Management' is in HR and Project, create a dedicated 'Time' application that the other two consume."}
574
+ ```
575
+
576
+ Ask via AskUserQuestion:
577
+ ```
578
+ question: "{language == 'fr' ? 'Souhaitez-vous extraire les modules partagés en application(s) dédiée(s) ?' : 'Do you want to extract shared modules as dedicated application(s)?'}"
579
+ header: "Extraction"
580
+ options:
581
+ - label: "{language == 'fr' ? 'Oui, extraire' : 'Yes, extract'}"
582
+ description: "{language == 'fr' ? 'Créer {sharedModules.length} application(s) transversale(s) supplémentaire(s)' : 'Create {sharedModules.length} additional cross-cutting application(s)'}"
583
+ - label: "{language == 'fr' ? 'Non, garder en l\'état' : 'No, keep as-is'}"
584
+ description: "{language == 'fr' ? 'Chaque application garde sa propre version du module' : 'Each application keeps its own version of the module'}"
585
+ ```
586
+
587
+ **IF "Oui, extraire":**
588
+ ```javascript
589
+ for (const shared of sharedModules) {
590
+ // Create new candidate application
591
+ candidate_applications.push({
592
+ name: shared.name,
593
+ description: `Application transversale pour ${shared.name}`,
594
+ modules: [shared.name],
595
+ context: "business",
596
+ isTransversal: true,
597
+ consumedBy: shared.applications
598
+ });
599
+
600
+ // Remove the shared module from original applications
601
+ for (const appName of shared.applications) {
602
+ const app = candidate_applications.find(a => a.name === appName);
603
+ app.modules = app.modules.filter(m => normalizeModuleName(m) !== shared.name);
604
+ app.dependencies = app.dependencies || [];
605
+ app.dependencies.push({ target: shared.name, type: "data-dependency" });
606
+ }
607
+ }
608
+ ```
609
+
610
+ Update `candidate_applications` in the project feature.json.
611
+
510
612
  ---
511
613
 
512
614
  ## PHASE 5: PERIMETRE (Bound Scope)
@@ -720,9 +822,25 @@ ba-writer.enrichSection({
720
822
  ba-writer.updateStatus({feature_id}, "framed")
721
823
  ```
722
824
 
723
- ### 9. Multi-Application Detection (NEW)
825
+ ### 9. Multi-Application Detection
724
826
 
725
827
  > **Analyze whether the project spans multiple independent applications.**
828
+ > **SKIP this section entirely if `workflow_mode = "project"`** — multi-app was already detected in step-00
829
+ > and cross-app analysis was done in section 5b above.
830
+
831
+ **IF `workflow_mode === "project"`:**
832
+ → SKIP section 9 entirely. Candidate applications are already identified and enriched.
833
+ → Write cadrage data to project-level feature.json:
834
+ ```
835
+ ba-writer.enrichSection({
836
+ projectId: {project_id},
837
+ section: "cadrage",
838
+ data: {cadrage data collected in phases 1-5 above}
839
+ })
840
+ ```
841
+ → Continue to section 10 (summary).
842
+
843
+ **IF `workflow_mode === "application"` (single-app mode):**
726
844
 
727
845
  After coverage matrix is validated, check if the identified functional domains suggest multiple applications:
728
846
 
@@ -763,15 +881,15 @@ ba-writer.createProjectFeature({
763
881
  metadata: {
764
882
  projectName: {project_name derived from feature_description},
765
883
  language: {language},
766
- featureDescription: {feature_description}
884
+ featureDescription: {feature_description},
885
+ candidateApplications: [{detected candidates}]
767
886
  },
768
887
  cadrage: {cadrage data already collected in this step}
769
888
  })
770
889
  ```
771
- 2. Set `workflow.mode = "project"` in project feature.json
890
+ 2. Set `workflow_mode = "project"` and `project_id = PROJ-NNN`
772
891
  3. Load `questionnaire/00b-project.md` for additional project-level questions
773
- 4. Load `steps/step-01b-applications.md` for application decomposition
774
- 5. **STOP — DO NOT load step-02**
892
+ 4. Continue to section 10 and then step-01b
775
893
 
776
894
  **IF "Single application":**
777
895
  → Continue to step 10 (display summary) and step-02 as usual.
@@ -807,5 +925,6 @@ ba-writer.createProjectFeature({
807
925
 
808
926
  ## NEXT STEP
809
927
 
810
- **IF workflow.mode === "project":** Load: `./step-01b-applications.md`
811
- **ELSE:** Load: `./step-02-decomposition.md`
928
+ Load: `./step-01b-applications.md`
929
+
930
+ > step-01b handles both single-app (lightweight identity confirmation) and multi-app (full application decomposition).