---
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. Lightweight Sanity Check

> **Note:** Full build verification, POST-CHECKs, and test runs are handled by apex.
> Ralph only does a lightweight sanity check to detect regressions between apex invocations.

```bash
# Quick build check (quiet mode)
dotnet build --no-restore --verbosity quiet
BUILD_RC=$?
# Note: WSL bin\Debug cleanup handled by PostToolUse hook (wsl-dotnet-cleanup.sh)
if [ $BUILD_RC -ne 0 ]; then
  echo "BUILD REGRESSION detected between apex invocations"
  # Inject fix task — apex will handle the actual fix
  prd.tasks.push({ id: maxId+1, description: "Fix build regression",
    status: "pending", category: "validation", dependencies: [],
    acceptance_criteria: "dotnet build passes" });
  writeJSON('.ralph/prd.json', prd);
fi
```

### 1.7. Category Completeness Check (RUNS EVERY ITERATION)

See `references/category-completeness.md` for detailed implementation.

```javascript
const { missing, guardrailsNeeded } = checkCategoryCompleteness(prd);

if (guardrailsNeeded.length > 0) {
  prd.tasks.push(...guardrailsNeeded);
  writeJSON('.ralph/prd.json', prd);
  console.log(`PRD updated: +${guardrailsNeeded.length} guardrails, ${prd.tasks.filter(t => t.status === 'pending').length} pending tasks`);
}

// Artifact verification (check if completed tasks have files)
const completedCats = new Set(prd.tasks.filter(t => t.status === 'completed').map(t => t.category));
const artifactChecks = {
  'frontend': () => glob.sync('**/src/pages/**/*.tsx').length > 0,
  'test': () => glob.sync('tests/**/*Tests.cs').length > 0,
  'api': () => glob.sync('src/*/Controllers/**/*Controller.cs').length > 0
};
for (const [cat, check] of Object.entries(artifactChecks)) {
  if (completedCats.has(cat) && !check()) {
    console.warn(`ARTIFACT MISSING: ${cat} tasks marked completed but no files found — resetting 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;
      });
    writeJSON('.ralph/prd.json', prd);
  }
}
```

## 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

**Re-read state after section 1.7 (categories may have been injected):**
```javascript
const tasksCompletedNow = prd.tasks.filter(t => t.status === 'completed').length;
const tasksTotalNow = prd.tasks.length;
const allDone = (tasksCompletedNow + prd.tasks.filter(t => t.status === 'skipped').length) === tasksTotalNow;
```

If `allDone`:

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

```javascript
if ({team_active}) {
  SendMessage({
    type: "message", recipient: "team-lead",
    content: `MODULE_COMPLETE:${current_module}`,
    summary: `${current_module} complete`
  });
  return;
}
```

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

See `references/module-transition.md` for complete module advancement protocol.

```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 section 5 (compact loop)
  } else {
    // All categories complete — advance to next module
    // (detailed logic in references/module-transition.md)
  }
}
```

### 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

```javascript
const hasPending = prd.tasks.some(t => t.status === 'pending');
const MAX_RETRIES = 3;

// Before declaring dead-end, check if any failed tasks have retries left
const retriableCount = prd.tasks.filter(t =>
  t.status === 'failed' && (t._retryCount || 0) < MAX_RETRIES
).length;

if (!hasPending && !allDone && retriableCount > 0) {
  // NOT a dead-end — compact loop will reset failed tasks to pending
  console.log(`${retriableCount} failed tasks have retries remaining — entering compact loop`);
  // Fall through to section 5
} else if (!hasPending && !allDone && retriableCount === 0) {
  // TRUE dead-end: all retries exhausted
  console.log(`DEAD-END: ${tasksCompletedNow} done, ${tasksFailed} failed (retries exhausted), ${tasksBlocked} blocked`);
  prd.status = 'failed';
  writeJSON('.ralph/prd.json', prd);
  // → step-05
}
```

## 5. Compact Loop (MANDATORY — EXECUTE IMMEDIATELY)

> **THIS IS NOT OPTIONAL.** If there are pending tasks, you MUST enter the compact loop NOW.
> DO NOT stop. DO NOT wait for user. DO NOT ask for confirmation. DO NOT re-read step files.
> **Read `references/compact-loop.md` and execute ALL sections inline.**
> **Stopping here is a VIOLATION of the execution guarantee.**

If `hasPending && iteration < max`:

1. Read `references/compact-loop.md`
2. Execute sections A → B → C → D inline (find tasks → delegate to apex → commit PRD → loop back)
3. **IMMEDIATELY** return to section 1 of THIS step (do NOT pause)
4. Repeat until ALL tasks are complete, max iterations reached, or dead-end

---

## CRITICAL RULES — EXECUTION GUARANTEE:

- **NEVER** output completion promise unless ALL tasks done (all modules if multi)
- **NEVER** stop between iterations — this is the CORE PROMISE of ralph-loop
- **NEVER** ask the user "should I continue?" or "is this OK?" — JUST CONTINUE
- **NEVER** pause to show intermediate results and wait for feedback
- **NEVER** generate code directly — ALWAYS delegate to /apex
- **BATCH** same-category tasks (max 5)
- **MODULE COMPLETENESS:** All layers must have completed tasks before advancing
- **Only valid stop conditions:** 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)
