# Module Transition Logic

> **Reference for:** step-04-check.md section 3b
> **Trigger:** Multi-module mode + current module complete
> **Purpose:** Advance to next module in queue OR finish if all done

---

## When Module Transition Occurs

```javascript
const queuePath = '.ralph/modules-queue.json';
if (fileExists(queuePath)) {
  const queue = readJSON(queuePath);
  const currentModule = queue.modules[queue.currentIndex];

  // MODULE COMPLETENESS CHECK
  const completedCats = new Set(prd.tasks.filter(t => t.status === 'completed').map(t => t.category));
  const expected = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'test'];
  const missing = expected.filter(c => !completedCats.has(c));

  if (missing.length > 0) {
    console.log(`Module ${currentModule.code}: missing categories ${missing.join(', ')} — continuing loop`);
    // Fall through to compact loop (section 5)
    return;
  }

  // All categories present — module is complete
  // Continue below...

  // HANDOFF FILE GATE (double-security — step-04 §1.8 is primary, this is defense-in-depth)
  // The build gate does NOT detect missing files if nothing references them (no compile error).
  const ftc = prd.implementation?.filesToCreate;
  if (ftc) {
    let ftcMissing = 0;
    const ftcDetails = [];
    for (const [cat, files] of Object.entries(ftc)) {
      for (const file of (files || [])) {
        if (!fileExists(file.path || file)) {
          ftcMissing++;
          ftcDetails.push(`${cat}: ${(file.path || file).split('/').pop()}`);
        }
      }
    }
    if (ftcMissing > 0) {
      console.error(`BLOCKING: ${ftcMissing} handoff files missing — cannot advance module`);
      ftcDetails.forEach(d => console.error(`  ${d}`));
      return; // Back to compact loop — remediation tasks already injected by category-completeness
    }
  }
}
```

---

## Advance to Next Module

```javascript
if (missing.length === 0) {
  // MANDATORY BUILD+TEST GATE before advancing module
  // (audit ba-002: module marked "100% complete" despite build errors and security flaws)
  const buildOk = execSync('dotnet build --no-restore --verbosity quiet').exitCode === 0;
  if (!buildOk) {
    console.error(`BLOCKING: Module ${currentModule.code} build fails — cannot advance to next module`);
    const maxId = Math.max(...prd.tasks.map(t => parseInt(t.id.replace(/\D/g,''))||0));
    prd.tasks.push({
      id: `GATE-${maxId+1}`, description: `BLOCKING: Fix build for module ${currentModule.code}`,
      status: 'pending', category: 'validation', dependencies: [],
      acceptance_criteria: 'dotnet build passes with 0 errors'
    });
    writeJSON(currentPrdPath, prd);
    return; // Back to compact loop — do NOT advance
  }

  const testOk = execSync('dotnet test --no-build --verbosity quiet').exitCode === 0;
  if (!testOk) {
    console.error(`BLOCKING: Module ${currentModule.code} tests fail — cannot advance to next module`);
    const maxId = Math.max(...prd.tasks.map(t => parseInt(t.id.replace(/\D/g,''))||0));
    prd.tasks.push({
      id: `GATE-${maxId+1}`, description: `BLOCKING: Fix tests for module ${currentModule.code}`,
      status: 'pending', category: 'validation', dependencies: [],
      acceptance_criteria: 'dotnet test passes with 0 failures'
    });
    writeJSON(currentPrdPath, prd);
    return; // Back to compact loop — do NOT advance
  }

  console.log(`MODULE GATE PASSED: ${currentModule.code} — build OK, tests OK`);

  // Mark current module done
  currentModule.status = 'completed';
  queue.completedModules++;
  const nextIndex = queue.currentIndex + 1;

  if (nextIndex < queue.totalModules) {
    // More modules to process
    queue.currentIndex = nextIndex;
    queue.modules[nextIndex].status = 'in-progress';
    writeJSON(queuePath, queue);

    // Signal module transition to step-01
    writeJSON('.ralph/module-changed.json', {
      fromModule: currentModule.code,
      toModule: queue.modules[nextIndex].code,
      timestamp: new Date().toISOString()
    });

    // Mark current PRD as complete (write to module-specific file, NOT .ralph/prd.json)
    prd.status = 'completed';
    writeJSON(currentModule.prdFile, prd);

    // Prepare next module's PRD (work directly with prd-{moduleCode}.json)
    const nextPrd = readJSON(queue.modules[nextIndex].prdFile);
    nextPrd.config.current_iteration = 1;
    nextPrd.config.max_iterations = prd.config.max_iterations;
    writeJSON(queue.modules[nextIndex].prdFile, nextPrd);
    // NOTE: Ralph works directly with prd-{moduleCode}.json via currentPrdPath resolution.
    // No need to copy to .ralph/prd.json — this avoids overwriting between modules.

    console.log(`MODULE COMPLETE: ${currentModule.code} → NEXT: ${queue.modules[nextIndex].code}`);

    // CONTEXT REFRESH: Force /compact between modules to prevent context degradation
    // (audit ba-002: quality degraded progressively — module 2 had more errors than module 1
    // because patterns from module 1 were lost during context compaction)
    // STATE PERSISTENCE: Write state BEFORE compaction so position survives context compression
    writeJSON('.ralph/ralph-state.json', {
      currentStep: 'step-01-task',
      currentModule: queue.modules[nextIndex].code,
      reason: 'module-transition',
      fromModule: currentModule.code,
      timestamp: new Date().toISOString()
    });

    console.log('Forcing /compact for fresh context before next module...');
    // The /compact command clears accumulated tool results and intermediate reasoning,
    // preserving only key decisions and the current state. This ensures the next module
    // starts with a clean context window instead of degraded fragments from the previous module.
    // INVOKE /compact
    // After compaction: read .ralph/ralph-state.json to recover position if context lost

    // Return to step-01 to load next module's tasks
    // (step-01 will detect module-changed.json and load next PRD)
    return GO_TO_STEP_01;
  }

  // ALL MODULES COMPLETE
  // Continue to section 3c (completion output) → step-05
}
```

