@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
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Section-Level Splitting — Large Module Handling
|
|
2
|
+
|
|
3
|
+
> **Loaded by:** step-01 section 4c (detection) and compact-loop.md (execution)
|
|
4
|
+
> **Purpose:** When a module has >4 domain entities AND >1 architecture section,
|
|
5
|
+
> split `/apex -d` calls per SECTION instead of sending the whole module at once.
|
|
6
|
+
> **Threshold:** `domainTaskCount > 4 AND sectionCount > 1`
|
|
7
|
+
> **Backward compatible:** Modules with <=4 entities are UNAFFECTED.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why Section-Level Splitting?
|
|
12
|
+
|
|
13
|
+
When a module has 7+ entities (e.g., HR with Employee, Department, Position, LeaveRequest, LeaveType, TimeTracking, WorkLog), a single `/apex -d` saturates the context window. Result: migrations incomplete, pages missing, conventions lost. Success rate drops from ~95% to ~30%.
|
|
14
|
+
|
|
15
|
+
**Solution:** Phase 0 builds ALL entities + ONE migration (foundation). Phase 1..N build services/controllers/pages PER SECTION. Each section has 1-3 entities — comfortably within apex's safe zone.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Architecture: Phases
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Phase 0: FOUNDATION (1 apex call)
|
|
23
|
+
- ALL entity classes (Domain layer)
|
|
24
|
+
- ALL EF configurations (Infrastructure layer)
|
|
25
|
+
- ONE migration covering ALL entities
|
|
26
|
+
- Application + Module navigation seed data
|
|
27
|
+
- DbContext registration + DI skeleton
|
|
28
|
+
-> Eliminates migration conflicts between sections
|
|
29
|
+
|
|
30
|
+
Phase 1..N: PER SECTION (1 apex call each)
|
|
31
|
+
- Services + DTOs + Validators for section entities
|
|
32
|
+
- Controllers for section entities
|
|
33
|
+
- Section-level seed data (NavigationSection, permissions, roles)
|
|
34
|
+
- Frontend pages (List, Detail, Create, Edit) for section entities
|
|
35
|
+
- i18n for section entities
|
|
36
|
+
- Tests for section entities
|
|
37
|
+
|
|
38
|
+
Phase Final: CROSS-VALIDATION (inline in step-04)
|
|
39
|
+
- dotnet build (full project)
|
|
40
|
+
- npm run typecheck
|
|
41
|
+
- MCP validate_conventions
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Why ALL Entities in Phase 0?
|
|
45
|
+
|
|
46
|
+
EF Core creates a single ModelSnapshot per DbContext. If Phase 1 creates a migration for Employee+Department and Phase 2 for LeaveRequest+LeaveType, the second migration fails when LeaveRequest has a FK to Employee (ModelSnapshot is inconsistent). **Phase 0 = all entities + 1 migration.** Section phases add services/controllers/pages on top of existing entities.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 1. Detection
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
const prd = readJSON('.ralph/prd.json');
|
|
54
|
+
const domainTasks = prd.tasks.filter(t => t.category === 'domain');
|
|
55
|
+
const sections = prd.architecture?.sections ?? [];
|
|
56
|
+
|
|
57
|
+
const shouldSplit = domainTasks.length > 4 && sections.length > 1;
|
|
58
|
+
|
|
59
|
+
if (!shouldSplit) {
|
|
60
|
+
// Standard execution — no splitting needed
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`SECTION SPLIT: ${domainTasks.length} domain tasks, ${sections.length} sections → activating`);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 2. Entity-to-Section Mapping
|
|
70
|
+
|
|
71
|
+
Build from `prd.architecture.sections[].resources[].entity`:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const entityToSection = {};
|
|
75
|
+
for (const section of prd.architecture.sections) {
|
|
76
|
+
for (const resource of section.resources ?? []) {
|
|
77
|
+
if (resource.entity) {
|
|
78
|
+
entityToSection[resource.entity] = section.code;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Orphan entities (not in any section) go into Phase 0
|
|
84
|
+
const allEntities = prd.architecture.entities?.map(e => e.name) ?? [];
|
|
85
|
+
const orphans = allEntities.filter(e => !entityToSection[e]);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 3. FK Dependency Resolution
|
|
91
|
+
|
|
92
|
+
Build section dependency graph from `prd.architecture.entities[].relationships[]`:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
const sectionDeps = {}; // sectionCode -> Set<sectionCode>
|
|
96
|
+
for (const entity of prd.architecture.entities ?? []) {
|
|
97
|
+
const entitySection = entityToSection[entity.name];
|
|
98
|
+
if (!entitySection) continue;
|
|
99
|
+
|
|
100
|
+
for (const rel of entity.relationships ?? []) {
|
|
101
|
+
const targetSection = entityToSection[rel.target];
|
|
102
|
+
// If target is in a DIFFERENT section, this section depends on that one
|
|
103
|
+
if (targetSection && targetSection !== entitySection) {
|
|
104
|
+
sectionDeps[entitySection] = sectionDeps[entitySection] || new Set();
|
|
105
|
+
sectionDeps[entitySection].add(targetSection);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 4. Topological Sort of Sections
|
|
114
|
+
|
|
115
|
+
Order sections by FK dependencies (sections with no deps first):
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
function topoSort(sections, sectionDeps) {
|
|
119
|
+
const sorted = [];
|
|
120
|
+
const visited = new Set();
|
|
121
|
+
const visiting = new Set();
|
|
122
|
+
|
|
123
|
+
function visit(code) {
|
|
124
|
+
if (visited.has(code)) return;
|
|
125
|
+
if (visiting.has(code)) {
|
|
126
|
+
// Circular dependency — break cycle, include as-is
|
|
127
|
+
console.warn(`Circular section dependency detected involving: ${code}`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
visiting.add(code);
|
|
131
|
+
for (const dep of sectionDeps[code] || []) {
|
|
132
|
+
visit(dep);
|
|
133
|
+
}
|
|
134
|
+
visiting.delete(code);
|
|
135
|
+
visited.add(code);
|
|
136
|
+
sorted.push(code);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const section of sections) {
|
|
140
|
+
visit(section.code);
|
|
141
|
+
}
|
|
142
|
+
return sorted;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const orderedSections = topoSort(prd.architecture.sections, sectionDeps);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 5. Phase Construction
|
|
151
|
+
|
|
152
|
+
### 5a. Phase 0 — Foundation PRD
|
|
153
|
+
|
|
154
|
+
Copy the master PRD and keep ONLY foundation tasks:
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const phase0Prd = deepClone(prd);
|
|
158
|
+
phase0Prd.tasks = prd.tasks.filter(t =>
|
|
159
|
+
t.category === 'domain' ||
|
|
160
|
+
t.category === 'infrastructure' ||
|
|
161
|
+
// Module-level seed data (NavigationApplicationSeedData, NavigationModuleSeedData)
|
|
162
|
+
(t.category === 'seedData' && !t.section)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Remove section-specific content from architecture (keep all entities for EF)
|
|
166
|
+
// Keep ALL entities, ALL relationships — Phase 0 needs the full domain model
|
|
167
|
+
|
|
168
|
+
phase0Prd._phaseInfo = { phase: 0, type: 'foundation' };
|
|
169
|
+
writeJSON(`.ralph/prd-${moduleCode}-phase0.json`, phase0Prd);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Phase 0 includes:**
|
|
173
|
+
- ALL `domain` tasks (entity classes, enums, value objects)
|
|
174
|
+
- ALL `infrastructure` tasks (EF configs, DbContext, migration)
|
|
175
|
+
- Module-level `seedData` tasks (NavigationApplication, NavigationModule seed data)
|
|
176
|
+
|
|
177
|
+
**Phase 0 excludes:**
|
|
178
|
+
- `application` tasks (services, DTOs, validators)
|
|
179
|
+
- `api` tasks (controllers)
|
|
180
|
+
- `frontend` tasks (pages, routes, i18n)
|
|
181
|
+
- `test` tasks
|
|
182
|
+
- Section-level `seedData` tasks (NavigationSection, permissions per section)
|
|
183
|
+
|
|
184
|
+
### 5b. Section Phase PRDs (Phase 1..N)
|
|
185
|
+
|
|
186
|
+
For each section in topological order:
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
for (let i = 0; i < orderedSections.length; i++) {
|
|
190
|
+
const sectionCode = orderedSections[i];
|
|
191
|
+
const phaseNum = i + 1;
|
|
192
|
+
|
|
193
|
+
const sectionPrd = deepClone(prd);
|
|
194
|
+
|
|
195
|
+
// Filter tasks by section
|
|
196
|
+
sectionPrd.tasks = prd.tasks.filter(t =>
|
|
197
|
+
t.section === sectionCode && (
|
|
198
|
+
t.category === 'application' ||
|
|
199
|
+
t.category === 'api' ||
|
|
200
|
+
t.category === 'frontend' ||
|
|
201
|
+
t.category === 'i18n' ||
|
|
202
|
+
t.category === 'test' ||
|
|
203
|
+
t.category === 'validation' ||
|
|
204
|
+
// Section-level seed data
|
|
205
|
+
(t.category === 'seedData' && t.section === sectionCode)
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Filter architecture to section-relevant entities only
|
|
210
|
+
const sectionEntities = new Set();
|
|
211
|
+
const section = prd.architecture.sections.find(s => s.code === sectionCode);
|
|
212
|
+
for (const resource of section?.resources ?? []) {
|
|
213
|
+
if (resource.entity) sectionEntities.add(resource.entity);
|
|
214
|
+
}
|
|
215
|
+
sectionPrd.architecture.entities = prd.architecture.entities?.filter(
|
|
216
|
+
e => sectionEntities.has(e.name)
|
|
217
|
+
) ?? [];
|
|
218
|
+
sectionPrd.architecture.sections = [section];
|
|
219
|
+
|
|
220
|
+
// Filter seedData to section-relevant parts
|
|
221
|
+
if (sectionPrd.seedData?.coreSeedData) {
|
|
222
|
+
const csd = sectionPrd.seedData.coreSeedData;
|
|
223
|
+
if (csd.navigationSections) {
|
|
224
|
+
csd.navigationSections = csd.navigationSections.filter(
|
|
225
|
+
s => s.code === sectionCode
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (csd.navigationResources) {
|
|
229
|
+
csd.navigationResources = csd.navigationResources.filter(
|
|
230
|
+
r => sectionEntities.has(r.entity)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Resolve phase dependencies
|
|
236
|
+
const deps = [0]; // All sections depend on Phase 0
|
|
237
|
+
for (const depSection of sectionDeps[sectionCode] || []) {
|
|
238
|
+
const depPhaseNum = orderedSections.indexOf(depSection) + 1;
|
|
239
|
+
if (depPhaseNum > 0) deps.push(depPhaseNum);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
sectionPrd._phaseInfo = { phase: phaseNum, type: 'section', sectionCode };
|
|
243
|
+
writeJSON(`.ralph/prd-${moduleCode}-section-${sectionCode}.json`, sectionPrd);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 5c. Orphan Entities
|
|
248
|
+
|
|
249
|
+
Entities not mapped to any section are included in Phase 0 with their services/controllers:
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
if (orphans.length > 0) {
|
|
253
|
+
// Add orphan application/api/frontend tasks to Phase 0 PRD
|
|
254
|
+
const orphanTasks = prd.tasks.filter(t =>
|
|
255
|
+
!t.section && (
|
|
256
|
+
t.category === 'application' ||
|
|
257
|
+
t.category === 'api' ||
|
|
258
|
+
t.category === 'frontend' ||
|
|
259
|
+
t.category === 'test'
|
|
260
|
+
)
|
|
261
|
+
);
|
|
262
|
+
phase0Prd.tasks.push(...orphanTasks);
|
|
263
|
+
writeJSON(`.ralph/prd-${moduleCode}-phase0.json`, phase0Prd);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 6. Build _sectionSplit State
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
const phases = [
|
|
273
|
+
{
|
|
274
|
+
phase: 0,
|
|
275
|
+
type: 'foundation',
|
|
276
|
+
sectionCode: undefined,
|
|
277
|
+
entities: allEntities,
|
|
278
|
+
prdFile: `.ralph/prd-${moduleCode}-phase0.json`,
|
|
279
|
+
status: 'pending',
|
|
280
|
+
dependsOn: []
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < orderedSections.length; i++) {
|
|
285
|
+
const sectionCode = orderedSections[i];
|
|
286
|
+
const sectionEntities = [];
|
|
287
|
+
const section = prd.architecture.sections.find(s => s.code === sectionCode);
|
|
288
|
+
for (const resource of section?.resources ?? []) {
|
|
289
|
+
if (resource.entity) sectionEntities.push(resource.entity);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const deps = [0]; // Phase 0
|
|
293
|
+
for (const depSection of sectionDeps[sectionCode] || []) {
|
|
294
|
+
const depPhaseNum = orderedSections.indexOf(depSection) + 1;
|
|
295
|
+
if (depPhaseNum > 0) deps.push(depPhaseNum);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
phases.push({
|
|
299
|
+
phase: i + 1,
|
|
300
|
+
type: 'section',
|
|
301
|
+
sectionCode,
|
|
302
|
+
entities: sectionEntities,
|
|
303
|
+
prdFile: `.ralph/prd-${moduleCode}-section-${sectionCode}.json`,
|
|
304
|
+
status: 'pending',
|
|
305
|
+
dependsOn: deps
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
prd._sectionSplit = {
|
|
310
|
+
enabled: true,
|
|
311
|
+
phases,
|
|
312
|
+
currentPhase: 0,
|
|
313
|
+
entityToSection
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
writeJSON('.ralph/prd.json', prd);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## 7. Execution (in compact-loop.md)
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
// Find next pending phase with dependencies met
|
|
325
|
+
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
|
|
326
|
+
if (!nextPhase) {
|
|
327
|
+
// All phases done — fall through to standard completion check
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check phase dependencies
|
|
332
|
+
const depsOk = nextPhase.dependsOn.every(depIdx =>
|
|
333
|
+
prd._sectionSplit.phases[depIdx].status === 'completed'
|
|
334
|
+
);
|
|
335
|
+
if (!depsOk) {
|
|
336
|
+
// Phase blocked — this should not happen with topological sort
|
|
337
|
+
console.warn(`Phase ${nextPhase.phase} blocked by incomplete dependencies`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// INVOKE /apex -d {nextPhase.prdFile}
|
|
342
|
+
// apex sees a normal PRD (smaller scope) and executes normally
|
|
343
|
+
|
|
344
|
+
// After apex returns:
|
|
345
|
+
nextPhase.status = 'completed';
|
|
346
|
+
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
347
|
+
writeJSON('.ralph/prd.json', prd);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 8. Result Merging
|
|
353
|
+
|
|
354
|
+
After each phase apex call, merge task statuses back into the master PRD:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
const phasePrd = readJSON(nextPhase.prdFile);
|
|
358
|
+
|
|
359
|
+
// Map phase task statuses back to master PRD
|
|
360
|
+
for (const phaseTask of phasePrd.tasks) {
|
|
361
|
+
const masterTask = prd.tasks.find(t => t.id === phaseTask.id);
|
|
362
|
+
if (masterTask) {
|
|
363
|
+
masterTask.status = phaseTask.status;
|
|
364
|
+
masterTask.completed_at = phaseTask.completed_at;
|
|
365
|
+
masterTask.commit_hash = phaseTask.commit_hash;
|
|
366
|
+
masterTask.files_changed = phaseTask.files_changed;
|
|
367
|
+
masterTask.error = phaseTask.error;
|
|
368
|
+
masterTask.validation = phaseTask.validation;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
writeJSON('.ralph/prd.json', prd);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 9. Phase Failure Handling
|
|
378
|
+
|
|
379
|
+
If a phase fails (apex returns with failed tasks):
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
if (phasePrd.tasks.some(t => t.status === 'failed')) {
|
|
383
|
+
nextPhase.status = 'failed';
|
|
384
|
+
|
|
385
|
+
// Check retries
|
|
386
|
+
const retryCount = nextPhase._retryCount || 0;
|
|
387
|
+
if (retryCount < 2) {
|
|
388
|
+
nextPhase.status = 'pending';
|
|
389
|
+
nextPhase._retryCount = retryCount + 1;
|
|
390
|
+
console.log(`Phase ${nextPhase.phase} failed — retry ${retryCount + 1}/2`);
|
|
391
|
+
|
|
392
|
+
// Reset failed tasks in phase PRD
|
|
393
|
+
for (const task of phasePrd.tasks) {
|
|
394
|
+
if (task.status === 'failed') {
|
|
395
|
+
task.status = 'pending';
|
|
396
|
+
task.error = null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
writeJSON(nextPhase.prdFile, phasePrd);
|
|
400
|
+
} else {
|
|
401
|
+
console.error(`Phase ${nextPhase.phase} failed after 2 retries — marking failed`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 10. Cleanup (in step-05-report.md)
|
|
409
|
+
|
|
410
|
+
After all phases complete or on final report:
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
if (prd._sectionSplit?.enabled) {
|
|
414
|
+
// Delete temporary phase PRD files
|
|
415
|
+
for (const phase of prd._sectionSplit.phases) {
|
|
416
|
+
if (fileExists(phase.prdFile) && phase.prdFile !== '.ralph/prd.json') {
|
|
417
|
+
deleteFile(phase.prdFile);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Remove split state from master PRD
|
|
421
|
+
delete prd._sectionSplit;
|
|
422
|
+
writeJSON('.ralph/prd.json', prd);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Summary Table
|
|
429
|
+
|
|
430
|
+
| Phase | Content | Depends On | Entities |
|
|
431
|
+
|-------|---------|------------|----------|
|
|
432
|
+
| Phase 0 | ALL domain + infrastructure + migration + module seed data | — | ALL |
|
|
433
|
+
| Phase 1 | Section A: services, controllers, seed, pages, tests | Phase 0 | Section A entities |
|
|
434
|
+
| Phase 2 | Section B: idem | Phase 0 (+ Phase 1 if FK) | Section B entities |
|
|
435
|
+
| Phase N | Section N: idem | Phase 0 (+ earlier phases if FK) | Section N entities |
|
|
436
|
+
| Final | Cross-validation (dotnet build, typecheck, MCP) | All phases | — |
|
|
437
|
+
|
|
438
|
+
**Threshold:** `> 4 domain tasks` AND `> 1 section`
|
|
439
|
+
**Backward compatible:** `<= 4 domain tasks` OR `1 section` → no splitting, zero impact
|
|
@@ -261,6 +261,33 @@ if (missingCategories.length > 0) {
|
|
|
261
261
|
|
|
262
262
|
**Why this matters:** In test-v4-005, the PRD was generated with only backend categories (domain, infrastructure, application, api, seedData, validation). The frontend and test categories were entirely absent, resulting in 0 frontend pages and 0 tests generated across 3 modules.
|
|
263
263
|
|
|
264
|
+
### 4c. Section Split Detection (Large Module)
|
|
265
|
+
|
|
266
|
+
> **Purpose:** Modules with >4 domain entities overwhelm a single `/apex -d` call.
|
|
267
|
+
> Split execution into Phase 0 (foundation) + Phase 1..N (per section).
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
const domainTasks = prd.tasks.filter(t => t.category === 'domain');
|
|
271
|
+
const sections = prd.architecture?.sections ?? [];
|
|
272
|
+
|
|
273
|
+
if (domainTasks.length > 4 && sections.length > 1 && !prd._sectionSplit?.enabled) {
|
|
274
|
+
console.log(`SECTION SPLIT: ${domainTasks.length} domain tasks, ${sections.length} sections → activating`);
|
|
275
|
+
|
|
276
|
+
// Read references/section-splitting.md for full logic
|
|
277
|
+
// 1. Build entity-to-section mapping from architecture.sections[].resources[].entity
|
|
278
|
+
// 2. Build section dependency graph from architecture.entities[].relationships[]
|
|
279
|
+
// 3. Topological sort sections by FK dependencies
|
|
280
|
+
// 4. Create Phase 0 PRD (all domain + infrastructure + module seed data)
|
|
281
|
+
// 5. Create Section PRDs (per-section: application + api + seedData + frontend + i18n + test)
|
|
282
|
+
// 6. Set prd._sectionSplit state
|
|
283
|
+
|
|
284
|
+
// LOAD references/section-splitting.md — execute sections 2-6
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Activation threshold:** `> 4 domain tasks` AND `> 1 architecture section`
|
|
289
|
+
**No-op when:** `<= 4 domain tasks` OR `<= 1 section` OR `_sectionSplit.enabled` already set
|
|
290
|
+
|
|
264
291
|
## 5. Find Current Task
|
|
265
292
|
|
|
266
293
|
```javascript
|
|
@@ -52,13 +52,57 @@ writeJSON('.ralph/prd.json', prd);
|
|
|
52
52
|
|
|
53
53
|
### 3. Delegate to /apex
|
|
54
54
|
|
|
55
|
+
#### 3a. Section-Split Mode
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
if (prd._sectionSplit?.enabled) {
|
|
59
|
+
// Find next pending phase with dependencies met
|
|
60
|
+
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
|
|
61
|
+
|
|
62
|
+
if (!nextPhase) {
|
|
63
|
+
// All phases done — skip to step-04 completion check
|
|
64
|
+
goto STEP_04;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const depsOk = nextPhase.dependsOn.every(depIdx =>
|
|
68
|
+
prd._sectionSplit.phases[depIdx].status === 'completed'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!depsOk) {
|
|
72
|
+
console.warn(`Phase ${nextPhase.phase} blocked by incomplete dependencies`);
|
|
73
|
+
goto STEP_04;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const phaseLabel = nextPhase.type === 'foundation'
|
|
77
|
+
? 'Phase 0: Foundation (all entities + migration + module seed data)'
|
|
78
|
+
: `Phase ${nextPhase.phase}: Section "${nextPhase.sectionCode}" (services + controllers + pages + tests)`;
|
|
79
|
+
console.log(`SECTION SPLIT: ${phaseLabel}`);
|
|
80
|
+
|
|
81
|
+
// INVOKE /apex -d {nextPhase.prdFile}
|
|
82
|
+
// Apex sees a normal (smaller) PRD and executes normally
|
|
83
|
+
|
|
84
|
+
// After apex returns: merge results into master PRD
|
|
85
|
+
// Read references/section-splitting.md sections 7-9 (execution, merging, failure handling)
|
|
86
|
+
|
|
87
|
+
nextPhase.status = 'completed';
|
|
88
|
+
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
89
|
+
writeJSON('.ralph/prd.json', prd);
|
|
90
|
+
|
|
91
|
+
// Continue to step-03 (commit), then step-04 loops back for next phase
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> See `references/section-splitting.md` for full detection, mapping, PRD construction, and merge logic.
|
|
96
|
+
|
|
97
|
+
#### 3b. Standard Mode (no split)
|
|
98
|
+
|
|
55
99
|
**INVOKE `/apex -d .ralph/prd.json`**
|
|
56
100
|
|
|
57
101
|
This delegates ALL remaining tasks for the current module to apex:
|
|
58
102
|
|
|
59
103
|
- Apex reads the PRD, extracts context (`context_code`, `app_name`, `module_code`, `entities`, `sections`)
|
|
60
104
|
- Apex executes ALL layers: domain → infrastructure → migration → application → api → seed data → frontend → tests
|
|
61
|
-
- Apex runs full POST-CHECKs (
|
|
105
|
+
- Apex runs full POST-CHECKs (50 checks from `references/post-checks.md`)
|
|
62
106
|
- Apex commits per layer (atomic commits)
|
|
63
107
|
- Apex validates with MCP (`validate_conventions`)
|
|
64
108
|
- Apex updates task statuses in the PRD file directly
|
|
@@ -97,6 +97,15 @@ Write to `.ralph/reports/{feature-slug}.md`:
|
|
|
97
97
|
**{completedModules}/{totalModules} modules completed**
|
|
98
98
|
{end if}
|
|
99
99
|
|
|
100
|
+
{if prd._sectionSplit?.enabled:}
|
|
101
|
+
## Section Split Execution
|
|
102
|
+
| Phase | Type | Section | Entities | Status |
|
|
103
|
+
|-------|------|---------|----------|--------|
|
|
104
|
+
{for each phase:}
|
|
105
|
+
| {phase} | {type} | {sectionCode || '-'} | {entities.length} | {status} |
|
|
106
|
+
|
|
107
|
+
{end if}
|
|
108
|
+
|
|
100
109
|
## Test Metrics
|
|
101
110
|
| Metric | Value |
|
|
102
111
|
|--------|-------|
|
|
@@ -121,6 +130,16 @@ Write to `.ralph/reports/{feature-slug}.md`:
|
|
|
121
130
|
## 3. Finalize
|
|
122
131
|
|
|
123
132
|
```javascript
|
|
133
|
+
// Clean up section-split temporary files
|
|
134
|
+
if (prd._sectionSplit?.enabled) {
|
|
135
|
+
for (const phase of prd._sectionSplit.phases) {
|
|
136
|
+
if (fileExists(phase.prdFile) && phase.prdFile !== '.ralph/prd.json') {
|
|
137
|
+
deleteFile(phase.prdFile);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
delete prd._sectionSplit;
|
|
141
|
+
}
|
|
142
|
+
|
|
124
143
|
prd.updated_at = new Date().toISOString();
|
|
125
144
|
writeJSON('.ralph/prd.json', prd);
|
|
126
145
|
```
|