@atlashub/smartstack-cli 3.33.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 (65) hide show
  1. package/.documentation/agents.html +5 -1
  2. package/.documentation/apex.html +644 -0
  3. package/.documentation/business-analyse.html +81 -1
  4. package/.documentation/cli-commands.html +5 -1
  5. package/.documentation/commands.html +5 -1
  6. package/.documentation/efcore.html +5 -1
  7. package/.documentation/gitflow.html +5 -1
  8. package/.documentation/hooks.html +5 -1
  9. package/.documentation/index.html +60 -2
  10. package/.documentation/init.html +414 -1
  11. package/.documentation/installation.html +5 -1
  12. package/.documentation/ralph-loop.html +365 -216
  13. package/.documentation/test-web.html +5 -1
  14. package/dist/index.js +32 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp-entry.mjs +7 -24
  17. package/dist/mcp-entry.mjs.map +1 -1
  18. package/package.json +1 -2
  19. package/templates/agents/ba-writer.md +142 -15
  20. package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
  21. package/templates/skills/apex/SKILL.md +9 -3
  22. package/templates/skills/apex/_shared.md +49 -4
  23. package/templates/skills/{ralph-loop → apex}/references/core-seed-data.md +20 -11
  24. package/templates/skills/{ralph-loop → apex}/references/error-classification.md +2 -1
  25. package/templates/skills/apex/references/post-checks.md +463 -3
  26. package/templates/skills/apex/references/smartstack-api.md +76 -8
  27. package/templates/skills/apex/references/smartstack-frontend.md +74 -1
  28. package/templates/skills/apex/references/smartstack-layers.md +21 -3
  29. package/templates/skills/apex/steps/step-00-init.md +121 -1
  30. package/templates/skills/apex/steps/step-01-analyze.md +58 -0
  31. package/templates/skills/apex/steps/step-02-plan.md +36 -0
  32. package/templates/skills/apex/steps/step-03-execute.md +114 -7
  33. package/templates/skills/apex/steps/step-04-examine.md +116 -2
  34. package/templates/skills/business-analyse/SKILL.md +31 -20
  35. package/templates/skills/business-analyse/_module-loop.md +68 -9
  36. package/templates/skills/business-analyse/_shared.md +80 -21
  37. package/templates/skills/business-analyse/questionnaire/00-application.md +4 -2
  38. package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -0
  39. package/templates/skills/business-analyse/references/deploy-modes.md +69 -0
  40. package/templates/skills/business-analyse/references/team-orchestration.md +158 -7
  41. package/templates/skills/business-analyse/schemas/application-schema.json +15 -1
  42. package/templates/skills/business-analyse/schemas/project-schema.json +490 -0
  43. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +2 -1
  44. package/templates/skills/business-analyse/steps/step-00-init.md +220 -38
  45. package/templates/skills/business-analyse/steps/step-01-cadrage.md +184 -5
  46. package/templates/skills/business-analyse/steps/step-01b-applications.md +423 -0
  47. package/templates/skills/business-analyse/steps/step-02-decomposition.md +23 -6
  48. package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
  49. package/templates/skills/business-analyse/steps/step-03d-validate.md +32 -7
  50. package/templates/skills/business-analyse/steps/step-04a-collect.md +111 -0
  51. package/templates/skills/business-analyse/steps/step-05a-handoff.md +296 -103
  52. package/templates/skills/business-analyse/steps/step-05b-deploy.md +46 -14
  53. package/templates/skills/documentation/SKILL.md +92 -2
  54. package/templates/skills/ralph-loop/SKILL.md +14 -17
  55. package/templates/skills/ralph-loop/references/category-rules.md +63 -683
  56. package/templates/skills/ralph-loop/references/compact-loop.md +188 -428
  57. package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
  58. package/templates/skills/ralph-loop/references/team-orchestration.md +13 -14
  59. package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
  60. package/templates/skills/ralph-loop/steps/step-02-execute.md +80 -691
  61. package/templates/skills/ralph-loop/steps/step-03-commit.md +38 -79
  62. package/templates/skills/ralph-loop/steps/step-04-check.md +39 -58
  63. package/templates/skills/ralph-loop/steps/step-05-report.md +31 -123
  64. package/scripts/health-check.sh +0 -168
  65. package/scripts/postinstall.js +0 -18
