---
name: step-04-check
description: Check completion criteria with status-based logic
next_step: steps/step-05-report.md OR steps/step-01-task.md
---

# Step 4: Check Completion

## YOUR TASK:

Check if all tasks are complete and decide: output completion promise, advance module, or continue loop.

---

## 1. Read Current State

```javascript
const prd = readJSON('.ralph/prd.json');
const tasksCompleted = prd.tasks.filter(t => t.status === 'completed').length;
const tasksSkipped = prd.tasks.filter(t => t.status === 'skipped').length;
const tasksFailed = prd.tasks.filter(t => t.status === 'failed').length;
const tasksBlocked = prd.tasks.filter(t => t.status === 'blocked').length;
const tasksPending = prd.tasks.filter(t => t.status === 'pending').length;
const tasksTotal = prd.tasks.length;
const allDone = (tasksCompleted + tasksSkipped) === tasksTotal;
```

### 1.5. Regression Check (BLOCKING)

**Backend — build + tests:**
```bash
dotnet build --no-restore
if [ $? -ne 0 ]; then
  echo "BUILD REGRESSION — creating fix task"
  # Inject fix task, category: validation, acceptance: "dotnet build passes"
fi

TEST_PROJECT="tests/$(basename $(pwd)).Tests.Unit"
if [ -d "$TEST_PROJECT" ]; then
  dotnet test "$TEST_PROJECT" --no-build --verbosity normal
  if [ $? -ne 0 ]; then
    echo "TEST REGRESSION — creating fix task"
    prd.tasks.push({ id: maxId+1, description: "Fix test regression — all tests must pass",
      status: "pending", category: "validation", dependencies: [],
      acceptance_criteria: "dotnet test exits 0, all tests pass" });
    writeJSON('.ralph/prd.json', prd);
  fi
else
  # CRITICAL: Test project MUST exist if test tasks are marked completed
  const testTasksDone = prd.tasks.filter(t => t.category === 'test' && t.status === 'completed');
  if (testTasksDone.length > 0) {
    echo "ARTIFACT MISSING — test tasks marked completed but no test project exists"
    // Reset test tasks to pending and inject guardrail
    testTasksDone.forEach(t => { t.status = 'pending'; t.error = null; t.completed_at = null; });
    writeJSON('.ralph/prd.json', prd);
  fi
fi
```

**Frontend — typecheck + lint:**
```bash
if [ -f "web/package.json" ] || [ -f "package.json" ]; then
  npm run typecheck
  if [ $? -ne 0 ]; then
    echo "FRONTEND REGRESSION — creating fix task"
    prd.tasks.push({ id: maxId+1, description: "Fix frontend typecheck errors",
      status: "pending", category: "frontend", dependencies: [],
      acceptance_criteria: "npm run typecheck exits 0" });
    writeJSON('.ralph/prd.json', prd);
  fi
fi
```

## 2. Check Iteration Limit

If `current_iteration > max_iterations`:
```
MAX ITERATIONS REACHED: {iteration}/{max} | {completed}/{total} done
-> Generating partial report...
```
Set `prd.status = 'partial'` → step-05.

## 3. Check All Tasks Complete

If `allDone`:

### 3a. Team Mode (multi-module with Agent Teams)

```javascript
if ({team_active}) {
  // Signal team lead that this module is done
  SendMessage({
    type: "message", recipient: "team-lead",
    content: `MODULE_COMPLETE:${current_module}`,
    summary: `${current_module} complete`
  });
  // STOP — team lead manages next module
  return;
}
```

