@atlashub/smartstack-cli 3.8.0 → 3.9.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 (120) hide show
  1. package/dist/index.js +365 -2
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -1
  4. package/templates/agents/action.md +1 -0
  5. package/templates/agents/ba-writer.md +33 -0
  6. package/templates/agents/explore-codebase.md +1 -0
  7. package/templates/agents/explore-docs.md +1 -0
  8. package/templates/agents/fix-grammar.md +1 -0
  9. package/templates/agents/snipper.md +1 -0
  10. package/templates/skills/admin/SKILL.md +6 -0
  11. package/templates/skills/ai-prompt/SKILL.md +32 -136
  12. package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -0
  13. package/templates/skills/apex/SKILL.md +120 -0
  14. package/templates/skills/apex/_shared.md +86 -0
  15. package/templates/skills/apex/references/agent-teams-protocol.md +164 -0
  16. package/templates/skills/apex/references/smartstack-layers.md +173 -0
  17. package/templates/skills/apex/steps/step-00-init.md +156 -0
  18. package/templates/skills/apex/steps/step-01-analyze.md +169 -0
  19. package/templates/skills/apex/steps/step-02-plan.md +160 -0
  20. package/templates/skills/apex/steps/step-03-execute.md +166 -0
  21. package/templates/skills/apex/steps/step-04-validate.md +138 -0
  22. package/templates/skills/apex/steps/step-05-examine.md +124 -0
  23. package/templates/skills/apex/steps/step-06-resolve.md +105 -0
  24. package/templates/skills/apex/steps/step-07-tests.md +130 -0
  25. package/templates/skills/apex/steps/step-08-run-tests.md +115 -0
  26. package/templates/skills/application/SKILL.md +10 -0
  27. package/templates/skills/application/references/backend-controller-hierarchy.md +58 -0
  28. package/templates/skills/application/references/backend-entity-seeding.md +72 -0
  29. package/templates/skills/application/references/backend-verification.md +88 -0
  30. package/templates/skills/application/references/frontend-verification.md +111 -0
  31. package/templates/skills/application/references/nav-fallback-procedure.md +200 -0
  32. package/templates/skills/application/references/provider-template.md +134 -0
  33. package/templates/skills/application/references/test-frontend.md +73 -0
  34. package/templates/skills/application/references/test-prerequisites.md +72 -0
  35. package/templates/skills/application/steps/step-01-navigation.md +7 -198
  36. package/templates/skills/application/steps/step-03b-provider.md +4 -128
  37. package/templates/skills/application/steps/step-04-backend.md +20 -350
  38. package/templates/skills/application/steps/step-05-frontend.md +12 -101
  39. package/templates/skills/application/steps/step-07-tests.md +12 -132
  40. package/templates/skills/business-analyse/SKILL.md +11 -2
  41. package/templates/skills/business-analyse/html/ba-interactive.html +176 -14
  42. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +1 -0
  43. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +16 -4
  44. package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +7 -2
  45. package/templates/skills/business-analyse/html/src/scripts/09-export.js +103 -0
  46. package/templates/skills/business-analyse/html/src/scripts/10-comments.js +12 -6
  47. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +24 -2
  48. package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +12 -0
  49. package/templates/skills/business-analyse/html/src/template.html +1 -0
  50. package/templates/skills/business-analyse/references/cadrage-structure-cards.md +78 -0
  51. package/templates/skills/business-analyse/references/cadrage-vibe-coding.md +97 -0
  52. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +92 -0
  53. package/templates/skills/business-analyse/references/deploy-data-build.md +121 -0
  54. package/templates/skills/business-analyse/references/deploy-modes.md +49 -0
  55. package/templates/skills/business-analyse/references/handoff-file-templates.md +119 -0
  56. package/templates/skills/business-analyse/references/handoff-mappings.md +81 -0
  57. package/templates/skills/business-analyse/references/html-data-mapping.md +10 -2
  58. package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -0
  59. package/templates/skills/business-analyse/references/review-data-mapping.md +363 -0
  60. package/templates/skills/business-analyse/references/spec-auto-inference.md +57 -0
  61. package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -0
  62. package/templates/skills/business-analyse/references/ui-resource-cards.md +110 -0
  63. package/templates/skills/business-analyse/references/validate-incremental-html.md +55 -0
  64. package/templates/skills/business-analyse/steps/step-00-init.md +35 -68
  65. package/templates/skills/business-analyse/steps/step-01-cadrage.md +5 -194
  66. package/templates/skills/business-analyse/steps/step-03a-data.md +6 -49
  67. package/templates/skills/business-analyse/steps/step-03b-ui.md +12 -178
  68. package/templates/skills/business-analyse/steps/step-03d-validate.md +3 -48
  69. package/templates/skills/business-analyse/steps/step-04-consolidation.md +9 -104
  70. package/templates/skills/business-analyse/steps/step-05a-handoff.md +25 -441
  71. package/templates/skills/business-analyse/steps/step-05b-deploy.md +19 -187
  72. package/templates/skills/business-analyse/steps/step-06-review.md +277 -0
  73. package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -0
  74. package/templates/skills/cc-agent/steps/step-02-generate.md +5 -78
  75. package/templates/skills/check-version/SKILL.md +7 -0
  76. package/templates/skills/controller/references/controller-code-templates.md +159 -0
  77. package/templates/skills/controller/references/permission-sync-templates.md +152 -0
  78. package/templates/skills/controller/steps/step-03-generate.md +6 -158
  79. package/templates/skills/controller/steps/step-04-perms.md +5 -144
  80. package/templates/skills/debug/SKILL.md +7 -0
  81. package/templates/skills/explore/SKILL.md +6 -0
  82. package/templates/skills/feature-full/SKILL.md +39 -142
  83. package/templates/skills/feature-full/steps/step-01-implementation.md +120 -0
  84. package/templates/skills/gitflow/references/init-config-template.md +135 -0
  85. package/templates/skills/gitflow/references/init-name-normalization.md +103 -0
  86. package/templates/skills/gitflow/references/plan-template.md +69 -0
  87. package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -0
  88. package/templates/skills/gitflow/references/start-local-config.md +110 -0
  89. package/templates/skills/gitflow/steps/step-init.md +18 -289
  90. package/templates/skills/gitflow/steps/step-plan.md +6 -63
  91. package/templates/skills/gitflow/steps/step-start.md +16 -126
  92. package/templates/skills/mcp/SKILL.md +9 -213
  93. package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -0
  94. package/templates/skills/mcp/steps/step-02-tools.md +73 -0
  95. package/templates/skills/notification/SKILL.md +7 -0
  96. package/templates/skills/quick-search/SKILL.md +5 -0
  97. package/templates/skills/ralph-loop/SKILL.md +99 -381
  98. package/templates/skills/ralph-loop/references/category-rules.md +259 -0
  99. package/templates/skills/ralph-loop/references/compact-loop.md +182 -0
  100. package/templates/skills/ralph-loop/references/task-transform-legacy.md +259 -0
  101. package/templates/skills/ralph-loop/references/team-orchestration.md +189 -0
  102. package/templates/skills/ralph-loop/steps/step-00-init.md +111 -383
  103. package/templates/skills/ralph-loop/steps/step-01-task.md +79 -896
  104. package/templates/skills/ralph-loop/steps/step-02-execute.md +68 -680
  105. package/templates/skills/ralph-loop/steps/step-03-commit.md +47 -277
  106. package/templates/skills/ralph-loop/steps/step-04-check.md +124 -607
  107. package/templates/skills/ralph-loop/steps/step-05-report.md +68 -367
  108. package/templates/skills/refactor/SKILL.md +12 -176
  109. package/templates/skills/refactor/steps/step-01-discover.md +60 -0
  110. package/templates/skills/refactor/steps/step-02-execute.md +67 -0
  111. package/templates/skills/review-code/SKILL.md +19 -257
  112. package/templates/skills/review-code/steps/step-01-smartstack.md +96 -0
  113. package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -0
  114. package/templates/skills/review-code/steps/step-03-react.md +44 -0
  115. package/templates/skills/ui-components/SKILL.md +7 -0
  116. package/templates/skills/utils/SKILL.md +6 -0
  117. package/templates/skills/validate/SKILL.md +6 -0
  118. package/templates/skills/validate-feature/SKILL.md +8 -0
  119. package/templates/skills/workflow/SKILL.md +40 -118
  120. package/templates/skills/workflow/steps/step-01-implementation.md +84 -0