### MANDATORY POST-CHECK: modules-queue.json integrity

```javascript
// VERIFY: modules-queue.json updated correctly after advancement
const queueCheck = readJSON(queuePath);
const actualCompleted = queueCheck.modules.filter(m => m.status === 'completed').length;
if (queueCheck.completedModules !== actualCompleted) {
  console.error(`QUEUE SYNC ERROR: completedModules=${queueCheck.completedModules} but actual=${actualCompleted}`);
  queueCheck.completedModules = actualCompleted;
  writeJSON(queuePath, queueCheck);
  console.log('FIXED: completedModules synced to actual count');
}

if (nextIndex < queueCheck.totalModules) {
  if (queueCheck.currentIndex !== nextIndex) {
    console.error(`QUEUE SYNC ERROR: currentIndex=${queueCheck.currentIndex} but expected=${nextIndex}`);
    queueCheck.currentIndex = nextIndex;
    writeJSON(queuePath, queueCheck);
    console.log('FIXED: currentIndex synced');
  }
}
```

---

## Check: All Modules Complete

```javascript
if (nextIndex >= queue.totalModules) {
  // No more modules
  writeJSON(queuePath, queue);
  console.log(`ALL ${queue.totalModules} MODULES COMPLETE`);

  // Fall through to step 3c (completion output) → step-05
}
```

---

## Step-01 Module Detection

When returning to step-01 after module transition:

```javascript
// In step-01, section 0:
const moduleChangePath = '.ralph/module-changed.json';
if (fileExists(moduleChangePath)) {
  const transition = readJSON(moduleChangePath);
  console.log(`Module transition: ${transition.fromModule} → ${transition.toModule}`);
  deleteFile(moduleChangePath);

  // This is an exception to "Read Once" rule — PRD has changed
  // PROCEED with loading the new module's tasks
}
```

---

## State Transitions

```
[Phase A: Module 1]
├─ step-01: Load tasks from prd-module1.json
├─ step-02: Delegate to /apex
├─ step-03: Commit PRD state
├─ step-04: Check completion
│  └─ If all categories complete:
│     ├─ Write module-changed.json
│     ├─ Write next PRD to prd-{moduleCode}.json (via currentPrdPath)
│     └─ Return to step-01 ✓
│
└─ [Phase B: Module 2]
   ├─ step-01: Detect module-changed.json, load next PRD
   ├─ step-02: Delegate to /apex
   ├─ ... (repeat for module 2)
   │
   └─ [Module 2 complete]
      ├─ Mark module 2 done in modules-queue.json
      ├─ Check if more modules
      │  ├─ If yes: Transition to module 3 (goto Phase C)
      │  └─ If no: Fall through to step-05 ✓
      │
      └─ [Phase N: Final Module]
         ├─ ... (repeat)
         └─ ALL MODULES COMPLETE → step-05
```

---

## Rules

- **Module completeness check runs EVERY iteration:** Not just once
- **Exception to "Read Once":** When transitioning modules, step-01 re-reads the new PRD
- **Atomic module advancement:** Only transition when all required categories are complete
- **Max iterations applies globally:** Not per-module
- **State persistence:** modules-queue.json tracks progress across modules
