@atlashub/smartstack-cli 3.34.0 → 3.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.documentation/init.html +409 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +7 -24
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -2
- package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
- package/templates/skills/apex/SKILL.md +3 -3
- package/templates/skills/apex/references/post-checks.md +225 -0
- package/templates/skills/apex/references/smartstack-api.md +29 -1
- package/templates/skills/apex/references/smartstack-frontend.md +27 -0
- package/templates/skills/apex/references/smartstack-layers.md +18 -2
- package/templates/skills/apex/steps/step-00-init.md +73 -0
- package/templates/skills/apex/steps/step-01-analyze.md +21 -0
- package/templates/skills/apex/steps/step-03-execute.md +72 -5
- package/templates/skills/apex/steps/step-04-examine.md +7 -1
- package/templates/skills/business-analyse/SKILL.md +4 -3
- package/templates/skills/business-analyse/_shared.md +9 -0
- package/templates/skills/business-analyse/schemas/application-schema.json +13 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +190 -34
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +129 -10
- package/templates/skills/business-analyse/steps/step-01b-applications.md +184 -13
- package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +5 -1
- package/templates/skills/ralph-loop/SKILL.md +5 -0
- package/templates/skills/ralph-loop/references/category-rules.md +29 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +85 -2
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
- package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +45 -1
- package/templates/skills/ralph-loop/steps/step-05-report.md +19 -0
- package/scripts/health-check.sh +0 -168
- package/scripts/postinstall.js +0 -18
|
@@ -1,34 +1,201 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-01b-applications
|
|
3
|
-
description: Application
|
|
3
|
+
description: Application identity confirmation (single-app) or full application decomposition (multi-app)
|
|
4
4
|
model: opus
|
|
5
5
|
next_step: steps/step-02-decomposition.md
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
> **Context files:** `_shared.md`
|
|
9
9
|
|
|
10
|
-
# Step 1b: Application Decomposition
|
|
10
|
+
# Step 1b: Application Identity & Decomposition
|
|
11
11
|
|
|
12
12
|
## MANDATORY EXECUTION RULES
|
|
13
13
|
|
|
14
14
|
- ALWAYS use ULTRATHINK mode
|
|
15
|
-
- This step is ONLY loaded when `workflow.mode === "project"` (multi-application mode)
|
|
16
15
|
- ALL communication in `{language}`
|
|
17
|
-
- This step
|
|
16
|
+
- This step ALWAYS runs. It has two modes:
|
|
17
|
+
- **Single-app mode** (`workflow.mode !== "project"`): Lightweight application identity confirmation — name, code, context, route, icon, table prefix validation, application roles recap. Creates the `seedDataCore.navigationApplications` entry.
|
|
18
|
+
- **Multi-app mode** (`workflow.mode === "project"`): Full application decomposition as defined in sections 1-7 below.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
---
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## MODE GATE
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
IF workflow.mode === "project":
|
|
26
|
+
→ Execute FULL multi-app flow (sections 1-7 below)
|
|
27
|
+
ELSE:
|
|
28
|
+
→ Execute LIGHTWEIGHT single-app identity (sections 0a-0f below)
|
|
29
|
+
```
|
|
22
30
|
|
|
23
31
|
---
|
|
24
32
|
|
|
33
|
+
## SINGLE-APP PATH (sections 0a-0f)
|
|
34
|
+
|
|
35
|
+
> This path runs when `workflow.mode !== "project"` (single application, most common case).
|
|
36
|
+
> It confirms the application-level identity (Level 2 of the navigation hierarchy) before proceeding to module decomposition.
|
|
37
|
+
|
|
38
|
+
### 0a. Read Application Context
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
appFeature = ba-reader.findFeature({feature_id})
|
|
42
|
+
→ Read metadata: application, context, tablePrefix
|
|
43
|
+
→ Read cadrage: applicationRoles, coverageMatrix, globalScope
|
|
44
|
+
→ Read cadrage.codebaseContext (if exists)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 0b. Derive Application Identity
|
|
48
|
+
|
|
49
|
+
From the cadrage data, derive the formal application identity:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
applicationCode = toPascalCase(metadata.application) // e.g., "HumanResources"
|
|
53
|
+
applicationLabel = metadata.application // e.g., "Ressources Humaines" (human-readable)
|
|
54
|
+
context = metadata.context // "business" (validated in step-00)
|
|
55
|
+
applicationRoute = `/${context}/${toKebabCase(applicationCode)}` // e.g., "/business/human-resources"
|
|
56
|
+
applicationIcon = suggestIconFromDomain(applicationCode) // e.g., "users" for HR, "shopping-cart" for Sales
|
|
57
|
+
tablePrefix = metadata.tablePrefix // e.g., "rh_" (already defined in step-01)
|
|
58
|
+
sort = 1 // First (and only) application
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Icon suggestion heuristics:**
|
|
62
|
+
| Domain pattern | Suggested icon |
|
|
63
|
+
|---------------|---------------|
|
|
64
|
+
| HR, Employee, Staff, Personnel | `users` |
|
|
65
|
+
| Sales, Commerce, Revenue | `shopping-cart` |
|
|
66
|
+
| Finance, Accounting, Budget | `wallet` |
|
|
67
|
+
| Inventory, Stock, Warehouse | `package` |
|
|
68
|
+
| CRM, Customer, Client | `contact` |
|
|
69
|
+
| Project, Task, Planning | `clipboard-list` |
|
|
70
|
+
| Support, Ticket, Help | `headphones` |
|
|
71
|
+
| Reporting, Analytics, Dashboard | `bar-chart-2` |
|
|
72
|
+
| Other | `layout-grid` |
|
|
73
|
+
|
|
74
|
+
### 0c. Confirm Application Identity (BLOCKING)
|
|
75
|
+
|
|
76
|
+
Display the application identity:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
{language == "fr"
|
|
80
|
+
? "## Identité de l'application\n\nVoici l'identité de votre application dans la hiérarchie de navigation SmartStack :"
|
|
81
|
+
: "## Application Identity\n\nHere is your application's identity in the SmartStack navigation hierarchy:"}
|
|
82
|
+
|
|
83
|
+
| {language == "fr" ? "Niveau" : "Level"} | {language == "fr" ? "Valeur" : "Value"} |
|
|
84
|
+
|-------|-------|
|
|
85
|
+
| Context | `{context}` |
|
|
86
|
+
| Application Code | `{applicationCode}` |
|
|
87
|
+
| Application Name | `{applicationLabel}` |
|
|
88
|
+
| Route | `{applicationRoute}` |
|
|
89
|
+
| Icon | `{applicationIcon}` |
|
|
90
|
+
| Table Prefix | `{tablePrefix}` |
|
|
91
|
+
| Sort Order | `{sort}` |
|
|
92
|
+
|
|
93
|
+
{language == "fr"
|
|
94
|
+
? "### Rôles applicatifs\n\nCes rôles seront hérités par tous les modules :"
|
|
95
|
+
: "### Application Roles\n\nThese roles will be inherited by all modules:"}
|
|
96
|
+
|
|
97
|
+
| Role | Level | Permission Pattern |
|
|
98
|
+
|------|-------|--------------------|
|
|
99
|
+
{for each role in cadrage.applicationRoles: role.role | role.level | {context}.{kebab(applicationCode)}.* }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Ask via AskUserQuestion:
|
|
103
|
+
```
|
|
104
|
+
question: "{language == 'fr' ? 'Cette identité d\'application est-elle correcte ?' : 'Is this application identity correct?'}"
|
|
105
|
+
header: "Application"
|
|
106
|
+
options:
|
|
107
|
+
- label: "{language == 'fr' ? 'Oui, parfait' : 'Yes, perfect'}"
|
|
108
|
+
description: "{language == 'fr' ? 'Code, route, icône et rôles sont corrects' : 'Code, route, icon and roles are correct'}"
|
|
109
|
+
- label: "{language == 'fr' ? 'Modifier' : 'Modify'}"
|
|
110
|
+
description: "{language == 'fr' ? 'Changer le code, la route, l\'icône ou les rôles' : 'Change code, route, icon or roles'}"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**IF "Modify":**
|
|
114
|
+
→ Ask which field(s) to change via follow-up AskUserQuestion
|
|
115
|
+
→ Apply changes
|
|
116
|
+
→ Re-display and re-confirm (loop until validated)
|
|
117
|
+
|
|
118
|
+
### 0d. Build Application SeedData Entries
|
|
119
|
+
|
|
120
|
+
Create the `navigationApplications` entry (Level 2 hierarchy entry):
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"navigationApplications": [
|
|
125
|
+
{
|
|
126
|
+
"code": "{applicationCode}",
|
|
127
|
+
"label": "{applicationLabel}",
|
|
128
|
+
"icon": "{applicationIcon}",
|
|
129
|
+
"route": "{applicationRoute}",
|
|
130
|
+
"sort": 1
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Create the `applicationRoles` entries (from cadrage.applicationRoles):
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"applicationRoles": [
|
|
141
|
+
{ "code": "admin", "name": "{applicationLabel} Admin", "permissions": "*" },
|
|
142
|
+
{ "code": "manager", "name": "{applicationLabel} Manager", "permissions": "CRU" },
|
|
143
|
+
{ "code": "contributor", "name": "{applicationLabel} Contributor", "permissions": "CR" },
|
|
144
|
+
{ "code": "viewer", "name": "{applicationLabel} Viewer", "permissions": "R" }
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> **NOTE:** The roles map from `cadrage.applicationRoles` levels. If the cadrage defined custom roles beyond the standard 4, include them here too.
|
|
150
|
+
|
|
151
|
+
### 0e. Write Application Identity to Feature.json
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
ba-writer.enrichSection({
|
|
155
|
+
featureId: {feature_id},
|
|
156
|
+
section: "metadata",
|
|
157
|
+
data: {
|
|
158
|
+
applicationCode: "{applicationCode}",
|
|
159
|
+
applicationRoute: "{applicationRoute}",
|
|
160
|
+
applicationIcon: "{applicationIcon}"
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
> **NOTE:** The full `seedDataCore` arrays (`navigationApplications`, `applicationRoles`) will be written in step-03c when compiling the first module's specification. Here we only persist the identity fields in metadata so they are available to step-02 and step-03.
|
|
166
|
+
|
|
167
|
+
### 0f. Summary and Continue
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
{language == "fr"
|
|
171
|
+
? "✅ Application **{applicationCode}** identifiée\n\n- Route : `{applicationRoute}`\n- Icône : `{applicationIcon}`\n- Préfixe : `{tablePrefix}`\n- Rôles : {roleCount} rôles applicatifs définis\n\n→ Prochaine étape : décomposition en modules"
|
|
172
|
+
: "✅ Application **{applicationCode}** identified\n\n- Route: `{applicationRoute}`\n- Icon: `{applicationIcon}`\n- Prefix: `{tablePrefix}`\n- Roles: {roleCount} application roles defined\n\n→ Next step: module decomposition"}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
→ Load `steps/step-02-decomposition.md`
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## MULTI-APP PATH (sections 1-7)
|
|
180
|
+
|
|
181
|
+
> This path runs when `workflow.mode === "project"` (multi-application mode).
|
|
182
|
+
> It takes the candidate applications identified during cadrage and formalizes them.
|
|
183
|
+
|
|
25
184
|
### 1. Load Project Context
|
|
26
185
|
|
|
27
186
|
```
|
|
28
187
|
projectFeature = ba-reader.findProjectFeature()
|
|
29
|
-
candidateApps = projectFeature.cadrage.coverageMatrix grouped by domain
|
|
30
188
|
globalRoles = projectFeature.cadrage.globalRoles (if defined)
|
|
31
189
|
globalScope = projectFeature.cadrage.globalScope
|
|
190
|
+
|
|
191
|
+
// Candidate applications: PRE-IDENTIFIED from step-00 prompt analysis + enriched by step-01 cadrage
|
|
192
|
+
candidateApps = projectFeature.metadata.candidateApplications
|
|
193
|
+
// These candidates already have: name, description, modules[], context, dependencies[]
|
|
194
|
+
// They may also include transversal apps extracted from shared modules (step-01 section 5b)
|
|
195
|
+
|
|
196
|
+
IF candidateApps is null or empty:
|
|
197
|
+
// Fallback: derive from coverage matrix (legacy path)
|
|
198
|
+
candidateApps = projectFeature.cadrage.coverageMatrix grouped by domain
|
|
32
199
|
```
|
|
33
200
|
|
|
34
201
|
### 2. Application Identity (per candidate)
|
|
@@ -154,14 +321,14 @@ options:
|
|
|
154
321
|
- label: "{language == 'fr' ? 'Modifier' : 'Modify'}"
|
|
155
322
|
description: "{language == 'fr' ? 'Ajouter, supprimer ou modifier des applications' : 'Add, remove, or modify applications'}"
|
|
156
323
|
- label: "{language == 'fr' ? 'Fusionner en une seule' : 'Merge into one'}"
|
|
157
|
-
description: "{language == 'fr' ? 'Finalement, c'est une seule application avec plusieurs modules' : 'Actually, this is one application with multiple modules'}"
|
|
324
|
+
description: "{language == 'fr' ? 'Finalement, c\'est une seule application avec plusieurs modules' : 'Actually, this is one application with multiple modules'}"
|
|
158
325
|
```
|
|
159
326
|
|
|
160
327
|
**IF "Merge into one":**
|
|
161
328
|
→ Convert back to single-application mode
|
|
162
329
|
→ Delete project-level feature.json
|
|
163
330
|
→ Create application-level feature.json with all scope items
|
|
164
|
-
→
|
|
331
|
+
→ Execute single-app path (sections 0a-0f above)
|
|
165
332
|
|
|
166
333
|
### 5. Create Per-Application Feature.json Files
|
|
167
334
|
|
|
@@ -175,6 +342,9 @@ ba-writer.createApplicationFeature({
|
|
|
175
342
|
status: "draft",
|
|
176
343
|
metadata: {
|
|
177
344
|
application: {app.code},
|
|
345
|
+
applicationCode: {toPascalCase(app.code)},
|
|
346
|
+
applicationRoute: {`/${app.context}/${toKebabCase(app.code)}`},
|
|
347
|
+
applicationIcon: {app.icon},
|
|
178
348
|
context: {app.context},
|
|
179
349
|
language: {language},
|
|
180
350
|
featureDescription: {app.description},
|
|
@@ -208,6 +378,7 @@ ba-writer.enrichApplicationRegistry({
|
|
|
208
378
|
context: {app1.context},
|
|
209
379
|
tablePrefix: {app1.tablePrefix},
|
|
210
380
|
icon: {app1.icon},
|
|
381
|
+
route: {app1.route},
|
|
211
382
|
description: {app1.description},
|
|
212
383
|
applicationRoles: [{...}],
|
|
213
384
|
scope: { mustHave: [...], shouldHave: [...], couldHave: [...] },
|
|
@@ -236,9 +407,9 @@ ba-writer.updateStatus({project_id}, "decomposed")
|
|
|
236
407
|
? "### Décomposition en applications terminée\n\n"
|
|
237
408
|
: "### Application decomposition complete\n\n"}
|
|
238
409
|
|
|
239
|
-
| Application | Contexte | Préfixe | Feature ID |
|
|
240
|
-
|
|
241
|
-
{for each app: name | context | prefix | FEAT-NNN}
|
|
410
|
+
| Application | Contexte | Préfixe | Route | Feature ID |
|
|
411
|
+
|-------------|----------|---------|-------|------------|
|
|
412
|
+
{for each app: name | context | prefix | route | FEAT-NNN}
|
|
242
413
|
|
|
243
414
|
{language == "fr"
|
|
244
415
|
? "→ Prochaine étape : décomposition en modules pour **{firstApp}** (application {1}/{total})"
|
|
@@ -249,4 +420,4 @@ ba-writer.updateStatus({project_id}, "decomposed")
|
|
|
249
420
|
|
|
250
421
|
## NEXT STEP
|
|
251
422
|
|
|
252
|
-
Load: `./step-02-decomposition.md` (for the first application in topological order)
|
|
423
|
+
Load: `./step-02-decomposition.md` (for the first application in topological order, or the single application)
|
|
@@ -301,11 +301,20 @@ for (const entry of specification.navigation.entries) {
|
|
|
301
301
|
|
|
302
302
|
#### 8f. SeedData Core
|
|
303
303
|
|
|
304
|
-
|
|
304
|
+
9 MANDATORY typed arrays — each with structured objects, NOT flat strings or objects.
|
|
305
305
|
|
|
306
306
|
> **STRUCTURE CARD: specification.seedDataCore**
|
|
307
307
|
> ```json
|
|
308
308
|
> {
|
|
309
|
+
> "navigationApplications": [
|
|
310
|
+
> { "code": "{app}", "label": "{Application Name}", "icon": "{icon}", "route": "/business/{app-kebab}", "sort": 1 }
|
|
311
|
+
> ],
|
|
312
|
+
> "applicationRoles": [
|
|
313
|
+
> { "code": "admin", "name": "{App} Admin", "permissions": "*" },
|
|
314
|
+
> { "code": "manager", "name": "{App} Manager", "permissions": "CRU" },
|
|
315
|
+
> { "code": "contributor", "name": "{App} Contributor", "permissions": "CR" },
|
|
316
|
+
> { "code": "viewer", "name": "{App} Viewer", "permissions": "R" }
|
|
317
|
+
> ],
|
|
309
318
|
> "navigationModules": [
|
|
310
319
|
> { "code": "{module}", "label": "{Module Name}", "icon": "list", "route": "/business/{app}/{module}", "parentCode": "{app}", "sort": 1 }
|
|
311
320
|
> ],
|
|
@@ -339,7 +348,8 @@ for (const entry of specification.navigation.entries) {
|
|
|
339
348
|
> ]
|
|
340
349
|
> }
|
|
341
350
|
> ```
|
|
342
|
-
> **MANDATORY:** All
|
|
351
|
+
> **MANDATORY:** All 9 arrays must be present. Each element must be an object, NOT a string.
|
|
352
|
+
> **NOTE:** `navigationApplications` and `applicationRoles` are populated from the application identity confirmed in step-01b. They are written ONCE for the first module processed and remain empty `[]` for subsequent modules.
|
|
343
353
|
> **CRITICAL:** `navigationSections` and `navigationResources` are DERIVED from `specification.sections[]` — use the transform algorithm below (section 8f-bis).
|
|
344
354
|
> **IMPORTANT:** `create` and `edit` are NEVER sections — they are action pages reached via buttons. Do NOT include them in `navigationSections`. Only include actual sidebar sections (list, dashboard, approve, import, etc.) and hidden route sections (detail).
|
|
345
355
|
> **FORBIDDEN:** Do NOT use `navigationModule` (singular string), `permissions` as flat string array, `rolePermissions` as flat object, `permissionsConstants` as comma-separated string.
|
|
@@ -388,6 +398,8 @@ ba-writer.enrichSection({
|
|
|
388
398
|
section: "specification",
|
|
389
399
|
data: {
|
|
390
400
|
seedDataCore: {
|
|
401
|
+
navigationApplications: [ ... ], // FROM step-01b identity (only for first module, else [])
|
|
402
|
+
applicationRoles: [ ... ], // FROM cadrage.applicationRoles (only for first module, else [])
|
|
391
403
|
navigationModules: [ ... ],
|
|
392
404
|
navigationSections: navigationSections, // DERIVED
|
|
393
405
|
navigationResources: navigationResources, // DERIVED
|
|
@@ -73,7 +73,7 @@ Validate the module specification for completeness and consistency, write to fea
|
|
|
73
73
|
| messages | 4 | `specification.messages.length >= 4` | PASS/FAIL |
|
|
74
74
|
| messageFormat | `message` field present (BLOCKING) | `specification.messages.every(m => m.message)` | PASS/FAIL |
|
|
75
75
|
| lifeCycles | 1 (if entity has status) | `specification.lifeCycles.length >= 1` (if any entity has status/state field) | PASS/FAIL |
|
|
76
|
-
| seedDataCore |
|
|
76
|
+
| seedDataCore | 9 arrays present with content | See detailed check below | PASS/FAIL (BLOCKING) |
|
|
77
77
|
| apiEndpoints | 1 | `specification.apiEndpoints.length >= 1` | PASS/FAIL |
|
|
78
78
|
| i18nKeys | present | `specification.i18nKeys !== undefined && specification.i18nKeys !== null` | PASS/FAIL |
|
|
79
79
|
| navigationIcons | non-null | `specification.seedDataCore.navigationModules.every(m => m.icon !== null)` | PASS/FAIL |
|
|
@@ -81,7 +81,11 @@ Validate the module specification for completeness and consistency, write to fea
|
|
|
81
81
|
**seedDataCore detailed check (BLOCKING):**
|
|
82
82
|
```javascript
|
|
83
83
|
const sdc = specification.seedDataCore;
|
|
84
|
+
const currentModuleIndex = metadata.workflow?.currentModuleIndex || 0;
|
|
84
85
|
const checks = [
|
|
86
|
+
// navigationApplications and applicationRoles: required for first module (index 0), can be empty for subsequent modules
|
|
87
|
+
{ key: "navigationApplications", actual: sdc.navigationApplications?.length || 0, min: currentModuleIndex === 0 ? 1 : 0 },
|
|
88
|
+
{ key: "applicationRoles", actual: sdc.applicationRoles?.length || 0, min: currentModuleIndex === 0 ? 1 : 0 },
|
|
85
89
|
{ key: "navigationModules", actual: sdc.navigationModules?.length || 0, min: 1 },
|
|
86
90
|
{ key: "navigationSections", actual: sdc.navigationSections?.length || 0, min: 1 }, // EVERY module needs ≥1 section
|
|
87
91
|
{ key: "navigationResources", actual: sdc.navigationResources?.length || 0, min: 1 },
|
|
@@ -79,6 +79,8 @@ LOAD → DELEGATE TO /apex -d → VERIFY PRD STATE → COMMIT PRD → NEXT MODUL
|
|
|
79
79
|
| `{current_iteration}` | number | Current iteration |
|
|
80
80
|
| `{current_module}` | string\|null | Current module (multi-module) |
|
|
81
81
|
| `{modules_queue}` | object\|null | Module queue (multi-module) |
|
|
82
|
+
| `{section_split_mode}` | boolean | Section splitting active for current module (>4 entities + >1 section) |
|
|
83
|
+
| `{section_phases}` | SectionPhase[] | Phase execution plan (Phase 0: foundation, Phase 1..N: per-section) |
|
|
82
84
|
</state_variables>
|
|
83
85
|
|
|
84
86
|
<mcp_requirements>
|
|
@@ -141,6 +143,7 @@ When the user invokes `/ralph-loop`, they are giving you the instruction to:
|
|
|
141
143
|
|------|-------------|
|
|
142
144
|
| `references/category-rules.md` | Step-01 and compact loop (category ordering and dependency rules) |
|
|
143
145
|
| `references/compact-loop.md` | Step-04 section 5 (module loop with /apex delegation) |
|
|
146
|
+
| `references/section-splitting.md` | Step-01 section 4c when module has >4 entities + >1 section |
|
|
144
147
|
| `references/team-orchestration.md` | Step-00 when multi-module detected (2+ PRDs) |
|
|
145
148
|
</step_files>
|
|
146
149
|
|
|
@@ -151,6 +154,8 @@ When the user invokes `/ralph-loop`, they are giving you the instruction to:
|
|
|
151
154
|
├── progress.txt # Persistent memory
|
|
152
155
|
├── modules-queue.json # Multi-module tracking (if applicable)
|
|
153
156
|
├── prd-{module}.json # Per-module PRDs (from ss derive-prd, unified v3 format)
|
|
157
|
+
├── prd-{module}-phase0.json # Section split: Foundation (domain+infra+migration)
|
|
158
|
+
├── prd-{module}-section-{code}.json # Section split: Per-section (app+api+seed+frontend+tests)
|
|
154
159
|
├── logs/
|
|
155
160
|
└── reports/
|
|
156
161
|
└── {feature}.md
|
|
@@ -65,3 +65,32 @@ A valid PRD MUST contain tasks in these categories:
|
|
|
65
65
|
- `test` — at least 1 test task
|
|
66
66
|
|
|
67
67
|
If any category is missing, ralph step-01 injects a guardrail task.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Section-Level Splitting (>4 Entities)
|
|
72
|
+
|
|
73
|
+
When a module has many entities, a single `/apex -d` call saturates the context window.
|
|
74
|
+
Section-level splitting breaks the module into smaller, manageable phases.
|
|
75
|
+
|
|
76
|
+
**Activation threshold:** `> 4 domain tasks` AND `> 1 architecture section`
|
|
77
|
+
|
|
78
|
+
| Phase | Content | Depends On |
|
|
79
|
+
|-------|---------|------------|
|
|
80
|
+
| Phase 0 | ALL domain + infrastructure + migration + module seed data | — |
|
|
81
|
+
| Phase 1 | Section A: services, controllers, section seed, pages, i18n, tests | Phase 0 |
|
|
82
|
+
| Phase 2 | Section B: idem | Phase 0 (+ Phase 1 if FK cross-section) |
|
|
83
|
+
| Phase N | Section N: idem | Phase 0 (+ earlier phases if FK) |
|
|
84
|
+
| Final | Cross-validation (dotnet build, typecheck, MCP validate) | All phases |
|
|
85
|
+
|
|
86
|
+
**Key rules:**
|
|
87
|
+
- Phase 0 creates ALL entity classes + ALL EF configs + ONE migration (avoids ModelSnapshot conflicts)
|
|
88
|
+
- Section phases NEVER create entities or migrations — only services, controllers, pages on top
|
|
89
|
+
- FK cross-section dependencies determine section execution order (topological sort)
|
|
90
|
+
- Orphan entities (not in any section) are included in Phase 0 with their services/controllers
|
|
91
|
+
- Each phase produces a temporary PRD file: `.ralph/prd-{module}-phase0.json`, `.ralph/prd-{module}-section-{code}.json`
|
|
92
|
+
- Temporary PRD files are cleaned up in step-05 (report)
|
|
93
|
+
|
|
94
|
+
**Backward compatible:** Modules with `<= 4 domain tasks` or `1 section` → standard execution, zero impact.
|
|
95
|
+
|
|
96
|
+
See `references/section-splitting.md` for full detection, mapping, and execution logic.
|
|
@@ -23,10 +23,93 @@ Display compact progress:
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## A0. Section-Split Mode (checked BEFORE standard batching)
|
|
27
27
|
|
|
28
28
|
```javascript
|
|
29
29
|
const prd = readJSON('.ralph/prd.json');
|
|
30
|
+
|
|
31
|
+
if (prd._sectionSplit?.enabled) {
|
|
32
|
+
// SECTION-SPLIT: Delegate per-phase instead of per-category batch
|
|
33
|
+
|
|
34
|
+
// Find next pending phase with dependencies met
|
|
35
|
+
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
|
|
36
|
+
|
|
37
|
+
if (!nextPhase) {
|
|
38
|
+
// All phases done — check if master PRD tasks are all complete
|
|
39
|
+
goto CHECK_COMPLETION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const depsOk = nextPhase.dependsOn.every(depIdx =>
|
|
43
|
+
prd._sectionSplit.phases[depIdx].status === 'completed'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!depsOk) {
|
|
47
|
+
// Phase blocked — should not happen with topological sort
|
|
48
|
+
console.warn(`Phase ${nextPhase.phase} blocked — dependencies incomplete`);
|
|
49
|
+
goto CHECK_COMPLETION;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const phaseLabel = nextPhase.type === 'foundation'
|
|
53
|
+
? `Phase 0: Foundation (${nextPhase.entities.length} entities + migration)`
|
|
54
|
+
: `Phase ${nextPhase.phase}: Section "${nextPhase.sectionCode}" (${nextPhase.entities.length} entities)`;
|
|
55
|
+
console.log(`[{iteration}/{max}] SECTION SPLIT: ${phaseLabel}`);
|
|
56
|
+
|
|
57
|
+
// INVOKE /apex -d {nextPhase.prdFile}
|
|
58
|
+
// Apex sees a normal (smaller) PRD and executes normally
|
|
59
|
+
|
|
60
|
+
// After apex returns: merge results back into master PRD
|
|
61
|
+
// Read references/section-splitting.md sections 7-9
|
|
62
|
+
const phasePrd = readJSON(nextPhase.prdFile);
|
|
63
|
+
for (const phaseTask of phasePrd.tasks) {
|
|
64
|
+
const masterTask = prd.tasks.find(t => t.id === phaseTask.id);
|
|
65
|
+
if (masterTask) {
|
|
66
|
+
masterTask.status = phaseTask.status;
|
|
67
|
+
masterTask.completed_at = phaseTask.completed_at;
|
|
68
|
+
masterTask.commit_hash = phaseTask.commit_hash;
|
|
69
|
+
masterTask.files_changed = phaseTask.files_changed;
|
|
70
|
+
masterTask.error = phaseTask.error;
|
|
71
|
+
masterTask.validation = phaseTask.validation;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle phase failure
|
|
76
|
+
if (phasePrd.tasks.some(t => t.status === 'failed')) {
|
|
77
|
+
const retryCount = nextPhase._retryCount || 0;
|
|
78
|
+
if (retryCount < 2) {
|
|
79
|
+
nextPhase.status = 'pending';
|
|
80
|
+
nextPhase._retryCount = retryCount + 1;
|
|
81
|
+
console.log(`Phase ${nextPhase.phase} had failures — retry ${retryCount + 1}/2`);
|
|
82
|
+
// Reset failed tasks in phase PRD for retry
|
|
83
|
+
for (const task of phasePrd.tasks) {
|
|
84
|
+
if (task.status === 'failed') { task.status = 'pending'; task.error = null; }
|
|
85
|
+
}
|
|
86
|
+
writeJSON(nextPhase.prdFile, phasePrd);
|
|
87
|
+
} else {
|
|
88
|
+
nextPhase.status = 'failed';
|
|
89
|
+
console.error(`Phase ${nextPhase.phase} failed after 2 retries`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
nextPhase.status = 'completed';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
96
|
+
writeJSON('.ralph/prd.json', prd);
|
|
97
|
+
|
|
98
|
+
// → Skip to section C (commit PRD state), then D (loop back)
|
|
99
|
+
goto COMMIT_PRD;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **IMPORTANT:** When `_sectionSplit.enabled`, the standard category-based batching (section A below)
|
|
104
|
+
> is SKIPPED. The loop delegates per-phase to apex instead of per-category-batch.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## A. Find Eligible Tasks
|
|
109
|
+
|
|
110
|
+
> **Note:** This section is SKIPPED when `prd._sectionSplit?.enabled` (handled by A0 above).
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
30
113
|
const MAX_RETRIES = 3;
|
|
31
114
|
|
|
32
115
|
// RETRY: Reset failed tasks to pending if retries remain (max 3 attempts per task)
|
|
@@ -115,7 +198,7 @@ writeJSON('.ralph/prd.json', prd);
|
|
|
115
198
|
Apex handles everything for the current module:
|
|
116
199
|
- Reads PRD, extracts context (context_code, app_name, module_code, entities, sections)
|
|
117
200
|
- Executes ALL layers: domain → infrastructure → migration → application → api → seed data → frontend → tests
|
|
118
|
-
- Runs full POST-CHECKs (
|
|
201
|
+
- Runs full POST-CHECKs (50 checks from `references/post-checks.md`)
|
|
119
202
|
- Commits per layer (atomic commits)
|
|
120
203
|
- Validates with MCP (`validate_conventions`)
|
|
121
204
|
- Updates task statuses in the PRD file directly
|