@@ -279,6 +279,117 @@ Identify shared lookup/reference tables:
279
279
 
280
280
  ---
281
281
 
282
+ ### 2f. Cross-Application Validation (Project Mode Only)
283
+
284
+ > **Only runs when `workflow.mode === "project"`** (multi-application feature).
285
+ > Validates cross-application interactions identified during step-01b.
286
+
287
+ ```
288
+ IF workflow.mode !== "project":
289
+ SKIP this section entirely
290
+ → Proceed to section 6 storage
291
+ ```
292
+
293
+ **Process:**
294
+
295
+ 1. **Load project feature.json:**
296
+ ```
297
+ projectFeature = ba-reader.findProjectFeature()
298
+ applications = projectFeature.applications
299
+ applicationGraph = projectFeature.applicationDependencyGraph
300
+ ```
301
+
302
+ 2. **Cross-Application Shared Entities:**
303
+ ```javascript
304
+ const crossAppSharedEntities = [];
305
+ for (const app1 of applications) {
306
+ for (const app2 of applications) {
307
+ if (app1.code === app2.code) continue;
308
+ for (const mod1 of app1.modules) {
309
+ for (const mod2 of app2.modules) {
310
+ // Detect entity name overlap or FK references across apps
311
+ const shared = findSharedEntities(mod1.entities, mod2.entities);
312
+ if (shared.length > 0) {
313
+ crossAppSharedEntities.push({
314
+ entity: shared[0].name,
315
+ definedInApp: app1.code,
316
+ definedInModule: mod1.code,
317
+ referencedByApp: app2.code,
318
+ referencedByModule: mod2.code,
319
+ referenceType: shared[0].type
320
+ });
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ ```
327
+
328
+ 3. **Cross-Application Permission Path Consistency:**
329
+ ```javascript
330
+ // Verify permission paths use correct context per application
331
+ for (const app of applications) {
332
+ const expectedPrefix = `${app.context}.${toKebabCase(app.code)}`;
333
+ for (const mod of app.modules) {
334
+ for (const perm of mod.permissions || []) {
335
+ if (!perm.path.startsWith(expectedPrefix)) {
336
+ WARNING(`Permission path mismatch in ${app.code}/${mod.code}: "${perm.path}" should start with "${expectedPrefix}"`);
337
+ }
338
+ }
339
+ }
340
+ }
341
+ ```
342
+
343
+ 4. **Cross-Application Role Name Consistency:**
344
+ ```javascript
345
+ // Check for role name conflicts across applications
346
+ const allRoles = {};
347
+ for (const app of applications) {
348
+ for (const role of app.applicationRoles || []) {
349
+ if (allRoles[role.role] && allRoles[role.role].level !== role.level) {
350
+ WARNING(`Role "${role.role}" has different levels: ${allRoles[role.role].app}=${allRoles[role.role].level} vs ${app.code}=${role.level}`);
351
+ }
352
+ allRoles[role.role] = { app: app.code, level: role.level };
353
+ }
354
+ }
355
+ ```
356
+
357
+ 5. **Store Cross-Application Data:**
358
+ ```javascript
359
+ ba-writer.enrichSection({
360
+ featureId: {project_id},
361
+ section: "consolidation.crossApplicationInteractions",
362
+ data: crossAppSharedEntities.map(shared => ({
363
+ fromApplication: shared.definedInApp,
364
+ fromModule: shared.definedInModule,
365
+ toApplication: shared.referencedByApp,
366
+ toModule: shared.referencedByModule,
367
+ interactionType: shared.referenceType,
368
+ entity: shared.entity,
369
+ description: `${shared.entity} defined in ${shared.definedInApp}/${shared.definedInModule}, referenced by ${shared.referencedByApp}/${shared.referencedByModule}`
370
+ }))
371
+ });
372
+ ```
373
+
374
+ 6. **Display Cross-Application Interaction Map:**
375
+
376
+ ```
377
+ ═══════════════════════════════════════════════════════════════
378
+ CROSS-APPLICATION INTERACTIONS
379
+ ═══════════════════════════════════════════════════════════════
380
+
381
+ | Source App | Source Module | Target App | Target Module | Entity | Type |
382
+ |------------|-------------|------------|--------------|--------|------|
383
+ | HR | Employees | SelfService | LeaveRequests | Employee | FK |
384
+
385
+ Shared entities: {count}
386
+ Permission paths: {valid_count}/{total_count} consistent
387
+ Role conflicts: {conflict_count}
388
+ ═══════════════════════════════════════════════════════════════
389
+ ```
390
+
391
+ ---
392
+
282
393
  ## SINGLE-MODULE MODE
