# Section-Level Splitting — Large Module Handling

> **Loaded by:** step-01 section 4c (detection) and compact-loop.md (execution)
> **Purpose:** When a module has >4 domain entities AND >1 architecture section,
> split `/apex -d` calls per SECTION instead of sending the whole module at once.
> **Threshold:** `domainTaskCount > 4 AND sectionCount > 1`
> **Backward compatible:** Modules with <=4 entities are UNAFFECTED.

---

## Why Section-Level Splitting?

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%.

**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.

---

## Architecture: Phases

```
Phase 0: FOUNDATION (1 apex call)
  - ALL entity classes (Domain layer)
  - ALL EF configurations (Infrastructure layer)
  - ONE migration covering ALL entities
  - Application + Module navigation seed data
  - DbContext registration + DI skeleton
  -> Eliminates migration conflicts between sections

Phase 1..N: PER SECTION (1 apex call each)
  - Services + DTOs + Validators for section entities
  - Controllers for section entities
  - Section-level seed data (NavigationSection, permissions, roles)
  - Frontend pages (List, Detail, Create, Edit) for section entities
  - i18n for section entities
  - Tests for section entities

Phase Final: CROSS-VALIDATION (inline in step-04)
  - dotnet build (full project)
  - npm run typecheck
  - MCP validate_conventions
```

### Why ALL Entities in Phase 0?

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.

---

## 1. Detection

```javascript
const prd = readJSON(currentPrdPath);
const domainTasks = prd.tasks.filter(t => t.category === 'domain');
const sections = prd.architecture?.sections ?? [];

const shouldSplit = domainTasks.length > 4 && sections.length > 1;

if (!shouldSplit) {
  // Standard execution — no splitting needed
  return;
}

console.log(`SECTION SPLIT: ${domainTasks.length} domain tasks, ${sections.length} sections → activating`);
```

---

## 2. Entity-to-Section Mapping

Build from `prd.architecture.sections[].resources[].entity`:

```javascript
const entityToSection = {};
for (const section of prd.architecture.sections) {
  for (const resource of section.resources ?? []) {
    if (resource.entity) {
      entityToSection[resource.entity] = section.code;
    }
  }
}

// Orphan entities (not in any section) go into Phase 0
const allEntities = prd.architecture.entities?.map(e => e.name) ?? [];
const orphans = allEntities.filter(e => !entityToSection[e]);
```

---

## 3. FK Dependency Resolution

Build section dependency graph from `prd.architecture.entities[].relationships[]`:

```javascript
const sectionDeps = {}; // sectionCode -> Set<sectionCode>
for (const entity of prd.architecture.entities ?? []) {
  const entitySection = entityToSection[entity.name];
  if (!entitySection) continue;

  for (const rel of entity.relationships ?? []) {
    const targetSection = entityToSection[rel.target];
    // If target is in a DIFFERENT section, this section depends on that one
    if (targetSection && targetSection !== entitySection) {
      sectionDeps[entitySection] = sectionDeps[entitySection] || new Set();
      sectionDeps[entitySection].add(targetSection);
    }
  }
}
```

---

## 4. Topological Sort of Sections

Order sections by FK dependencies (sections with no deps first):

```javascript
function topoSort(sections, sectionDeps) {
  const sorted = [];
  const visited = new Set();
  const visiting = new Set();

  function visit(code) {
    if (visited.has(code)) return;
    if (visiting.has(code)) {
      // Circular dependency — break cycle, include as-is
      console.warn(`Circular section dependency detected involving: ${code}`);
      return;
    }
    visiting.add(code);
    for (const dep of sectionDeps[code] || []) {
      visit(dep);
    }
    visiting.delete(code);
    visited.add(code);
    sorted.push(code);
  }

  for (const section of sections) {
    visit(section.code);
  }
  return sorted;
}

const orderedSections = topoSort(prd.architecture.sections, sectionDeps);
```

---

## 5. Phase Construction

### 5a. Phase 0 — Foundation PRD

Copy the master PRD and keep ONLY foundation tasks:

```javascript
const phase0Prd = deepClone(prd);
phase0Prd.tasks = prd.tasks.filter(t =>
  t.category === 'domain' ||
  t.category === 'infrastructure' ||
  // Module-level seed data (NavigationApplicationSeedData, NavigationModuleSeedData)
  (t.category === 'seedData' && !t.section)
);

// Remove section-specific content from architecture (keep all entities for EF)
// Keep ALL entities, ALL relationships — Phase 0 needs the full domain model

phase0Prd._phaseInfo = { phase: 0, type: 'foundation' };
writeJSON(`.ralph/prd-${moduleCode}-phase0.json`, phase0Prd);
```

**Phase 0 includes:**
- ALL `domain` tasks (entity classes, enums, value objects)
- ALL `infrastructure` tasks (EF configs, DbContext, migration)
- Module-level `seedData` tasks (NavigationApplication, NavigationModule seed data)

**Phase 0 excludes:**
- `application` tasks (services, DTOs, validators)
- `api` tasks (controllers)
- `frontend` tasks (pages, routes, i18n)
- `test` tasks
- Section-level `seedData` tasks (NavigationSection, permissions per section)

### 5b. Section Phase PRDs (Phase 1..N)

For each section in topological order:

```javascript
for (let i = 0; i < orderedSections.length; i++) {
  const sectionCode = orderedSections[i];
  const phaseNum = i + 1;

  const sectionPrd = deepClone(prd);

  // Filter tasks by section
  sectionPrd.tasks = prd.tasks.filter(t =>
    t.section === sectionCode && (
      t.category === 'application' ||
      t.category === 'api' ||
      t.category === 'frontend' ||
      t.category === 'i18n' ||
      t.category === 'test' ||
      t.category === 'validation' ||
      // Section-level seed data
      (t.category === 'seedData' && t.section === sectionCode)
    )
  );

  // Filter architecture to section-relevant entities only
  const sectionEntities = new Set();
  const section = prd.architecture.sections.find(s => s.code === sectionCode);
  for (const resource of section?.resources ?? []) {
    if (resource.entity) sectionEntities.add(resource.entity);
  }
  sectionPrd.architecture.entities = prd.architecture.entities?.filter(
    e => sectionEntities.has(e.name)
  ) ?? [];
  sectionPrd.architecture.sections = [section];

  // Filter seedData to section-relevant parts
  if (sectionPrd.seedData?.coreSeedData) {
    const csd = sectionPrd.seedData.coreSeedData;
    if (csd.navigationSections) {
      csd.navigationSections = csd.navigationSections.filter(
        s => s.code === sectionCode
      );
    }
    if (csd.navigationResources) {
      csd.navigationResources = csd.navigationResources.filter(
        r => sectionEntities.has(r.entity)
      );
    }
  }

  // Resolve phase dependencies
  const deps = [0]; // All sections depend on Phase 0
  for (const depSection of sectionDeps[sectionCode] || []) {
    const depPhaseNum = orderedSections.indexOf(depSection) + 1;
    if (depPhaseNum > 0) deps.push(depPhaseNum);
  }

  sectionPrd._phaseInfo = { phase: phaseNum, type: 'section', sectionCode };
  writeJSON(`.ralph/prd-${moduleCode}-section-${sectionCode}.json`, sectionPrd);
}
```

### 5c. Orphan Entities

Entities not mapped to any section are included in Phase 0 with their services/controllers:

```javascript
if (orphans.length > 0) {
  // Add orphan application/api/frontend tasks to Phase 0 PRD
  const orphanTasks = prd.tasks.filter(t =>
    !t.section && (
      t.category === 'application' ||
      t.category === 'api' ||
      t.category === 'frontend' ||
      t.category === 'test'
    )
  );
  phase0Prd.tasks.push(...orphanTasks);
  writeJSON(`.ralph/prd-${moduleCode}-phase0.json`, phase0Prd);
}
```

---

## 6. Build _sectionSplit State

```javascript
const phases = [
  {
    phase: 0,
    type: 'foundation',
    sectionCode: undefined,
    entities: allEntities,
    prdFile: `.ralph/prd-${moduleCode}-phase0.json`,
    status: 'pending',
    dependsOn: []
  }
];

for (let i = 0; i < orderedSections.length; i++) {
  const sectionCode = orderedSections[i];
  const sectionEntities = [];
  const section = prd.architecture.sections.find(s => s.code === sectionCode);
  for (const resource of section?.resources ?? []) {
    if (resource.entity) sectionEntities.push(resource.entity);
  }

  const deps = [0]; // Phase 0
  for (const depSection of sectionDeps[sectionCode] || []) {
    const depPhaseNum = orderedSections.indexOf(depSection) + 1;
    if (depPhaseNum > 0) deps.push(depPhaseNum);
  }

  phases.push({
    phase: i + 1,
    type: 'section',
    sectionCode,
    entities: sectionEntities,
    prdFile: `.ralph/prd-${moduleCode}-section-${sectionCode}.json`,
    status: 'pending',
    dependsOn: deps
  });
}

prd._sectionSplit = {
  enabled: true,
  phases,
  currentPhase: 0,
  entityToSection
};

writeJSON(currentPrdPath, prd);
```

