@atlashub/smartstack-cli 4.51.0 → 4.52.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 (25) hide show
  1. package/dist/index.js +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/skills/apex/references/core-seed-data.md +15 -0
  5. package/templates/skills/apex/references/error-classification.md +27 -3
  6. package/templates/skills/apex/references/post-checks.md +3 -1
  7. package/templates/skills/apex/steps/step-00-init.md +57 -0
  8. package/templates/skills/apex/steps/step-03-execute.md +33 -5
  9. package/templates/skills/apex/steps/step-03b-layer1-seed.md +18 -0
  10. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +3 -0
  11. package/templates/skills/apex/steps/step-04-examine.md +35 -0
  12. package/templates/skills/business-analyse-develop/references/init-resume-recovery.md +54 -0
  13. package/templates/skills/business-analyse-develop/steps/step-00-init.md +10 -3
  14. package/templates/skills/business-analyse-develop/steps/step-01-task.md +14 -2
  15. package/templates/skills/business-analyse-develop/steps/step-04-check.md +12 -2
  16. package/templates/skills/business-analyse-handoff/references/entity-canonicalization.md +158 -0
  17. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +14 -0
  18. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +14 -0
  19. package/templates/skills/business-analyse-html/SKILL.md +4 -0
  20. package/templates/skills/business-analyse-html/references/data-build.md +24 -17
  21. package/templates/skills/business-analyse-html/references/data-mapping.md +79 -35
  22. package/templates/skills/business-analyse-html/references/output-modes.md +2 -1
  23. package/templates/skills/business-analyse-html/steps/step-01-collect.md +7 -2
  24. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +155 -40
  25. package/templates/skills/business-analyse-html/steps/step-04-verify.md +22 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.51.0",
3
+ "version": "4.52.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -1003,6 +1003,21 @@ public class RolePermissionSeedEntry
1003
1003
 
1004
1004
  ## 6. IClientSeedDataProvider Implementation
1005
1005
 
1006
+ > **IMPORTANT — HasData vs Seed*Async: TWO DIFFERENT MECHANISMS**
1007
+ >
1008
+ > | Mechanism | Where | When | Purpose |
1009
+ > |-----------|-------|------|---------|
1010
+ > | **EF Core `HasData()`** | `*Configuration.cs` (Infrastructure) | At migration time | Seeds static reference data into tables via `INSERT` in migration SQL. Data is part of the migration — no runtime code needed. |
1011
+ > | **`IClientSeedDataProvider.Seed*Async()`** | `*SeedDataProvider.cs` (Infrastructure) | At runtime startup | Seeds navigation, roles, permissions, role-permissions into the **Core** schema. Runs AFTER `MigrateAsync()` in `Program.cs`. |
1012
+ >
1013
+ > **Common mistake:** Generating `HasData()` calls for navigation/permissions instead of `Seed*Async()` methods.
1014
+ > Navigation and permission data goes into the **Core** schema (`core.nav_*`, `core.auth_*`) which is managed
1015
+ > by SmartStack, not by the client's migration. Only `IClientSeedDataProvider` can write to Core tables at runtime.
1016
+ >
1017
+ > **ANTI-STUB WARNING:** Each of the 4 Seed methods MUST contain real `context.{DbSet}.Add()` calls.
1018
+ > If ANY method is just `return Task.CompletedTask;`, the module will be **invisible** in the UI (empty menu).
1019
+ > This happens when this reference file is evicted from context by compression — see step-03b safeguard.
1020
+
1006
1021
  **File:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
1007
1022
 
1008
1023
  ### Critical Rules
@@ -100,6 +100,28 @@
100
100
 
101
101
  ---
102
102
 
103
+ ### Category G: PRD Quality Error (FIX = fix PRD or re-run handoff)
104
+
105
+ > **These errors are NOT caused by code generation — they originate from invalid PRD data.**
106
+ > The handoff generated file paths with illegal characters (spaces, apostrophes, accents in entity names).
107
+ > Code generation faithfully reproduced the invalid paths → build failure.
108
+
109
+ | Error Pattern | Example | Fix |
110
+ |--------------|---------|-----|
111
+ | `error CS1001: Identifier expected` with file name containing spaces | `Type d'absence.cs` → CS1001 | Re-run `/business-analyse-handoff` with entity canonicalization |
112
+ | `error CS1513/CS1514` with file name containing apostrophes | `Congé.cs` → accent in identifier | Fix entity name in BA or re-run handoff |
113
+ | Path contains non-ASCII characters in `.cs` file names | `Département.cs` | Canonicalize: strip diacritics, PascalCase |
114
+
115
+ **Detection heuristic:** If the error file path contains spaces, apostrophes, or accented characters, it is a PRD quality error, not a code error. Do NOT attempt to fix the generated code — fix the PRD source.
116
+
117
+ **Fix procedure:**
118
+ 1. Identify the invalid entity name from the error file path
119
+ 2. Report to user: "PRD quality error — entity name '{name}' is not a valid C# identifier"
120
+ 3. Either: manually canonicalize in the PRD file, or re-run `/business-analyse-handoff` (which now includes canonicalization)
121
+ 4. Re-run `/apex -d` with the corrected PRD
122
+
123
+ ---
124
+
103
125
  ## Decision Tree