283
394
 
284
395
  When only 1 module:
@@ -32,8 +32,20 @@ Build the development handoff data: verify consolidation, choose implementation
32
32
  Use ba-reader to locate the feature and verify consolidation status:
33
33
 
34
34
  ```
35
- ba-reader.findFeature({feature_id})
36
- Check status = "consolidated"
35
+ // Determine feature source based on workflow mode
36
+ IF workflow.mode === "project":
37
+ projectFeature = ba-reader.findProjectFeature()
38
+ // Verify ALL applications are specified
39
+ FOR each app in projectFeature.applications:
40
+ appFeature = ba-reader.findFeature(app.featureJsonPath)
41
+ IF appFeature.status !== "consolidated":
42
+ BLOCKING ERROR: "Application {app.code} not consolidated (status: {appFeature.status})"
43
+ → Return to step-04a-collect.md
44
+ // Use project-level feature for overall consolidation
45
+ feature = projectFeature
46
+ ELSE:
47
+ ba-reader.findFeature({feature_id})
48
+ → Check status = "consolidated"
37
49
  ```
38
50
 
39
51
  **IF** status ≠ "consolidated" → **STOP**. Return to step-04a-collect.md.
@@ -42,18 +54,22 @@ Display validation summary:
42
54
 
43
55
  ```
44
56
  ✓ Consolidation: APPROVED
57
+ ✓ Applications: {app_count} (project mode only)
45
58
  ✓ Modules: {count} specified and validated
46
59
  ✓ Cross-module: interactions mapped
60
+ ✓ Cross-application: interactions mapped (project mode only)
47
61
  ✓ Permissions: coherent
48
62
  → Proceeding to handoff...
49
63
  ```
50
64
 
51
65
  Include:
52
- - Number of modules
66
+ - Number of applications (project mode only)
67
+ - Number of modules across all applications
53
68
  - Number of entities across all modules
54
69
  - Number of use cases across all modules
55
70
  - Number of business rules across all modules
56
71
  - Cross-module interaction count
72
+ - Cross-application interaction count (project mode only)
57
73
 
58
74
  ---
59
75
 