@@ -47,78 +47,11 @@ From previous steps:
47
47
 
48
48
  ## PRE-REQUISITES
49
49
 
50
- ### 1. Verify Test Project Exists
51
-
52
- Check that the solution contains a test project:
53
-
54
- ```bash
55
- # Look for existing test project
56
- ls *Tests*/*.csproj 2>/dev/null || ls tests/*/*.csproj 2>/dev/null
57
- ```
58
-
59
- If NO test project exists, create one:
60
-
61
- ```bash
62
- dotnet new xunit -n {SolutionName}.Tests -o tests/{SolutionName}.Tests
63
- dotnet sln add tests/{SolutionName}.Tests/{SolutionName}.Tests.csproj
64
- ```
65
-
66
- ### 2. Verify Required NuGet Packages
67
-
68
- The test project MUST have these packages:
69
-
70
- | Package | Purpose |
71
- |---------|---------|
72
- | `xunit` | Test framework |
73
- | `FluentAssertions` | Assertion library |
74
- | `Moq` | Mocking framework |
75
- | `Microsoft.AspNetCore.Mvc.Testing` | Integration test support |
76
- | `Microsoft.EntityFrameworkCore.Sqlite` | SQLite in-memory for REAL integration tests |
77
- | `Microsoft.Data.Sqlite` | SQLite connection support |
78
- | `Microsoft.EntityFrameworkCore.InMemory` | In-memory DB for repository tests |
79
- | `FluentValidation.TestHelper` | Validator testing support |
80
-
81
- ```bash
82
- cd tests/{SolutionName}.Tests
83
- dotnet add package FluentAssertions
84
- dotnet add package Moq
85
- dotnet add package Microsoft.AspNetCore.Mvc.Testing
86
- dotnet add package Microsoft.EntityFrameworkCore.Sqlite
87
- dotnet add package Microsoft.Data.Sqlite
88
- dotnet add package Microsoft.EntityFrameworkCore.InMemory
89
- dotnet add package FluentValidation.TestHelper
90
- ```
91
-
92
- ### 3. Add Project References
93
-
94
- The test project MUST reference the API project (for WebApplicationFactory):
95
-
96
- ```bash
97
- cd tests/{SolutionName}.Tests
98
- dotnet add reference ../../src/{SolutionName}.Api/{SolutionName}.Api.csproj
99
- dotnet add reference ../../src/{SolutionName}.Application/{SolutionName}.Application.csproj
100
- dotnet add reference ../../src/{SolutionName}.Domain/{SolutionName}.Domain.csproj
101
- dotnet add reference ../../src/{SolutionName}.Infrastructure/{SolutionName}.Infrastructure.csproj
102
- ```
103
-
104
- ### 4. Ensure Program.cs is Accessible (BLOCKING)
105
-
106
- The API project's `Program` class must be accessible for `WebApplicationFactory<Program>`.
107
-
108
- Check if `Program.cs` ends with `public partial class Program { }`. If not, add it:
109
-
110
- ```csharp
111
- // At the very end of Program.cs
112
- public partial class Program { }
113
- ```
114
-
115
- **Alternatively**, add to the API `.csproj`:
116
-
117
- ```xml
118
- <ItemGroup>
119
- <InternalsVisibleTo Include="{SolutionName}.Tests" />
120
- </ItemGroup>
121
- ```
50
+ See [references/test-prerequisites.md](../references/test-prerequisites.md) for the complete setup:
51
+ 1. Verify test project exists (create xunit project if missing)
52
+ 2. Install required NuGet packages (xunit, FluentAssertions, Moq, Mvc.Testing, EF Sqlite, etc.)
53
+ 3. Add project references (API, Application, Domain, Infrastructure)
54
+ 4. Ensure Program.cs is accessible for WebApplicationFactory (BLOCKING)
122
55
 