104
126
 
105
127
  When a build or runtime error occurs, follow this tree:
@@ -123,9 +145,11 @@ BUILD/RUNTIME ERROR
123
145
  | YES -> Category E (edit config files)
124
146
  |
125
147
  +-- error CS#### (C# compiler error) ?
126
- | YES -> Is the missing type in ANY project file?
127
- | NO -> Category A (missing package)
128
- | YES -> Category F (fix source code)
148
+ | YES -> Does the error file path contain spaces, apostrophes, or accents?
149
+ | YES -> Category G (PRD quality error — fix handoff, not code)
150
+ | NO -> Is the missing type in ANY project file?
151
+ | NO -> Category A (missing package)
152
+ | YES -> Category F (fix source code)
129
153
  |
130
154
  +-- Test failure (Assert/Expected) ?
131
155
  YES -> Category F (fix source code, NOT tests)
@@ -90,7 +90,7 @@ bash references/checks/infrastructure-checks.sh
90
90
  | C20 | WARNING | Section route completeness (NavigationSection → frontend route + permissions) | seed-checks.sh |
91
91
  | C21 | WARNING | FORBIDDEN route patterns — /list and /detail/:id | seed-checks.sh |
92
92
  | C22 | WARNING | Permission path segment count (2-4 dots expected) | seed-checks.sh |
93
- | C23 | BLOCKING | IClientSeedDataProvider must have 4 methods + DI registration | seed-checks.sh |
93
+ | C23 | BLOCKING | IClientSeedDataProvider must have 4 methods with real implementation (not stubs) + DI registration | seed-checks.sh |
94
94
  | C32 | CRITICAL | Translation seed data must have idempotency guard | seed-checks.sh |
95
95
  | C33 | CRITICAL | Resource seed data must use actual section IDs from DB | seed-checks.sh |
96
96
  | C34 | BLOCKING | NavRoute segments must use kebab-case for multi-word codes — MCP overlap | seed-checks.sh |
@@ -101,6 +101,8 @@ bash references/checks/infrastructure-checks.sh
101
101
  | C47 | WARNING | Person Extension entities must not duplicate User fields | seed-checks.sh |
102
102
  | C48 | CRITICAL | Person Extension service must Include(User) | seed-checks.sh |
103
103
  | C53 | BLOCKING | Enum serialization — JsonStringEnumConverter required | seed-checks.sh |
104
+ | C57 | BLOCKING | SeedDataProvider Seed*Async methods must NOT be `return Task.CompletedTask` or empty body — stubs cause empty menu | seed-checks.sh |
105
+ | C58 | BLOCKING | File paths in filesToCreate must be valid C# identifiers — no spaces, apostrophes, or accents | seed-checks.sh |
104
106
 
105
107
  ### Architecture — Clean Architecture Layer Isolation (A1-A8)
106
108
 