@@ -101,9 +117,9 @@ See [references/cache-warming-strategy.md](../references/cache-warming-strategy.
101
117
 
102
118
  ---
103
119
 
104
- ### 2. Implementation Strategy Choice (Multi-Module)
120
+ ### 2. Implementation Strategy Choice (Multi-Module/Multi-App)
105
121
 
106
- **IF** more than 1 module defined in feature.json:
122
+ **IF** more than 1 module defined in feature.json (or project mode with multiple applications):
107
123
 
108
124
  Ask via AskUserQuestion:
109
125
 
@@ -117,8 +133,12 @@ options:
117
133
  description: "Implémenter toutes les entités, puis tous les services, puis tous les contrôleurs, etc. Plus de parallélisation possible mais plus complexe."
118
134
  - label: "Hybride"
119
135
  description: "Modules fondation en premier (couche par couche), puis modules dépendants (module par module)"
136
+ - label: "Application par application (Recommandé multi-app)"
137
+ description: "Implémenter chaque application complètement avant de passer à la suivante. Suit l'ordre topologique inter-applications."
120
138
  ```
121
139
 
140
+ > **Note:** "Application par application" only appears for project mode with 2+ applications.
141
+
122
142
  Store the chosen strategy in `handoff.implementationStrategy`.
123
143
 
124
144
  **IF** only 1 module → default to "Module par module" (no choice needed).
@@ -335,6 +355,31 @@ Generate `.ralph/prd-CrossModule.json` with:
335
355
 
336
356
  Add to progress.txt after all module tasks.
337
357
 
358
+ ### 6d. Cross-Application PRD (Project Mode Only)
359
+
360
+ > **Only generated when `workflow.mode === "project"`.**
361
+ > Contains cross-application integration tests and shared entity validation.
362
+
363
+ ```json
364
+ {
365
+ "$version": "3.0.0",
366
+ "implementation": {
367
+ "filesToCreate": {
368
+ "tests": [
369
+ { "path": "src/Tests/Integration/CrossApplication/SharedEntityValidationTests.cs", "type": "IntegrationTests", "module": "CrossApplication" },
370
+ { "path": "src/Tests/Integration/CrossApplication/CrossAppPermissionTests.cs", "type": "SecurityTests", "module": "CrossApplication" },
371
+ { "path": "src/Tests/Integration/CrossApplication/CrossAppE2EFlowTests.cs", "type": "E2ETests", "module": "CrossApplication" }
372
+ ]
373
+ }
374
+ }
375
+ }
376
+ ```
377
+
378
+ Write to: `.ralph/prd-CrossApplication.json`
379
+
380
+ > This PRD is generated IN ADDITION to the per-module cross-module PRD.
381
+ > It validates interactions between applications, not just between modules within the same application.
382
+
338
383
  ---
339
384
 
340
385
  ### 7. Write Handoff to Feature.json
@@ -434,53 +479,119 @@ const lang = master.metadata.language || "fr";
434
479
 
435
480
  const seedDataCore = {
436
481
  // Application-level navigation (MANDATORY — one entry per application)
437
- navigationApplications: [{
438
- code: appCode.toLowerCase(),
439
- name: appCode,
440
- labels: {
441
- fr: lang === "fr" ? (master.cadrage?.applicationName || appLabel) : appLabel,
442
- en: lang === "en" ? (master.cadrage?.applicationName || appLabel) : appLabel,
443
- it: appLabel,
444
- de: appLabel
445
- },
446
- description: {
447
- fr: lang === "fr" ? appDesc : `Gestion ${appLabel}`,
448
- en: lang === "en" ? appDesc : `${appLabel} management`,
449
- it: `Gestione ${appLabel}`,
450
- de: `${appLabel} Verwaltung`
451
- },
452
- icon: inferIconFromApplication(master) || "layout-grid",
453
- iconType: "lucide",
454
- context: contextCode,
455
- route: `/${contextCode}/${toKebabCase(appCode)}`,
456
- displayOrder: 1
457
- }],
458
-
459
- navigationModules: master.modules.map((m, i) => ({
460
- code: m.code,
461
- label: m.name || m.code,
462
- description: m.description,
463
- icon: inferIconFromModule(m) || "folder", // MUST be non-null — use sensible default
464
- iconType: "lucide",
465
- route: `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}`,
466
- displayOrder: (i + 1) * 10
467
- })),
468
-
469
- navigationSections: master.modules.flatMap(m =>
470
- (m.anticipatedSections || []).map((s, j) => ({
471
- moduleCode: m.code,
472
- code: s.code,
473
- label: s.description?.split(':')[0] || s.code,
474
- description: s.description || "",
475
- route: s.code === "list"
476
- ? `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}`
477
- : s.code === "detail"
478
- ? `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}/:id`
479
- : `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}/${toKebabCase(s.code)}`,
480
- displayOrder: (j + 1) * 10,
481
- navigation: s.code === "detail" ? "hidden" : "visible"
482
- }))
483
- ),
482
+ // PROJECT MODE: Generate one entry per application from project feature.json
483
+ // SINGLE-APP MODE: Generate one entry from master feature.json
484
+ navigationApplications: (() => {
485
+ if (workflow.mode === "project") {
486
+ // Multi-app: one navigation entry per application
487
+ return projectFeature.applications.map((app, idx) => {
488
+ const appLabel = toHumanReadable(app.code);
489
+ const appContext = app.context || "business";
490
+ return {
491
+ code: app.code.toLowerCase(),
492
+ name: app.code,
493
+ labels: {
494
+ fr: lang === "fr" ? (app.name || appLabel) : appLabel,
495
+ en: lang === "en" ? (app.name || appLabel) : appLabel,
496
+ it: appLabel,
497
+ de: appLabel
498
+ },
499
+ description: {
500
+ fr: lang === "fr" ? (app.description || `Gestion ${appLabel}`) : `Gestion ${appLabel}`,
501
+ en: lang === "en" ? (app.description || `${appLabel} management`) : `${appLabel} management`,
502
+ it: `Gestione ${appLabel}`,
503
+ de: `${appLabel} Verwaltung`
504
+ },
505
+ icon: app.icon || inferIconFromApplication({ metadata: { application: app.code } }) || "layout-grid",
506
+ iconType: "lucide",
507
+ context: appContext,
508
+ route: `/${appContext}/${toKebabCase(app.code)}`,
509
+ displayOrder: (idx + 1) * 10
510
+ };
511
+ });
512
+ } else {
513
+ // Single-app: one navigation entry
514
+ return [{
515
+ code: appCode.toLowerCase(),
516
+ name: appCode,
517
+ labels: {
518
+ fr: lang === "fr" ? (master.cadrage?.applicationName || appLabel) : appLabel,
519
+ en: lang === "en" ? (master.cadrage?.applicationName || appLabel) : appLabel,
520
+ it: appLabel,
521
+ de: appLabel
522
+ },
523
+ description: {
524
+ fr: lang === "fr" ? appDesc : `Gestion ${appLabel}`,
525
+ en: lang === "en" ? appDesc : `${appLabel} management`,
526
+ it: `Gestione ${appLabel}`,
527
+ de: `${appLabel} Verwaltung`
528
+ },
529
+ icon: inferIconFromApplication(master) || "layout-grid",
530
+ iconType: "lucide",
531
+ context: contextCode,
532
+ route: `/${contextCode}/${toKebabCase(appCode)}`,
533
+ displayOrder: 1
534
+ }];
535
+ }
536
+ })(),
537
+
538
+ // PROJECT MODE: Flatten modules from ALL applications
539
+ // SINGLE-APP MODE: Use master.modules directly
540
+ navigationModules: (() => {
541
+ if (workflow.mode === "project") {
542
+ let order = 0;
543
+ return projectFeature.applications.flatMap(app => {
544
+ const appContext = app.context || "business";
545
+ return (app.modules || []).map(m => ({
546
+ code: m.code,
547
+ applicationCode: app.code,
548
+ label: m.name || m.code,
549
+ description: m.description,
550
+ icon: inferIconFromModule(m) || "folder",
551
+ iconType: "lucide",
552
+ route: `/${appContext}/${toKebabCase(app.code)}/${toKebabCase(m.code)}`,
553
+ displayOrder: (++order) * 10
554
+ }));
555
+ });
556
+ } else {
557
+ return master.modules.map((m, i) => ({
558
+ code: m.code,
559
+ label: m.name || m.code,
560
+ description: m.description,
561
+ icon: inferIconFromModule(m) || "folder",
562
+ iconType: "lucide",
563
+ route: `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}`,
564
+ displayOrder: (i + 1) * 10
565
+ }));
566
+ }
567
+ })(),
568
+
569
+ navigationSections: (() => {
570
+ const buildSections = (modules, appContextCode, appCode) =>
571
+ modules.flatMap(m =>
572
+ (m.anticipatedSections || []).map((s, j) => ({
573
+ moduleCode: m.code,
574
+ code: s.code,
575
+ label: s.description?.split(':')[0] || s.code,
576
+ description: s.description || "",
577
+ route: s.code === "list"
578
+ ? `/${appContextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}`
579
+ : s.code === "detail"
580
+ ? `/${appContextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}/:id`
581
+ : `/${appContextCode}/${toKebabCase(appCode)}/${toKebabCase(m.code)}/${toKebabCase(s.code)}`,
582
+ displayOrder: (j + 1) * 10,
583
+ navigation: s.code === "detail" ? "hidden" : "visible"
584
+ }))
585
+ );
586
+
587
+ if (workflow.mode === "project") {
588
+ return projectFeature.applications.flatMap(app =>
589
+ buildSections(app.modules || [], app.context || "business", app.code)
590
+ );
591
+ } else {
592
+ return buildSections(master.modules, contextCode, appCode);
593
+ }
594
+ })(),
484
595
 
485
596
  navigationResources: master.cadrage.coverageMatrix
486
597
  .filter(cm => cm.module && cm.anticipatedResources?.length > 0)
@@ -504,37 +615,73 @@ const seedDataCore = {
504
615
  }));
505
616
  }),
506
617
 
507
- permissions: master.modules.flatMap(m => {
508
- const basePath = `business.${master.metadata.application.toLowerCase()}.${m.code.toLowerCase()}`;
509
- const actions = ['read', 'create', 'update', 'delete'];
510
- return [
511
- { path: `${basePath}.*`, action: '*', description: `Full ${m.name || m.code} access` },
512
- ...actions.map(action => ({
513
- path: `${basePath}.${action}`,
514
- action: action,
515
- description: `${action.charAt(0).toUpperCase() + action.slice(1)} ${m.name || m.code}`
516
- }))
517
- ];
518
- }),
519
-
520
- rolePermissions: master.cadrage.applicationRoles.map(role => ({
521
- role: role.role,
522
- level: role.level,
523
- permissions: master.modules.map(m => `business.${master.metadata.application.toLowerCase()}.${m.code.toLowerCase()}.${
524
- role.level === 'admin' ? '*' :
525
- role.level === 'manager' ? 'read,create,update' :
526
- role.level === 'contributor' ? 'read,create' : 'read'
527
- }`)
528
- })),
529
-
530
- permissionConstants: master.modules.flatMap(m =>
531
- ['Read', 'Create', 'Update', 'Delete', 'Validate', 'Export'].map(action => ({
532
- module: m.code,
533
- action: action,
534
- constant: `${master.metadata.application}${m.code}${action}`,
535
- path: `business.${master.metadata.application.toLowerCase()}.${m.code.toLowerCase()}.${action.toLowerCase()}`
536
- }))
537
- )
618
+ // PROJECT MODE: permissions scoped per application context
619
+ // SINGLE-APP MODE: all permissions under business.{app}
620
+ permissions: (() => {
621
+ const buildPermissions = (modules, appContext, appCode) =>
622
+ modules.flatMap(m => {
623
+ const basePath = `${appContext}.${toKebabCase(appCode)}.${toKebabCase(m.code)}`;
624
+ const actions = ['read', 'create', 'update', 'delete'];
625
+ return [
626
+ { path: `${basePath}.*`, action: '*', description: `Full ${m.name || m.code} access` },
627
+ ...actions.map(action => ({
628
+ path: `${basePath}.${action}`,
629
+ action: action,
630
+ description: `${action.charAt(0).toUpperCase() + action.slice(1)} ${m.name || m.code}`
631
+ }))
632
+ ];
633
+ });
634
+
635
+ if (workflow.mode === "project") {
636
+ return projectFeature.applications.flatMap(app =>
637
+ buildPermissions(app.modules || [], app.context || "business", app.code)
638
+ );
639
+ } else {
640
+ return buildPermissions(master.modules, contextCode, appCode);
641
+ }
642
+ })(),
643
+
644
+ rolePermissions: (() => {
645
+ const buildRolePermissions = (roles, modules, appContext, appCode) =>
646
+ roles.map(role => ({
647
+ role: role.role,
648
+ level: role.level,
649
+ permissions: modules.map(m => `${appContext}.${toKebabCase(appCode)}.${toKebabCase(m.code)}.${
650
+ role.level === 'admin' ? '*' :
651
+ role.level === 'manager' ? 'read,create,update' :
652
+ role.level === 'contributor' ? 'read,create' : 'read'
653
+ }`)
654
+ }));
655
+
656
+ if (workflow.mode === "project") {
657
+ return projectFeature.applications.flatMap(app =>
658
+ buildRolePermissions(app.applicationRoles || [], app.modules || [], app.context || "business", app.code)
659
+ );
660
+ } else {
661
+ return buildRolePermissions(master.cadrage.applicationRoles, master.modules, contextCode, appCode);
662
+ }
663
+ })(),
664
+
665
+ permissionConstants: (() => {
666
+ const buildConstants = (modules, appCode) =>
667
+ modules.flatMap(m =>
668
+ ['Read', 'Create', 'Update', 'Delete', 'Validate', 'Export'].map(action => ({
669
+ module: m.code,
670
+ application: appCode,
671
+ action: action,
672
+ constant: `${appCode}${m.code}${action}`,
673
+ path: `${contextCode}.${toKebabCase(appCode)}.${toKebabCase(m.code)}.${action.toLowerCase()}`
674
+ }))
675
+ );
676
+
677
+ if (workflow.mode === "project") {
678
+ return projectFeature.applications.flatMap(app =>
679
+ buildConstants(app.modules || [], app.code)
680
+ );
681
+ } else {
682
+ return buildConstants(master.modules, appCode);
683
+ }
684
+ })()
538
685
  };
539
686
 
540
687
  // Icon inference for APPLICATION level (NEVER leave as null)
@@ -609,26 +756,72 @@ IF seedDataCore.navigationSections.length === 0:
609
756
  #### 7c. Master Handoff (after ALL modules written + seedDataCore generated)
610
757
 
611
758
  ```