123
56
  ---
124
57
 
@@ -343,62 +276,13 @@ dotnet test tests/{SolutionName}.Tests/{SolutionName}.Tests.csproj --no-build --
343
276
 
344
277
  ## FRONTEND TESTS (React Components)
345
278
 
346
- ### 7. Check Frontend Test Infrastructure
279
+ See [references/test-frontend.md](../references/test-frontend.md) for the complete frontend test setup:
347
280
 
348
- Verify that the frontend project has Vitest + Testing Library configured:
349
-
350
- ```bash
351
- # Check if vitest.config.ts exists in the web/frontend project
352
- ls {WebProjectPath}/vitest.config.ts 2>/dev/null
353
- ```
354
-
355
- If NOT found, deploy the test infrastructure:
356
-
357
- 1. **Install packages:**
358
-
359
- ```bash
360
- cd {WebProjectPath}
361
- npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom msw
362
- ```
363
-
364
- 2. **Copy infrastructure files** from SmartStack templates:
365
-
366
- | Template Source | Destination |
367
- |----------------|-------------|
368
- | `templates/project/test-frontend/vitest.config.ts` | `{WebProjectPath}/vitest.config.ts` |
369
- | `templates/project/test-frontend/setup.ts` | `{WebProjectPath}/src/test/setup.ts` |
370
- | `templates/project/test-frontend/test-utils.tsx` | `{WebProjectPath}/src/test/test-utils.tsx` |
371
- | `templates/project/test-frontend/msw/server.ts` | `{WebProjectPath}/src/test/msw/server.ts` |
372
- | `templates/project/test-frontend/msw/handlers.ts` | `{WebProjectPath}/src/test/msw/handlers.ts` |
373
-
374
- 3. **Add test script** to `package.json` (if missing):
375
-
376
- ```json
377
- {
378
- "scripts": {
379
- "test": "vitest run",
380
- "test:watch": "vitest",
381
- "test:coverage": "vitest run --coverage"
382
- }
383
- }
384
- ```
385
-
386
- ### 8. Generate Frontend Tests
387
-
388
- ```
389
- Tool: mcp__smartstack__scaffold_frontend_tests
390
- Args:
391
- name: "{entity_name}"
392
- components: ["all"]
393
- apiRoute: "/api/{entity_code}"
394
- ```
395
-
396
- This generates:
397
- - `src/pages/{context}/{application}/__tests__/{EntityName}Page.test.tsx` - Page rendering, loading, error states
398
- - `src/pages/{context}/{application}/__tests__/{EntityName}ListView.test.tsx` - List display, pagination, view toggle
399
- - `src/pages/{context}/{application}/__tests__/{EntityName}DetailPage.test.tsx` - Detail view, tabs, back navigation
400
- - `src/hooks/__tests__/use{EntityName}Preferences.test.ts` - Preference get/set
401
- - `src/services/api/__tests__/{entityName}Api.test.ts` - API client calls with MSW
281
+ ### 7-8. Infrastructure & Test Generation
282
+ - Check Vitest + Testing Library + MSW configuration
283
+ - Install packages if missing (`vitest`, `@testing-library/react`, `msw`, etc.)
284
+ - Copy infrastructure templates (vitest.config.ts, setup.ts, test-utils.tsx, MSW handlers)
285
+ - Generate tests via `mcp__smartstack__scaffold_frontend_tests` (Page, ListView, DetailPage, Hooks, API)
402
286
 