---

## 7. Execution (in compact-loop.md)

```javascript
// Find next pending phase with dependencies met
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
if (!nextPhase) {
  // All phases done — fall through to standard completion check
  return;
}

// Check phase dependencies
const depsOk = nextPhase.dependsOn.every(depIdx =>
  prd._sectionSplit.phases[depIdx].status === 'completed'
);
if (!depsOk) {
  // Phase blocked — this should not happen with topological sort
  console.warn(`Phase ${nextPhase.phase} blocked by incomplete dependencies`);
  return;
}

// INVOKE /apex -d {nextPhase.prdFile}
// apex sees a normal PRD (smaller scope) and executes normally

// After apex returns:
nextPhase.status = 'completed';
prd._sectionSplit.currentPhase = nextPhase.phase;
writeJSON(currentPrdPath, prd);
```

---

## 8. Result Merging

After each phase apex call, merge task statuses back into the master PRD:

```javascript
const phasePrd = readJSON(nextPhase.prdFile);

// Map phase task statuses back to master PRD
for (const phaseTask of phasePrd.tasks) {
  const masterTask = prd.tasks.find(t => t.id === phaseTask.id);
  if (masterTask) {
    masterTask.status = phaseTask.status;
    masterTask.completed_at = phaseTask.completed_at;
    masterTask.commit_hash = phaseTask.commit_hash;
    masterTask.files_changed = phaseTask.files_changed;
    masterTask.error = phaseTask.error;
    masterTask.validation = phaseTask.validation;
  }
}

writeJSON(currentPrdPath, prd);
```

---

## 9. Phase Failure Handling

If a phase fails (apex returns with failed tasks):

```javascript
if (phasePrd.tasks.some(t => t.status === 'failed')) {
  nextPhase.status = 'failed';

  // Check retries
  const retryCount = nextPhase._retryCount || 0;
  if (retryCount < 2) {
    nextPhase.status = 'pending';
    nextPhase._retryCount = retryCount + 1;
    console.log(`Phase ${nextPhase.phase} failed — retry ${retryCount + 1}/2`);

    // Reset failed tasks in phase PRD
    for (const task of phasePrd.tasks) {
      if (task.status === 'failed') {
        task.status = 'pending';
        task.error = null;
      }
    }
    writeJSON(nextPhase.prdFile, phasePrd);
  } else {
    console.error(`Phase ${nextPhase.phase} failed after 2 retries — marking failed`);
  }
}
```

---

## 10. Cleanup (in step-05-report.md)

After all phases complete or on final report:

```javascript
if (prd._sectionSplit?.enabled) {
  // Delete temporary phase PRD files
  for (const phase of prd._sectionSplit.phases) {
    if (fileExists(phase.prdFile) && phase.prdFile !== currentPrdPath) {
      deleteFile(phase.prdFile);
    }
  }
  // Remove split state from master PRD
  delete prd._sectionSplit;
  writeJSON(currentPrdPath, prd);
}
```

---

## Summary Table

| Phase | Content | Depends On | Entities |
|-------|---------|------------|----------|
| Phase 0 | ALL domain + infrastructure + migration + module seed data | — | ALL |
| Phase 1 | Section A: services, controllers, seed, pages, tests | Phase 0 | Section A entities |
| Phase 2 | Section B: idem | Phase 0 (+ Phase 1 if FK) | Section B entities |
| Phase N | Section N: idem | Phase 0 (+ earlier phases if FK) | Section N entities |
| Final | Cross-validation (dotnet build, typecheck, MCP) | All phases | — |

**Threshold:** `> 4 domain tasks` AND `> 1 section`
**Backward compatible:** `<= 4 domain tasks` OR `1 section` → no splitting, zero impact