### 3b. Sequential Multi-Module (modules-queue.json)

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

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

  // ARTIFACT EXISTENCE CHECK: categories with completed tasks must have real files
  const artifactChecks = {
    'infrastructure': () => fs.existsSync('src/*/Persistence/Migrations') && fs.readdirSync('src/*/Persistence/Migrations').length > 0,
    'test': () => fs.existsSync(`tests/${projectName}.Tests.Unit`) && glob.sync('tests/**/*Tests.cs').length > 0,
    'api': () => glob.sync('src/*/Controllers/**/*Controller.cs').length > 0,
    'frontend': () => glob.sync('**/src/pages/**/*.tsx').length > 0
  };
  for (const [cat, check] of Object.entries(artifactChecks)) {
    if (completedCats.has(cat) && !check()) {
      // Tasks marked completed but artifacts missing — reset to pending
      prd.tasks.filter(t => t.category === cat && t.status === 'completed')
        .forEach(t => { t.status = 'pending'; t.error = 'Artifacts missing — re-execute'; t.completed_at = null; });
      missing.push(cat);
      console.log(`ARTIFACT RESET: ${cat} tasks reset to pending (no files found)`);
    }
  }

  if (missing.length > 0) {
    // Inject guardrail tasks for missing layers
    let maxId = Math.max(...prd.tasks.map(t => t.id));
    for (const cat of missing) {
      maxId++;
      prd.tasks.push({
        id: maxId, description: `[${cat}] GUARDRAIL: Generate missing ${cat} for ${currentModule.code}`,
        status: 'pending', category: cat, dependencies: [],
        acceptance_criteria: `${cat} layer fully implemented`, started_at: null, completed_at: null,
        iteration: null, commit_hash: null, files_changed: { created: [], modified: [] },
        validation: null, error: null, module: currentModule.code
      });
    }
    writeJSON('.ralph/prd.json', prd);
    // Fall through to section 5 (compact loop)
  } else {
    // Module complete — advance
    currentModule.status = 'completed';
    queue.completedModules++;
    const nextIndex = queue.currentIndex + 1;

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

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

      // Load next module PRD
      prd.status = 'completed';
      writeJSON('.ralph/prd.json', prd);
      const nextPrd = readJSON(queue.modules[nextIndex].prdFile);
      nextPrd.config.current_iteration = 1;
      nextPrd.config.max_iterations = prd.config.max_iterations;
      writeJSON('.ralph/prd.json', nextPrd);

      console.log(`MODULE COMPLETE: ${currentModule.code} → NEXT: ${queue.modules[nextIndex].code}`);
      // → step-01 (module transition)
      return;
    }

    // ALL MODULES COMPLETE
    writeJSON(queuePath, queue);
    console.log(`ALL ${queue.totalModules} MODULES COMPLETE`);
  }
}
```

### 3c. Completion Output

```
ALL TASKS COMPLETE | Iterations: {n} | Tasks: {completed}/{total}
<promise>{completion_promise}</promise>
```

Set `prd.status = 'completed'` → step-05.

## 4. Dead-End Check

If `!hasPending && !allDone`: all remaining blocked/failed, no progress possible.
```
DEAD-END: {completed} done, {failed} failed, {blocked} blocked
```
Set `prd.status = 'failed'` → step-05.

## 5. Compact Loop (CRITICAL)

> **MANDATORY:** After first full iteration, ALL subsequent iterations use this inline loop.
> DO NOT stop. DO NOT wait for user. DO NOT re-read step files.
> **Read `references/compact-loop.md` and execute inline.**

If `hasPending && iteration < max`:

1. Read `references/compact-loop.md`
2. Execute sections A → B → C → D inline
3. Return to section 1 of THIS step

---

## CRITICAL RULES:

- **NEVER** output completion promise unless ALL tasks done (all modules if multi)
- **NEVER** delegate the loop to sub-agents (except team mode for multi-module)
- **NEVER** stop between iterations
- **BATCH** same-category tasks (max 5)
- **MODULE COMPLETENESS:** All layers must have completed tasks before advancing
- Only valid stop: all complete, max iterations, dead-end, or user Ctrl+C

---

## NEXT STEP:

- All done + more modules in queue → `./step-01-task.md`
- All done (no more modules) → `./step-05-report.md`
- Max iterations or dead-end → `./step-05-report.md`
- Tasks remaining → compact loop (section 5)