403
287
  ### 9. Run Frontend Tests (BLOCKING)
404
288
 
@@ -407,11 +291,7 @@ cd {WebProjectPath}
407
291
  npx vitest run --reporter=default
408
292
  ```
409
293
 
410
- **ALL frontend tests MUST pass.** If tests fail:
411
- 1. Read the failure output carefully
412
- 2. Fix the failing tests or the components they test
413
- 3. Re-run until all tests pass
414
- 4. Do NOT proceed to the next step until all tests are green
294
+ **ALL frontend tests MUST pass.** Fix failures before proceeding.
415
295
 
416
296
  ---
417
297
 
@@ -35,12 +35,16 @@ The skill auto-detects which use case applies by scanning existing features in `
35
35
 
36
36
  <parameters>
37
37
 
38
- No flags. The entire input is the `{feature_description}`.
38
+ | Flag | Description |
39
+ |------|-------------|
40
+ | (aucun) | Mode standard — analyse complete ou mise a jour |
41
+ | `-review` | Mode review — lit `ba-review.json`, cree une nouvelle version avec les corrections appliquees |
39
42
 
40
- Step-00 handles all detection automatically:
43
+ Step-00 handles detection automatically:
41
44
  - **New vs Update**: scans `docs/business/` for existing applications
42
45
  - **Single vs Multi-module**: determined during step-02 decomposition
43
46
  - **Language**: detected from config or asked once
47
+ - **Review mode**: if `-review` flag, routes directly to step-06
44
48
 
45
49
  </parameters>
46
50
 
@@ -108,6 +112,10 @@ When step-00 detects that the description matches an existing application:
108
112
  - **Step 00:** Detection, locate existing feature, create version N+1
109
113
  - **Step 01:** Cadrage delta: what changes, impact on existing
110
114
  - **Step 02-05:** Same flow as new, with existing context loaded
115
+
116
+ **Review workflow (`-review` flag):**
117
+ - **Step 00:** Detection, scan for latest application, locate `ba-review.json`
118
+ - **Step 06:** Apply corrections, create new version, reverse-map data, regenerate all artifacts
111
119
  </workflow>
112
120
 
113
121
  <state_variables>
@@ -149,6 +157,7 @@ When step-00 detects that the description matches an existing application:
149
157
  | 04 | `steps/step-04-consolidation.md` | Opus | Cross-module validation, E2E flows, permissions coherence |
150
158
  | 05a | `steps/step-05a-handoff.md` | Sonnet | Handoff: file mapping (7 categories), BR-to-code mapping, API summary, write handoff |
151
159
  | 05b | `steps/step-05b-deploy.md` | Sonnet | Deploy: prd.json, progress.txt, manifest, ba-interactive.html, auto-launch ralph-loop |
160
+ | 06 | `steps/step-06-review.md` | Opus | Apply review corrections, create new version, regenerate all artifacts |
152
161
 
153
162
  </step_files>
154
163
 
@@ -1380,6 +1380,18 @@ body {
1380
1380
  font-size: 0.65rem;
1381
1381
  }
1382
1382
 