612
- ba-writer.enrichSection({
613
- featureId: {feature_id},
614
- section: "handoff",
615
- data: {
616
- status: "handed-off",
617
- complexity: "{simple|medium|complex}",
618
- implementationStrategy: "{strategy}",
619
- moduleCount: {count},
620
- moduleOrder: [...],
621
- totalFilesToCreate: {sum across all modules},
622
- totalTasks: {sum across all modules},
623
- prdStructure: "per-module | consolidated",
624
- prdFiles: [
625
- { module: "{module1}", path: ".ralph/prd-{module1}.json" },
626
- { module: "{module2}", path: ".ralph/prd-{module2}.json" }
627
- ],
628
- progressTrackerPath: ".ralph/progress.txt",
629
- handedOffAt: "{ISO timestamp}"
630
- }
631
- })
759
+ // Write handoff to application-level feature.json (single-app mode)
760
+ // OR to each application feature.json + project feature.json (project mode)
761
+
762
+ IF workflow.mode === "project":
763
+ // Write handoff to EACH application feature.json
764
+ FOR each app in projectFeature.applications:
765
+ ba-writer.enrichSection({
766
+ featureId: app.featureJsonPath,
767
+ section: "handoff",
768
+ data: {
769
+ status: "handed-off",
770
+ complexity: "{app-level complexity}",
771
+ implementationStrategy: "{strategy}",
772
+ moduleCount: app.modules.length,
773
+ moduleOrder: app.modules.map(m => m.code),
774
+ totalFilesToCreate: {sum across app modules},
775
+ totalTasks: {sum across app modules},
776
+ prdFiles: app.modules.map(m => ({ module: m.code, path: `.ralph/prd-${m.code}.json` })),
777
+ handedOffAt: "{ISO timestamp}"
778
+ }
779
+ })
780
+
781
+ // Write project-level handoff summary
782
+ ba-writer.enrichSection({
783
+ featureId: {project_id},
784
+ section: "handoff",
785
+ data: {
786
+ status: "handed-off",
787
+ complexity: "{global complexity}",
788
+ implementationStrategy: "{strategy}",
789
+ applicationCount: projectFeature.applications.length,
790
+ applicationOrder: projectFeature.applicationDependencyGraph.topologicalOrder,
791
+ totalModuleCount: {sum of all app module counts},
792
+ totalFilesToCreate: {sum across ALL applications and modules},
793
+ totalTasks: {sum across ALL applications and modules},
794
+ prdStructure: "per-module",
795
+ prdFiles: [
796
+ ...allModulePrdFiles,
797
+ { module: "CrossModule", path: ".ralph/prd-CrossModule.json" },
798
+ { module: "CrossApplication", path: ".ralph/prd-CrossApplication.json" }
799
+ ],
800
+ progressTrackerPath: ".ralph/progress.txt",
801
+ handedOffAt: "{ISO timestamp}"
802
+ }
803
+ })
804
+ ELSE:
805
+ ba-writer.enrichSection({
806
+ featureId: {feature_id},
807
+ section: "handoff",
808
+ data: {
809
+ status: "handed-off",
810
+ complexity: "{simple|medium|complex}",
811
+ implementationStrategy: "{strategy}",
812
+ moduleCount: {count},
813
+ moduleOrder: [...],
814
+ totalFilesToCreate: {sum across all modules},
815
+ totalTasks: {sum across all modules},
816
+ prdStructure: "per-module | consolidated",
817
+ prdFiles: [
818
+ { module: "{module1}", path: ".ralph/prd-{module1}.json" },
819
+ { module: "{module2}", path: ".ralph/prd-{module2}.json" }
820
+ ],
821
+ progressTrackerPath: ".ralph/progress.txt",
822
+ handedOffAt: "{ISO timestamp}"
823
+ }
824
+ })
632
825
  ```
633
826
 
634
827
  #### 7d. Final Verification (BLOCKING)