@@ -94,6 +94,46 @@ if (prd.specificationFiles) {
94
94
 
95
95
  Each layer step (step-03a through step-03d) will load its companion files at the start. This provides full BA specifications (entity attributes, BR formulas, UC steps, screen columns) without loading the entire spec corpus.
96
96
 
97
+ **PRD Quality Gate (delegate mode — BLOCKING):**
98
+
99
+ > Validate that PRD file paths are valid C# identifiers BEFORE starting execution.
100
+ > Invalid entity names (French with spaces/apostrophes/accents) cause CS1001 build errors
101
+ > that waste 3+ retry iterations before being correctly classified.
102
+
103
+ ```javascript
104
+ // Extract all file paths from PRD
105
+ const filesToCreate = prd.$version === '4.0.0' ? prd.expectedFiles : prd.implementation?.filesToCreate;
106
+ if (filesToCreate) {
107
+ const invalidPaths = [];
108
+ for (const [category, files] of Object.entries(filesToCreate)) {
109
+ for (const file of (files || [])) {
110
+ const filePath = file.path || file;
111
+ // Extract filename (without extension) from path
112
+ const fileName = filePath.split('/').pop().replace(/\.(cs|tsx|ts|json)$/, '');
113
+ // Check for illegal characters in C# identifiers
114
+ if (/[\s'àâäéèêëïîôùûüçÀÂÄÉÈÊËÏÎÔÙÛÜÇ]/.test(fileName)) {
115
+ invalidPaths.push({ category, path: filePath, fileName });
116
+ }
117
+ }
118
+ }
119
+
120
+ if (invalidPaths.length > 0) {
121
+ console.error('BLOCKING — PRD QUALITY ERROR (not a code error):');
122
+ console.error('The following file paths contain illegal C# identifier characters:');
123
+ for (const p of invalidPaths) {
124
+ console.error(` ${p.category}: ${p.path} (invalid name: "${p.fileName}")`);
125
+ }
126
+ console.error('');
127
+ console.error('FIX: Re-run /business-analyse-handoff which now includes entity name canonicalization.');
128
+ console.error(' Or manually fix entity names in the PRD (strip accents, remove spaces/apostrophes, PascalCase).');
129
+ console.error('');
130
+ console.error('This is a PRD quality error (Category G), NOT a code generation error.');
131
+ console.error('Do NOT attempt to generate code from this PRD — it will fail with CS1001.');
132
+ STOP; // Do not proceed with invalid PRD
133
+ }
134
+ }
135
+ ```
136
+
97
137
  **Jump to:** section 3 (MCP verify) → section 6 (determine needs) → section 9 (summary)
98
138
 
99
139
  ---
@@ -248,6 +288,23 @@ needs_notification = {module_complexity} in ["crud-workflow","complex"] OR menti
248
288
 
249
289
  Read latest task directory in `.claude/output/apex/`:
250
290
  - **If state.json exists:** resume step-03 at next uncompleted layer (skip completed)
291
+ - **Delegate context recovery:** If `state.delegate_mode === true`:
292
+ 1. Restore `{delegate_prd_path}` from `state.delegate_prd_path`
293
+ 2. Restore `{app_name}`, `{module_code}`, `{sections}`, `{entities}` from state
294
+ 3. Re-read PRD at `{delegate_prd_path}` to rebuild `{specification_loading_plan}`:
295
+ ```javascript
296
+ const prd = readJSON(state.delegate_prd_path);
297
+ if (prd.specificationFiles) {
298
+ const sf = prd.specificationFiles;
299
+ specification_loading_plan = {
300
+ layer0_domain: [sf.entities],
301
+ layer1_seed: [sf.entities, sf.permissions],
302
+ layer2_backend: [sf.rules, sf.usecases],
303
+ layer3_frontend: [sf.screens, sf.usecases]
304
+ };
305
+ }
306
+ ```
307
+ 4. Set `{delegate_mode} = true` — companion spec loading will work in remaining layers
251
308
  - **Else if 00-context.md exists:** restore step-00 state, re-derive post-step-00 from git + files
252
309
  - **Else:** full re-derive from git history + files
253
310
 
@@ -46,19 +46,35 @@ Execute all layers (Layer 0 → Layer 1 → Layer 2 → Layer 3 → Layer 4).
46
46
  BEFORE starting Layer N:
47
47
  Verify these variables are still accessible:
48
48
  {app_name}, {module_code}, {sections}, {entities}, {code_patterns}
49
+ IF delegate_mode: also verify {delegate_prd_path}, {specification_loading_plan}
49
50
 
50
51
  IF any variable is missing or empty:
51
52
  1. Read .claude/output/apex/{task_id}/state.json (if exists)
52
- 2. IF state.json missing re-derive from filesystem:
53
+ 2. IF state.json has delegate context:
54
+ - {delegate_prd_path} = state.delegate_prd_path
55
+ - {delegate_mode} = state.delegate_mode
56
+ - Re-read PRD at {delegate_prd_path} to rebuild specification_loading_plan:
57
+ const prd = readJSON(delegate_prd_path);
58
+ if (prd.specificationFiles) {
59
+ specification_loading_plan = {
60
+ layer0_domain: [prd.specificationFiles.entities],
61
+ layer1_seed: [prd.specificationFiles.entities, prd.specificationFiles.permissions],
62
+ layer2_backend: [prd.specificationFiles.rules, prd.specificationFiles.usecases],
63
+ layer3_frontend: [prd.specificationFiles.screens, prd.specificationFiles.usecases]
64
+ };
65
+ }
66
+ - {app_name} = state.app_name || prd.project?.application || prd.metadata?.applicationCode
67
+ - {module_code} = state.module_code || prd.project?.module || prd.metadata?.moduleCode
68
+ 3. IF state.json missing OR no delegate context → re-derive from filesystem:
53
69
  - {app_name}: Glob("docs/business/*/") → first directory name
54
70
  - {module_code}: Glob("src/**/Domain/Entities/*/") → target module directory
55
71
  - {entities}: Glob("src/**/Domain/Entities/{module_code}/*.cs") → entity names
56
72
  - {sections}: Glob("src/**/Seeding/Data/{module_code}/*NavigationSeedData.cs") → parse
57
73
  - {code_patterns}: Read state.json or re-derive from DependencyInjection.cs
58
- 3. IF recovered: verify consistency with naming derivation rules (step-00 §4f)
59
- 4. IF Layer N-1 already completed: skip to Layer N directly
74
+ 4. IF recovered: verify consistency with naming derivation rules (step-00 §4f)
75
+ 5. IF Layer N-1 already completed: skip to Layer N directly
60
76
 
61
- Cost: ~5 tool calls. Only triggered if context was compressed.
77
+ Cost: ~5-8 tool calls. Only triggered if context was compressed.
62
78
  ```
63
79
 
64
80
  ---
@@ -126,6 +142,7 @@ Layer 4: feat({module}): [devdata] test data for development # if applicable
126
142
  ## State Auto-Save (after each layer)
127
143
 
128
144
  > **Automatic** — not dependent on `-s` flag. Enables reliable resume after context loss.
145
+ > **CRITICAL (audit ba-003):** Delegate context MUST be persisted for resume to work in delegate mode.
129
146
 
130
147
  After each layer's build gate passes, write state to `.claude/output/apex/{task_id}/state.json`:
131
148
 
@@ -137,10 +154,21 @@ After each layer's build gate passes, write state to `.claude/output/apex/{task_
137
154
  "files_created": ["Employee.cs", "EmployeeConfiguration.cs", "..."],
138
155
  "build_gates": { "layer0": "pass", "layer1": "pass", "layer2": "pass" },
139
156
  "commits": ["abc1234", "def5678", "ghi9012"],
140
- "timestamp": "2026-03-06T14:30:00Z"
157
+ "timestamp": "2026-03-06T14:30:00Z",
158
+ "delegate_mode": true,
159
+ "delegate_prd_path": ".ralph/prd-employees.json",
160
+ "app_name": "HumanResources",
161
+ "module_code": "employee-management",
162
+ "sections": ["employees", "departments"],
163
+ "entities": ["Employee", "Department"]
141
164
  }
142
165
  ```
143
166
 
167
+ > **Fields `delegate_mode`, `delegate_prd_path`, `app_name`, `module_code`, `sections`, `entities`**
168
+ > are persisted so that Context Recovery Protocol (above) can restore the full execution context
169
+ > after context compression or resume (`-r`). The `specification_loading_plan` is rebuilt from the
170
+ > PRD file at `delegate_prd_path` — no need to serialize it directly.
171
+
144
172
  ---
145
173
 
146
174
  ## Save Output (if save_mode)
@@ -42,6 +42,24 @@ TaskUpdate(taskId: progress_tracker_id,
42
42
 
43
43
  > This layer is required. Seed data makes modules visible in the UI. Without it, the module exists in code but is invisible to users. Reference: `references/core-seed-data.md` (loaded above) for complete C# templates.
44
44
 
45
+ > **CONTEXT COMPRESSION SAFEGUARD (delegate mode):**
46
+ > In delegate mode (`-d`), `references/core-seed-data.md` may have been evicted from context by compression
47
+ > before Layer 1 begins (it was loaded at step-03 entry but is ~1464 lines).
48
+ >
49
+ > **CHECK:** Can you see Section 6 "IClientSeedDataProvider Implementation" from `core-seed-data.md` in your context?
50
+ > - **YES** → Proceed normally using the templates from that section.
51
+ > - **NO** → **Re-read** `references/core-seed-data.md` lines 1004-1296 NOW (the IClientSeedDataProvider template).
52
+ > This section contains the complete C# template for the provider class with all 4 Seed methods.
53
+ >
54
+ > **WARNING — ANTI-STUB RULE:**
55
+ > The SeedDataProvider MUST contain real implementation code. Each of the 4 methods
56
+ > (`SeedNavigationAsync`, `SeedRolesAsync`, `SeedPermissionsAsync`, `SeedRolePermissionsAsync`)
57
+ > MUST have actual entity creation logic with `context.{DbSet}.Add()` calls.
58
+ >
59
+ > **BLOCKING:** If ANY Seed method body is just `return Task.CompletedTask;` or `{ }` (empty),
60
+ > the seed data layer is INVALID. The module will be invisible in the UI (menu vide / empty menu).
61
+ > Re-read the template from `references/core-seed-data.md` Section 6 and generate proper implementation.
62
+
45
63
  ---
46
64
 
47
65
  ### Mode Detection: CREATE vs UPDATE
@@ -169,6 +169,9 @@ For each module:
169
169
  3. Add the new namespace import + registration for all 4 languages
170
170
  4. If config uses dynamic imports: add namespace to the `ns` array
171
171
  5. Verify: `grep -q "{module_namespace}" src/**/i18n/config.ts` → must match
172
+ 6. **I18n File/Import Alignment Check:** Verify that every locale JSON file on disk for this module
173
+ has a corresponding import in the i18n config index.ts. Glob `src/**/i18n/locales/*/` for module files,
174
+ then verify each is imported. Missing imports cause silent translation failures (keys show as raw strings).
172
175
  - Permissions: Call MCP generate_permissions for the module permission root (2 segments: {app}.{module}),
173
176
  then also call MCP generate_permissions for each section (3 segments: {app}.{module}.{section}).
174
177
  - Section routes: Ensure navigation seed data has ComponentKey matching PageRegistry keys.
@@ -131,6 +131,41 @@ IF ANY cell = NO → BLOCKING — return to step-03b to fix
131
131
 
132
132
  Execute all checks from `references/post-checks.md`. If any fails, fix in step-03 and re-validate.
133
133
 
134
+ ### 6c. SeedDataProvider Content Verification (BLOCKING)
135
+
136
+ > **LESSON LEARNED (audit ba-003):** SeedDataProvider existed and had 4 methods (passed C23),
137
+ > but all methods were `return Task.CompletedTask` — stubs generated when `core-seed-data.md`
138
+ > was evicted from context by compression. Result: empty menu, 0 applications visible.
139
+
140
+ ```javascript
141
+ // Find the SeedDataProvider file
142
+ const providerFiles = Glob("**/Seeding/*SeedDataProvider.cs");
143
+ for (const providerFile of providerFiles) {
144
+ const content = readFile(providerFile);
145
+
146
+ // Check 1: No stub methods — each Seed*Async must have real implementation
147
+ const methods = ['SeedNavigationAsync', 'SeedRolesAsync', 'SeedPermissionsAsync', 'SeedRolePermissionsAsync'];
148
+ for (const method of methods) {
149
+ const methodRegex = new RegExp(`${method}[^{]*\\{([^}]*)\\}`, 's');
150
+ const match = content.match(methodRegex);
151
+ if (match) {
152
+ const body = match[1].trim();
153
+ if (body === 'return Task.CompletedTask;' || body === '' || body === 'return;') {
154
+ BLOCKING_ERROR(`${providerFile}: ${method} is a STUB (${body || 'empty body'}). ` +
155
+ `Re-read references/core-seed-data.md Section 6 and generate real implementation. ` +
156
+ `Stub seed methods cause EMPTY MENU — 0 applications visible in UI.`);
157
+ }
158
+ }
159
+ }
160
+
161
+ // Check 2: Must contain actual entity creation calls
162
+ if (!content.includes('.Add(') && !content.includes('.AddRange(')) {
163
+ BLOCKING_ERROR(`${providerFile}: No .Add() or .AddRange() calls found. ` +
164
+ `SeedDataProvider must actually insert seed data, not just return.`);
165
+ }
166
+ }
167
+ ```
168
+
134
169
  ---
135
170
 
136
171
  ## 7. Acceptance Criteria POST-CHECK
@@ -65,6 +65,60 @@ if (resumeValid) {
65
65
  }
66
66
  ```
67
67
 
68
+ ### Ralph State Recovery (context compression defense)
69
+
70
+ > **When context compression occurs mid-execution**, conversation variables are lost.
71
+ > `ralph-state.json` tracks the current position and is written before each major transition.
72
+ > This section reads it back to recover position.
73
+
74
+ ```javascript
75
+ // ALWAYS attempt to read ralph-state.json on resume — regardless of -r flag
76
+ // This file is written by: step-00 init, step-02 execute, compact-loop, module-transition
77
+ const statePath = '.ralph/ralph-state.json';
78
+ if (fileExists(statePath)) {
79
+ const state = readJSON(statePath);
80
+
81
+ console.log(`Ralph state recovered: step=${state.currentStep}, module=${state.currentModule}, ` +
82
+ `iteration=${state.iteration || '?'}, reason=${state.reason || 'normal'}`);
83
+
84
+ // Restore conversation variables from state
85
+ if (state.currentModule) {
86
+ {current_module} = state.currentModule;
87
+ }
88
+ if (state.iteration) {
89
+ {current_iteration} = state.iteration;
90
+ }
91
+ if (state.prdVersion) {
92
+ {prd_version} = state.prdVersion;
93
+ }
94
+
95
+ // Route to the correct step based on saved state
96
+ // This replaces the default "always go to step-01" behavior
97
+ const stepRouting = {
98
+ 'step-01-task': 'steps/step-01-task.md',
99
+ 'step-02-execute': 'steps/step-02-execute.md',
100
+ 'step-03-commit': 'steps/step-03-commit.md',
101
+ 'step-04-check': 'steps/step-04-check.md',
102
+ 'compact-loop': 'steps/step-04-check.md', // compact-loop re-enters via step-04
103
+ 'step-05-report': 'steps/step-05-report.md'
104
+ };
105
+
106
+ const targetStep = stepRouting[state.currentStep] || 'steps/step-01-task.md';
107
+
108
+ // Special case: module transition — go to step-01 to detect module-changed.json
109
+ if (state.reason === 'module-transition') {
110
+ console.log(`Module transition recovery: ${state.fromModule} → ${state.currentModule}`);
111
+ console.log('Routing to step-01-task.md to detect module-changed.json');
112
+ // targetStep stays 'steps/step-01-task.md'
113
+ }
114
+
115
+ console.log(`Recovery routing: → ${targetStep}`);
116
+ // The calling code (step-00) should load targetStep instead of the default step-01
117
+ }
118
+ ```
119
+
120
+ **IMPORTANT:** This recovery is triggered BEFORE the default "Proceed to step-01" routing in step-00-init.md. If ralph-state.json provides a different target step, that step should be loaded instead.
121
+
68
122
  ---
69
123
 
70
124
  ## Auto-Recovery: BA Artifacts Without PRD
@@ -76,10 +76,11 @@ If tests are slow (>30s in logs), display warning but **continue execution**:
76
76
 
77
77
  ## 4. Resume or Initialize
78
78
 
79
- See `references/init-resume-recovery.md` for complete Resume Mode and Auto-Recovery logic.
79
+ See `references/init-resume-recovery.md` for complete Resume Mode, Auto-Recovery, and Ralph State Recovery logic.
80
80
 
81
81
  **Quick:**
82
- - If `-r` flag: restore state from .ralph/prd.json
82
+ - If `-r` flag: restore state from .ralph/prd.json + .ralph/ralph-state.json
83
+ - Else if `.ralph/ralph-state.json` exists: recover position (step, module, iteration) — see init-resume-recovery.md "Ralph State Recovery"
83
84
  - Else if BA artifacts exist: auto-recover PRDs via `ss business-analyse-handoff`
84
85
  - Else: fresh start
85
86
 
@@ -222,14 +223,20 @@ MCP: Ready | Branch: {branch} | PRD: v{prd_version}
222
223
 
223
224
  ### 7b. Initialize State File (context compression defense)
224
225
 
226
+ > **ralph-state.json is written here AND updated by step-02, compact-loop, and module-transition.**
227
+ > On resume or after context compression, `init-resume-recovery.md` reads it back to route
228
+ > to the correct step instead of always restarting from step-01.
229
+
225
230
  ```javascript
226
231
  writeJSON('.ralph/ralph-state.json', {
227
232
  currentStep: 'step-01-task',
228
233
  currentModule: {current_module},
229
234
  iteration: 1,
230
235
  prdVersion: {prd_version},
236
+ modulesQueuePath: fileExists('.ralph/modules-queue.json') ? '.ralph/modules-queue.json' : null,
231
237
  timestamp: new Date().toISOString()
232
238
  });
233
239
  ```
234
240
 
235
- **Proceed directly to step-01-task.md**
241
+ **Step routing:** If ralph-state recovery (section 4) provided a different target step, load THAT step.
242
+ Otherwise, proceed to step-01-task.md.
@@ -6,8 +6,20 @@ next_step: steps/step-02-execute.md
6
6
 
7
7
  # Step 1: Load Task
8
8
 
9
- > **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
10
- > read `.ralph/ralph-state.json` to recover your position.
9
+ > **STATE RECOVERY (executable):** If context was compressed and you are unsure of your position:
10
+ ```javascript
11
+ if (fileExists('.ralph/ralph-state.json')) {
12
+ const state = readJSON('.ralph/ralph-state.json');
13
+ if (state.currentStep && state.currentStep !== 'step-01-task') {
14
+ console.warn(`RECOVERY: ralph-state.json says currentStep="${state.currentStep}" but we are in step-01.`);
15
+ console.warn(`This may indicate context compression routed to the wrong step.`);
16
+ console.warn(`Current module: ${state.currentModule}, iteration: ${state.iteration}`);
17
+ }
18
+ // Restore module context
19
+ if (state.currentModule) { {current_module} = state.currentModule; }
20
+ if (state.iteration) { {current_iteration} = state.iteration; }
21
+ }
22
+ ```
11
23
 
12
24
  > **MODULE TRANSITION CHECK:** Before "Only Read Once" rule, check for module transition:
13
25
 
@@ -6,8 +6,18 @@ next_step: steps/step-05-report.md OR steps/step-01-task.md
6
6
 
7
7
  # Step 4: Check Completion
8
8
 
9
- > **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
10
- > read `.ralph/ralph-state.json` to recover your position.
9
+ > **STATE RECOVERY (executable):** If context was compressed:
10
+ ```javascript
11
+ if (fileExists('.ralph/ralph-state.json')) {
12
+ const state = readJSON('.ralph/ralph-state.json');
13
+ if (state.currentStep && state.currentStep !== 'step-04-check' && state.currentStep !== 'compact-loop') {
14
+ console.warn(`RECOVERY: ralph-state.json says currentStep="${state.currentStep}" but we are in step-04.`);
15
+ }
16
+ // Restore module context if lost
17
+ if (state.currentModule) { {current_module} = state.currentModule; }
18
+ if (state.iteration) { {current_iteration} = state.iteration; }
19
+ }
20
+ ```
11
21
 
12
22
  ## YOUR TASK:
13
23
 
@@ -0,0 +1,158 @@
1
+ # Entity Name Canonicalization
2
+
3
+ > **Used by:** step-01-transform (section 2: file mapping) and step-02-export (POST-CHECK)
4
+ > **Purpose:** Convert BA entity names (potentially French, with spaces/apostrophes/accents) into valid C# identifiers for file paths.
5
+
6
+ ---
7
+
8
+ ## Why This Exists
9
+
10
+ Business analysts write entity names in the user's language (e.g., French: "Type d'absence", "Employé", "Congé maladie"). These names are used as-is in `entities.json`. However, C# file paths and class names **MUST** be valid identifiers:
11
+ - No spaces
12
+ - No apostrophes
13
+ - No accents (diacritics)
14
+ - PascalCase convention
15
+
16
+ Without canonicalization, the handoff generates paths like `src/Domain/Entities/Type d'absence.cs` → build failure (CS1001).
17
+
18
+ ---
19
+
20
+ ## Canonicalization Rules
21
+
22
+ ### Step 1: Map to English PascalCase (PREFERRED)
23
+
24
+ If the BA provides an explicit `codeIdentifier` or `englishName` field on the entity, use it directly.
25
+
26
+ ### Step 2: Strip Diacritics
27
+
28
+ Remove accents from characters:
29
+
30
+ | Input | Output |
31
+ |-------|--------|
32
+ | é, è, ê, ë | e |
33
+ | à, â, ä | a |
34
+ | ù, û, ü | u |
35
+ | ô, ö | o |
36
+ | î, ï | i |
37
+ | ç | c |
38
+
39
+ Example: `Employé` → `Employe`, `Congé` → `Conge`
40
+
41
+ ### Step 3: Remove Apostrophes and Split on Spaces
42
+
43
+ Split the name on spaces, apostrophes, hyphens, and underscores:
44
+
45
+ | Input | Tokens |
46
+ |-------|--------|
47
+ | `Type d'absence` | `["Type", "d", "absence"]` |
48
+ | `Congé maladie` | `["Conge", "maladie"]` |
49
+ | `Mise à jour` | `["Mise", "a", "jour"]` |
50
+
51
+ ### Step 4: Remove Articles and Prepositions (1-2 letter tokens)
52
+
53
+ Remove French articles and prepositions that don't contribute to the identifier:
54
+
55
+ **Remove:** `d`, `de`, `du`, `l`, `la`, `le`, `les`, `un`, `une`, `des`, `a`, `au`, `aux`, `en`
56
+
57
+ | Input Tokens | Filtered |
58
+ |-------------|----------|
59
+ | `["Type", "d", "absence"]` | `["Type", "absence"]` |
60
+ | `["Mise", "a", "jour"]` | `["Mise", "jour"]` |
61
+
62
+ ### Step 5: PascalCase Assembly
63
+
64
+ Capitalize first letter of each remaining token, join without separator:
65
+
66
+ | Filtered Tokens | Result |
67
+ |----------------|--------|
68
+ | `["Type", "absence"]` | `TypeAbsence` |
69
+ | `["Conge", "maladie"]` | `CongeMaladie` |
70
+ | `["Mise", "jour"]` | `MiseJour` |
71
+ | `["Employe"]` | `Employe` |
72
+
73
+ ---
74
+
75
+ ## Canonicalization Function (Pseudocode)
76
+
77
+ ```javascript
78
+ function canonicalizeEntityName(baName) {
79
+ // Step 1: Use explicit code identifier if available
80
+ // (handled by caller — check entity.codeIdentifier || entity.englishName first)
81
+
82
+ // Step 2: Strip diacritics
83
+ let name = baName.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
84
+
85
+ // Step 3: Split on non-alphanumeric
86
+ let tokens = name.split(/[\s'\-_]+/).filter(t => t.length > 0);
87
+
88
+ // Step 4: Remove French articles/prepositions
89
+ const stopWords = new Set(['d', 'de', 'du', 'l', 'la', 'le', 'les', 'un', 'une', 'des', 'a', 'au', 'aux', 'en']);
90
+ tokens = tokens.filter(t => !stopWords.has(t.toLowerCase()));
91
+
92
+ // Step 5: PascalCase
93
+ return tokens.map(t => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase()).join('');
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Validation: Is Valid C# Identifier?
100
+
101
+ After canonicalization, verify the result is a valid C# identifier:
102
+
103
+ ```javascript
104
+ function isValidCSharpIdentifier(name) {
105
+ // Must start with letter or underscore
106
+ // Must contain only letters, digits, underscores
107
+ // Must not be a C# keyword
108
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
109
+ && !CSHARP_KEYWORDS.has(name);
110
+ }
111
+
112
+ const CSHARP_KEYWORDS = new Set([
113
+ 'abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch',
114
+ 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default',
115
+ 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit',
116
+ 'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach',
117
+ 'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal',
118
+ 'is', 'lock', 'long', 'namespace', 'new', 'null', 'object',
119
+ 'operator', 'out', 'override', 'params', 'private', 'protected',
120
+ 'public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short',
121
+ 'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch',
122
+ 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong',
123
+ 'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void',
124
+ 'volatile', 'while'
125
+ ]);
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Examples
131
+
132
+ | BA Entity Name (French) | Canonicalized | File Path |
133
+ |-------------------------|---------------|-----------|
134
+ | `Type d'absence` | `TypeAbsence` | `src/Domain/Entities/App/Module/TypeAbsence.cs` |
135
+ | `Employé` | `Employe` | `src/Domain/Entities/App/Module/Employe.cs` |
136
+ | `Congé maladie` | `CongeMaladie` | `src/Domain/Entities/App/Module/CongeMaladie.cs` |
137
+ | `Mise à jour` | `MiseJour` | `src/Domain/Entities/App/Module/MiseJour.cs` |
138
+ | `Département` | `Departement` | `src/Domain/Entities/App/Module/Departement.cs` |
139
+ | `Employee` | `Employee` | `src/Domain/Entities/App/Module/Employee.cs` (no change) |
140
+ | `AbsenceType` | `AbsenceType` | `src/Domain/Entities/App/Module/AbsenceType.cs` (no change) |
141
+
142
+ ---
143
+
144
+ ## Integration Points
145
+
146
+ 1. **step-01-transform.md**: Apply `canonicalizeEntityName()` to ALL entity names BEFORE generating `filesToCreate` paths
147
+ 2. **step-02-export.md**: POST-CHECK validates all paths in `filesToCreate` are valid C# identifiers
148
+ 3. **entity-domain-mapping.md**: `{EntityName}` in path templates refers to the CANONICALIZED name
149
+ 4. **handoff-file-templates.md**: All `{EntityName}` placeholders use canonicalized names
150
+
151
+ ## Anti-Patterns
152
+
153
+ | Anti-Pattern | Risk | Correct Approach |
154
+ |-------------|------|-----------------|
155
+ | Translate to English semantically | `MiseJour` → `Update`? No — loses domain meaning | Only strip illegal chars, keep semantics |
156
+ | Remove all short words | `TypeAbsence` missing `Type`? | Only remove articles/prepositions, not nouns |
157
+ | Lowercase everything | `typeabsence` is not PascalCase | PascalCase each token |
158
+ | Skip validation | `Task` is a valid name but also a C# type | Warn on namespace conflicts, don't block |
@@ -19,6 +19,7 @@ next_step: steps/step-02-export.md
19
19
  - **NEVER** invent entities/BRs not in module JSON files
20
20
  - **NEVER** load all module data in the main conversation (causes context overflow on 5+ modules)
21
21
  - **ALWAYS** generate API endpoints from use cases + entities (BA does not produce apiEndpoints)
22
+ - **ALWAYS** canonicalize entity names before generating file paths — see `references/entity-canonicalization.md`
22
23
 
23
24
  ## YOUR TASK
24
25
 
@@ -96,6 +97,19 @@ Then build the handoff data following these rules:
96
97
  ### Section UC/BR Enrichment
97
98
  Using sectionCode from usecases.json and rules.json, link each UC and BR to its section.
98
99
 
100
+ ### Entity Name Canonicalization (MANDATORY)
101
+ Before generating any file paths, canonicalize ALL entity names from `entities.json`:
102
+ 1. Read `references/entity-canonicalization.md` for complete rules
103
+ 2. For each entity in `entities.json > entities[]`:
104
+ - If entity has `codeIdentifier` or `englishName` → use it directly
105
+ - Otherwise → apply canonicalization: strip diacritics, remove apostrophes/spaces, remove French articles (d, de, du, l, la, le, les, un, une, des, a, au, aux, en), PascalCase
106
+ 3. Use the CANONICALIZED name in ALL file path templates (`{EntityName}`, `{ServiceName}`, `{DtoName}`, etc.)
107
+ 4. Store the mapping `{ originalName: "Type d'absence", canonicalName: "TypeAbsence" }` in handoff metadata for traceability
108
+
109
+ **Example:** Entity `"Type d'absence"` → canonicalized to `"TypeAbsence"` → path `src/Domain/Entities/App/Module/TypeAbsence.cs`
110
+
111
+ **BLOCKING:** If ANY entity name after canonicalization is not a valid C# identifier (`/^[A-Za-z_][A-Za-z0-9_]*$/`), STOP and report the error.
112
+
99
113
  ### File Mapping (8 Categories)
100
114
  Read `references/handoff-file-templates.md` for complete JSON templates.
101
115
  All backend paths MUST include {ApplicationName}/ hierarchy.