1383
+ /* Review save button */
1384
+ .btn-review {
1385
+ background: var(--success);
1386
+ color: #fff;
1387
+ border-color: var(--success);
1388
+ font-weight: 600;
1389
+ }
1390
+ .btn-review:hover {
1391
+ background: #16a34a;
1392
+ border-color: #16a34a;
1393
+ }
1394
+
1383
1395
  @media (max-width: 768px) {
1384
1396
  .body.review-open {
1385
1397
  grid-template-columns: 1fr;
@@ -1408,6 +1420,7 @@ body {
1408
1420
  <div class="header-spacer"></div>
1409
1421
  <div class="header-actions">
1410
1422
  <button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
1423
+ <button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
1411
1424
  <button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
1412
1425
  <button class="btn btn-sm review-toggle-btn" id="reviewToggleBtn" onclick="toggleReviewPanel()" title="Ouvrir/fermer le panneau de review">
1413
1426
  Review
@@ -2009,6 +2022,7 @@ body {
2009
2022
  ============================================ */
2010
2023
  const APP_KEY = 'ba-{{APPLICATION_ID}}';
2011
2024
  let data = {{FEATURE_DATA}};
2025
+ const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
2012
2026
  const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
2013
2027
 
2014
2028
  // Initialize optional data structures
@@ -2605,6 +2619,8 @@ function renderAllModuleSpecs() {
2605
2619
  });
2606
2620
  // Restore currently visible section after re-render
2607
2621
  restoreCurrentSection();
2622
+ // Re-initialize inline comment buttons on module spec cards
2623
+ if (typeof initInlineComments === 'function') initInlineComments();
2608
2624
  }
2609
2625
 
2610
2626
  function renderModuleSpecSection(mod) {
@@ -3007,6 +3023,17 @@ function renderModuleMockups(code) {
3007
3023
  }
3008
3024
 
3009
3025
  function getPermRoles() {
3026
+ // Extract roles from actual permission data (handles English role names from feature.json)
3027
+ const rolesFromPerms = [];
3028
+ const seen = new Set();
3029
+ data.modules.forEach(m => {
3030
+ (data.moduleSpecs[m.code]?.permissions || []).forEach(p => {
3031
+ const role = p.split('|')[0];
3032
+ if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
3033
+ });
3034
+ });
3035
+ if (rolesFromPerms.length > 0) return [...rolesFromPerms, ...(data.customRoles || [])];
3036
+ // Fallback: use stakeholder names if no permission data exists
3010
3037
  const baseRoles = data.cadrage.stakeholders.length > 0
3011
3038
  ? data.cadrage.stakeholders.map(s => s.role)
3012
3039
  : ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
@@ -3021,9 +3048,7 @@ function getPermActions() {
3021
3048
  function renderPermissionGrid(code) {
3022
3049
  const roles = getPermRoles();
3023
3050
  const actions = getPermActions();
3024
- const baseRolesCount = data.cadrage.stakeholders.length > 0
3025
- ? data.cadrage.stakeholders.length
3026
- : 4;
3051
+ const baseRolesCount = roles.length - (data.customRoles || []).length;
3027
3052
  const baseActionsCount = 6;
3028
3053
 
3029
3054
  const perms = data.moduleSpecs[code]?.permissions || [];
@@ -3040,7 +3065,8 @@ function renderPermissionGrid(code) {
3040
3065
  <td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce role">&#10005;</span>' : ''}</td>
3041
3066
  ${actions.map(action => {
3042
3067
  const key = role + '|' + action;
3043
- const checked = perms.includes(key);
3068
+ const wildcardKey = role + '|*';
3069
+ const checked = perms.includes(key) || perms.includes(wildcardKey);
3044
3070
  return `<td style="text-align:center;"><input type="checkbox" ${checked ? 'checked' : ''} onchange="togglePermission('${code}','${key}',this.checked)" style="cursor:pointer;width:16px;height:16px;"></td>`;
3045
3071
  }).join('')}
3046
3072
  </tr>
@@ -3171,10 +3197,15 @@ function renderConsolPermissions() {
3171
3197
  roles.forEach(role => {
3172
3198
  html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
3173
3199
  data.modules.forEach(m => {
3174
- const perms = (data.moduleSpecs[m.code]?.permissions || []).filter(p => p.startsWith(role + '|'));
3200
+ const allPerms = data.moduleSpecs[m.code]?.permissions || [];
3201
+ const hasWildcard = allPerms.includes(role + '|*');
3202
+ const perms = hasWildcard
3203
+ ? getPermActions()
3204
+ : allPerms.filter(p => p.startsWith(role + '|'));
3175
3205
  const count = perms.length;
3176
3206
  const color = count > 4 ? 'var(--success)' : count > 2 ? 'var(--warning)' : count > 0 ? 'var(--text-muted)' : 'var(--border)';
3177
- html += `<td style="text-align:center;color:${color};font-weight:600;">${count > 0 ? count + ' droits' : 'Aucun'}</td>`;
3207
+ const label = hasWildcard ? 'Tous' : (count > 0 ? count + ' droits' : 'Aucun');
3208
+ html += `<td style="text-align:center;color:${color};font-weight:600;">${label}</td>`;
3178
3209
  });
3179
3210
  html += '</tr>';
3180
3211
  });
@@ -3446,6 +3477,109 @@ function exportJSON() {
3446
3477
  showNotification('Export JSON telecharge');
3447
3478
  }
3448
3479
 
3480
+ /* ============================================
3481
+ SAVE REVIEW JSON (corrections for new version)
3482
+ ============================================ */
3483
+ function detectChanges(original, current) {
3484
+ const changes = { cadrage: false, modulesAdded: [], modulesRemoved: [], modulesModified: [], commentsCount: 0 };
3485
+
3486
+ // Cadrage changes
3487
+ if (JSON.stringify(original.cadrage) !== JSON.stringify(current.cadrage)) {
3488
+ changes.cadrage = true;
3489
+ }
3490
+
3491
+ // Module changes
3492
+ const origCodes = (original.modules || []).map(m => m.code);
3493
+ const currCodes = (current.modules || []).map(m => m.code);
3494
+ changes.modulesAdded = currCodes.filter(c => !origCodes.includes(c));
3495
+ changes.modulesRemoved = origCodes.filter(c => !currCodes.includes(c));
3496
+ currCodes.filter(c => origCodes.includes(c)).forEach(code => {
3497
+ const origSpec = JSON.stringify(original.moduleSpecs?.[code] || {});
3498
+ const currSpec = JSON.stringify(current.moduleSpecs?.[code] || {});
3499
+ if (origSpec !== currSpec) changes.modulesModified.push(code);
3500
+ });
3501
+
3502
+ // Comments count
3503
+ changes.commentsCount = (current.comments || []).length;
3504
+
3505
+ return changes;
3506
+ }
3507
+
3508
+ function saveReviewJSON() {
3509
+ // Collect all editable fields (same as exportJSON)
3510
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
3511
+ setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
3512
+ });
3513
+ document.querySelectorAll('.editable[data-module-field]').forEach(el => {
3514
+ const code = el.dataset.moduleCode;
3515
+ const field = el.dataset.moduleField;
3516
+ if (data.moduleSpecs[code]) {
3517
+ data.moduleSpecs[code][field] = el.textContent.trim();
3518
+ }
3519
+ });
3520
+
3521
+ const changes = detectChanges(ORIGINAL_DATA, data);
3522
+ const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
3523
+ || changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
3524
+ || changes.commentsCount > 0;
3525
+
3526
+ // Build review export with metadata envelope
3527
+ const reviewData = {
3528
+ _reviewMeta: {
3529
+ sourceVersion: data.metadata.version || '1.0',
3530
+ sourceApplicationId: data.metadata.applicationId || '',
3531
+ exportedAt: new Date().toISOString(),
3532
+ hasChanges: hasChanges,
3533
+ changeSummary: {
3534
+ cadrageModified: changes.cadrage,
3535
+ modulesAdded: changes.modulesAdded,
3536
+ modulesRemoved: changes.modulesRemoved,
3537
+ modulesModified: changes.modulesModified,
3538
+ commentsCount: changes.commentsCount
3539
+ }
3540
+ },
3541
+ metadata: data.metadata,
3542
+ cadrage: data.cadrage,
3543
+ modules: data.modules,
3544
+ dependencies: data.dependencies,
3545
+ moduleSpecifications: {},
3546
+ consolidation: data.consolidation,
3547
+ wireframeComments: data.wireframeComments,
3548
+ specComments: data.specComments,
3549
+ customRoles: data.customRoles,
3550
+ customActions: data.customActions,
3551
+ comments: data.comments
3552
+ };
3553
+
3554
+ // Structure module specs (same logic as exportJSON)
3555
+ data.modules.forEach(m => {
3556
+ const spec = data.moduleSpecs[m.code] || {};
3557
+ reviewData.moduleSpecifications[m.code] = {
3558
+ module: m,
3559
+ useCases: (spec.useCases || []).map((uc, i) => ({
3560
+ id: 'UC-' + String(i + 1).padStart(3, '0'),
3561
+ ...uc
3562
+ })),
3563
+ businessRules: (spec.businessRules || []).map((br, i) => ({
3564
+ id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
3565
+ ...br
3566
+ })),
3567
+ entities: spec.entities || [],
3568
+ permissions: spec.permissions || [],
3569
+ notes: spec.notes || ''
3570
+ };
3571
+ });
3572
+
3573
+ const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
3574
+ const url = URL.createObjectURL(blob);
3575
+ const a = document.createElement('a');
3576
+ a.href = url;
3577
+ a.download = 'ba-review.json';
3578
+ a.click();
3579
+ URL.revokeObjectURL(url);
3580
+ showNotification('ba-review.json telecharge — sauvegardez-le a cote du HTML');
3581
+ }
3582
+
3449
3583
 
3450
3584
  /* --- 10-comments.js --- */
3451
3585
  /* ============================================
@@ -3463,14 +3597,20 @@ function exportJSON() {
3463
3597
  */
3464
3598
 
3465
3599
  function initInlineComments() {
3466
- // Add comment buttons under each card and uc-item
3600
+ // Add comment buttons under each card and uc-item (direct children of sections)
3467
3601
  document.querySelectorAll('.section').forEach(section => {
3468
3602
  const sectionId = section.id;
3469
- const cards = section.querySelectorAll(':scope > .card, :scope > .uc-item');
3470
- cards.forEach((card, index) => {
3471
- if (card.querySelector('.comment-btn-container')) return; // already initialized
3472
- const container = createCommentUI(sectionId, index);
3473
- card.appendChild(container);
3603
+ section.querySelectorAll(':scope > .card, :scope > .uc-item').forEach((card, index) => {
3604
+ if (card.querySelector('.comment-btn-container')) return;
3605
+ card.appendChild(createCommentUI(sectionId, index));
3606
+ });
3607
+ });
3608
+ // Second pass: nested list containers in module specs (ucList-*, brList-*, entList-*)
3609
+ document.querySelectorAll('[id^="ucList-"], [id^="brList-"], [id^="entList-"]').forEach(list => {
3610
+ const listId = list.id;
3611
+ list.querySelectorAll(':scope > .uc-item, :scope > .entity-block, :scope > div').forEach((item, index) => {
3612
+ if (item.querySelector('.comment-btn-container')) return;
3613
+ item.appendChild(createCommentUI(listId, index));
3474
3614
  });
3475
3615
  });
3476
3616
  }
@@ -3738,12 +3878,34 @@ function getSectionLabel(sectionId) {
3738
3878
  const mod = data.modules.find(m => m.code === code);
3739
3879
  return mod ? mod.name : code;
3740
3880
  }
3881
+ // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
3882
+ const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
3883
+ if (listMatch) {
3884
+ const tabLabels = { uc: 'Cas d\'utilisation', br: 'Regles metier', ent: 'Donnees' };
3885
+ const mod = data.modules.find(m => m.code === listMatch[2]);
3886
+ const modName = mod ? mod.name : listMatch[2];
3887
+ return modName + ' > ' + (tabLabels[listMatch[1]] || listMatch[1]);
3888
+ }
3741
3889
  return sectionId;
3742
3890
  }
3743
3891
 
3744
3892
  function navigateToComment(sectionId, cardIndex) {
3745
- showSection(sectionId);
3746
- // Scroll to the card and open its comment thread
3893
+ // Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
3894
+ const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
3895
+ if (listMatch) {
3896
+ const [, tabType, moduleCode] = listMatch;
3897
+ showSection('module-spec-' + moduleCode);
3898
+ setTimeout(() => {
3899
+ switchTab(moduleCode, tabType);
3900
+ scrollToCommentThread(sectionId, cardIndex);
3901
+ }, 150);
3902
+ } else {
3903
+ showSection(sectionId);
3904
+ scrollToCommentThread(sectionId, cardIndex);
3905
+ }
3906
+ }
3907
+
3908
+ function scrollToCommentThread(sectionId, cardIndex) {
3747
3909
  setTimeout(() => {
3748
3910
  const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
3749
3911
  if (thread && !thread.classList.contains('visible')) {
@@ -3,6 +3,7 @@
3
3
  ============================================ */
4
4
  const APP_KEY = 'ba-{{APPLICATION_ID}}';
5
5
  let data = {{FEATURE_DATA}};
6
+ const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
6
7
  const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
7
8
 
8
9
  // Initialize optional data structures
@@ -17,6 +17,8 @@ function renderAllModuleSpecs() {
17
17
  });
18
18
  // Restore currently visible section after re-render
19
19
  restoreCurrentSection();
20
+ // Re-initialize inline comment buttons on module spec cards
21
+ if (typeof initInlineComments === 'function') initInlineComments();
20
22
  }
21
23
 
22
24
  function renderModuleSpecSection(mod) {
@@ -419,6 +421,17 @@ function renderModuleMockups(code) {
419
421
  }
420
422
 
421
423
  function getPermRoles() {
424
+ // Extract roles from actual permission data (handles English role names from feature.json)
425
+ const rolesFromPerms = [];
426
+ const seen = new Set();
427
+ data.modules.forEach(m => {
428
+ (data.moduleSpecs[m.code]?.permissions || []).forEach(p => {
429
+ const role = p.split('|')[0];
430
+ if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
431
+ });
432
+ });
433
+ if (rolesFromPerms.length > 0) return [...rolesFromPerms, ...(data.customRoles || [])];
434
+ // Fallback: use stakeholder names if no permission data exists
422
435
  const baseRoles = data.cadrage.stakeholders.length > 0
423
436
  ? data.cadrage.stakeholders.map(s => s.role)
424
437
  : ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
@@ -433,9 +446,7 @@ function getPermActions() {
433
446
  function renderPermissionGrid(code) {
434
447
  const roles = getPermRoles();
435
448
  const actions = getPermActions();
436
- const baseRolesCount = data.cadrage.stakeholders.length > 0
437
- ? data.cadrage.stakeholders.length
438
- : 4;
449
+ const baseRolesCount = roles.length - (data.customRoles || []).length;
439
450
  const baseActionsCount = 6;
440
451
 
441
452
  const perms = data.moduleSpecs[code]?.permissions || [];
@@ -452,7 +463,8 @@ function renderPermissionGrid(code) {
452
463
  <td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce role">&#10005;</span>' : ''}</td>
453
464
  ${actions.map(action => {
454
465
  const key = role + '|' + action;
455
- const checked = perms.includes(key);
466
+ const wildcardKey = role + '|*';
467
+ const checked = perms.includes(key) || perms.includes(wildcardKey);
456
468
  return `<td style="text-align:center;"><input type="checkbox" ${checked ? 'checked' : ''} onchange="togglePermission('${code}','${key}',this.checked)" style="cursor:pointer;width:16px;height:16px;"></td>`;
457
469
  }).join('')}
458
470
  </tr>
@@ -38,10 +38,15 @@ function renderConsolPermissions() {
38
38
  roles.forEach(role => {
39
39
  html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
40
40
  data.modules.forEach(m => {
41
- const perms = (data.moduleSpecs[m.code]?.permissions || []).filter(p => p.startsWith(role + '|'));
41
+ const allPerms = data.moduleSpecs[m.code]?.permissions || [];
42
+ const hasWildcard = allPerms.includes(role + '|*');
43
+ const perms = hasWildcard
44
+ ? getPermActions()
45
+ : allPerms.filter(p => p.startsWith(role + '|'));
42
46
  const count = perms.length;
43
47
  const color = count > 4 ? 'var(--success)' : count > 2 ? 'var(--warning)' : count > 0 ? 'var(--text-muted)' : 'var(--border)';
44
- html += `<td style="text-align:center;color:${color};font-weight:600;">${count > 0 ? count + ' droits' : 'Aucun'}</td>`;
48
+ const label = hasWildcard ? 'Tous' : (count > 0 ? count + ' droits' : 'Aucun');
49
+ html += `<td style="text-align:center;color:${color};font-weight:600;">${label}</td>`;
45
50
  });
46
51
  html += '</tr>';
47
52
  });
@@ -63,3 +63,106 @@ function exportJSON() {
63
63
  URL.revokeObjectURL(url);
64
64
  showNotification('Export JSON telecharge');
65
65
  }
66
+
67
+ /* ============================================
68
+ SAVE REVIEW JSON (corrections for new version)
69
+ ============================================ */
70
+ function detectChanges(original, current) {
71
+ const changes = { cadrage: false, modulesAdded: [], modulesRemoved: [], modulesModified: [], commentsCount: 0 };
72
+
73
+ // Cadrage changes
74
+ if (JSON.stringify(original.cadrage) !== JSON.stringify(current.cadrage)) {
75
+ changes.cadrage = true;
76
+ }
77
+
78
+ // Module changes
79
+ const origCodes = (original.modules || []).map(m => m.code);
80
+ const currCodes = (current.modules || []).map(m => m.code);
81
+ changes.modulesAdded = currCodes.filter(c => !origCodes.includes(c));
82
+ changes.modulesRemoved = origCodes.filter(c => !currCodes.includes(c));
83
+ currCodes.filter(c => origCodes.includes(c)).forEach(code => {
84
+ const origSpec = JSON.stringify(original.moduleSpecs?.[code] || {});
85
+ const currSpec = JSON.stringify(current.moduleSpecs?.[code] || {});
86
+ if (origSpec !== currSpec) changes.modulesModified.push(code);
87
+ });
88
+
89
+ // Comments count
90
+ changes.commentsCount = (current.comments || []).length;
91
+
92
+ return changes;
93
+ }
94
+
95
+ function saveReviewJSON() {
96
+ // Collect all editable fields (same as exportJSON)
97
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
98
+ setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
99
+ });
100
+ document.querySelectorAll('.editable[data-module-field]').forEach(el => {
101
+ const code = el.dataset.moduleCode;
102
+ const field = el.dataset.moduleField;
103
+ if (data.moduleSpecs[code]) {
104
+ data.moduleSpecs[code][field] = el.textContent.trim();
105
+ }
106
+ });
107
+
108
+ const changes = detectChanges(ORIGINAL_DATA, data);
109
+ const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
110
+ || changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
111
+ || changes.commentsCount > 0;
112
+
113
+ // Build review export with metadata envelope
114
+ const reviewData = {
115
+ _reviewMeta: {
116
+ sourceVersion: data.metadata.version || '1.0',
117
+ sourceApplicationId: data.metadata.applicationId || '',
118
+ exportedAt: new Date().toISOString(),
119
+ hasChanges: hasChanges,
120
+ changeSummary: {
121
+ cadrageModified: changes.cadrage,
122
+ modulesAdded: changes.modulesAdded,
123
+ modulesRemoved: changes.modulesRemoved,
124
+ modulesModified: changes.modulesModified,
125
+ commentsCount: changes.commentsCount
126
+ }
127
+ },
128
+ metadata: data.metadata,
129
+ cadrage: data.cadrage,
130
+ modules: data.modules,
131
+ dependencies: data.dependencies,
132
+ moduleSpecifications: {},
133
+ consolidation: data.consolidation,
134
+ wireframeComments: data.wireframeComments,
135
+ specComments: data.specComments,
136
+ customRoles: data.customRoles,
137
+ customActions: data.customActions,
138
+ comments: data.comments
139
+ };
140
+
141
+ // Structure module specs (same logic as exportJSON)
142
+ data.modules.forEach(m => {
143
+ const spec = data.moduleSpecs[m.code] || {};
144
+ reviewData.moduleSpecifications[m.code] = {
145
+ module: m,
146
+ useCases: (spec.useCases || []).map((uc, i) => ({
147
+ id: 'UC-' + String(i + 1).padStart(3, '0'),
148
+ ...uc
149
+ })),
150
+ businessRules: (spec.businessRules || []).map((br, i) => ({
151
+ id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
152
+ ...br
153
+ })),
154
+ entities: spec.entities || [],
155
+ permissions: spec.permissions || [],
156
+ notes: spec.notes || ''
157
+ };
158
+ });
159
+
160
+ const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
161
+ const url = URL.createObjectURL(blob);
162
+ const a = document.createElement('a');
163
+ a.href = url;
164
+ a.download = 'ba-review.json';
165
+ a.click();
166
+ URL.revokeObjectURL(url);
167
+ showNotification('ba-review.json telecharge — sauvegardez-le a cote du HTML');
168
+ }