---
name: step-01-task
description: Load or create tasks from prd.json (v3 unified or v2 legacy)
next_step: steps/step-02-execute.md
---

# Step 1: Load Task

> **STATE RECOVERY (executable):** If context was compressed and you are unsure of your position:
```javascript
if (fileExists('.ralph/ralph-state.json')) {
  const state = readJSON('.ralph/ralph-state.json');
  if (state.currentStep && state.currentStep !== 'step-01-task') {
    console.warn(`RECOVERY: ralph-state.json says currentStep="${state.currentStep}" but we are in step-01.`);
    console.warn(`This may indicate context compression routed to the wrong step.`);
    console.warn(`Current module: ${state.currentModule}, iteration: ${state.iteration}`);
  }
  // Restore module context
  if (state.currentModule) { {current_module} = state.currentModule; }
  if (state.iteration) { {current_iteration} = state.iteration; }
}
```

> **MODULE TRANSITION CHECK:** Before "Only Read Once" rule, check for module transition:

```javascript
if (fileExists('.ralph/module-changed.json')) {
  const t = readJSON('.ralph/module-changed.json');
  console.log(`Module transition: ${t.fromModule} → ${t.toModule}`);
  deleteFile('.ralph/module-changed.json');
  // PROCEED (exception to "Only Read Once" rule)
}
```

> **CONTEXT OPTIMIZATION:** Read ONCE per module (first iteration).
> Iterations > 1 within same module → use COMPACT LOOP in step-04.
> Re-read ONLY on module transition (module-changed.json detected above).

## YOUR TASK:

Load task from prd.json or create initial task breakdown with categories, dependencies, and acceptance criteria.

---

## 0. Multi-Module Queue Check

See `references/multi-module-queue.md` for full module queue initialization, detection, and loading.

Quick: If `glob('.ralph/prd-*.json').length > 1` → create modules-queue.json and load first module's PRD.

---

## PrdJson → Ralph v2 Transformation (DEPRECATED)

See [references/task-transform-legacy.md](../references/task-transform-legacy.md) for the full `transformPrdJsonToRalphV2()` function including:
- Layer-ordered task generation (domain → infra → app → api → frontend → seedData → tests)
- Consolidated frontend + i18n tasks
- Guardrails 1d-1j (inject missing infrastructure, API, frontend, tests, seed data, migration)

---

## 1. Check for Existing prd.json

```javascript
// PRD PATH RESOLUTION (MANDATORY - runs before any PRD access)
const queuePath = '.ralph/modules-queue.json';
let currentPrdPath = '.ralph/prd.json';
if (fileExists(queuePath)) {
  const queue = readJSON(queuePath);
  currentPrdPath = queue.modules[queue.currentIndex].prdFile;
}
```

If `{currentPrdPath}` exists:
1. Read the PRD
2. **v3 FAST PATH:** If `$version === "3.0.0"` AND `tasks[]` exists:
   - Inject runtime fields if missing (status, config, feature, created, updated_at, history)
   - Write back to `{currentPrdPath}`
   - **Run CATEGORY COMPLETENESS CHECK (section 4b) before proceeding**
   - Skip directly to section 5 (find next task)
2b. **v3 filesToCreate PATH:** If `$version === "3.0.0"` AND `implementation.filesToCreate` exists AND NO `tasks[]`:
   → Transform filesToCreate into tasks[]:

   ```javascript
   const ftc = prd.implementation.filesToCreate;
   const tasks = [];
   let taskId = 1;

   // CATEGORY ORDER: domain → infrastructure → application → api → seedData → frontend → test → documentation
   const categoryOrder = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'tests', 'documentation'];

   // Load companion specs if available (for rich acceptance criteria)
   const specFiles = prd.specificationFiles || {};
   const specsDir = '.ralph';
   let specEntities = null, specRules = null, specUsecases = null, specScreens = null, specPermissions = null;
   try {
     if (specFiles.entities) specEntities = readJSON(`${specsDir}/${specFiles.entities}`);
     if (specFiles.rules) specRules = readJSON(`${specsDir}/${specFiles.rules}`);
     if (specFiles.usecases) specUsecases = readJSON(`${specsDir}/${specFiles.usecases}`);
     if (specFiles.screens) specScreens = readJSON(`${specsDir}/${specFiles.screens}`);
     if (specFiles.permissions) specPermissions = readJSON(`${specsDir}/${specFiles.permissions}`);
   } catch (e) { /* companion files absent — fallback to generic AC */ }

   function deriveAcceptanceCriteria(file, category, prd) {
     const fileName = (file.path || file).split('/').pop().replace(/\.\w+$/, '');

     // If no companion specs available, fallback to generic AC
     if (!specEntities && !specRules) {
       return `File ${file.path || file} exists and compiles`;
     }

     switch (category) {
       case 'domain': {
         const entityName = fileName;
         const entity = (specEntities?.entities || []).find(e => e.name === entityName);
         if (entity && entity.attributes) {
           const attrs = entity.attributes.map(a => `${a.name}:${a.type}`).join(', ');
           const rels = (entity.relationships || []).map(r => `${r.type} ${r.target}`).join(', ');
           return `Entity ${entityName} with attributes [${attrs}]${rels ? '. Relations: [' + rels + ']' : ''}`;
         }
         break;
       }
       case 'application': {
         const linkedBRs = (prd.brToCodeMapping || [])
           .filter(br => br.implementationPoints?.some(ip => ip.component?.includes(fileName)))
           .map(br => `${br.ruleId}: ${br.statement || br.title}`);
         const linkedUCs = (file.linkedUCs || []).join(', ');
         if (linkedBRs.length > 0 || linkedUCs) {
           const parts = [];
           if (linkedBRs.length > 0) parts.push(`Implements BRs: [${linkedBRs.join('; ')}]`);
           if (linkedUCs) parts.push(`Handles UCs: [${linkedUCs}]`);
           return parts.join('. ');
         }
         break;
       }
       case 'infrastructure': {
         const entityName = fileName.replace('Configuration', '');
         const entity = (specEntities?.entities || []).find(e => e.name === entityName);
         if (entity && entity.attributes) {
           const props = entity.attributes.map(a => a.name).join(', ');
           return `EF Config for ${entityName}. Attributes: [${props}]`;
         }
         break;
       }
       case 'api': {
         const entityName = fileName.replace('Controller', '');
         const endpoints = (prd.apiEndpointSummary || [])
           .filter(ep => ep.operation?.includes(entityName))
           .map(ep => `${ep.method} ${ep.route}`);
         const perms = (specPermissions?.permissionPaths || [])
           .filter(p => p.toLowerCase().includes(entityName.toLowerCase()))
           .slice(0, 5);
         const parts = [`Controller ${entityName}`];
         if (endpoints.length > 0) parts.push(`Endpoints: [${endpoints.join(', ')}]`);
         if (perms.length > 0) parts.push(`Permissions: [${perms.join(', ')}]`);
         return parts.join('. ');
       }
       case 'frontend': {
         const screen = (specScreens?.screens || []).find(s =>
           fileName.toLowerCase().includes(s.code?.toLowerCase() || s.name?.toLowerCase() || '')
         );
         if (screen) {
           const parts = [file.type || 'Page'];
           if (screen.columns) parts.push(`columns [${screen.columns.map(c => c.key || c.name || c).join(', ')}]`);
           if (screen.filters) parts.push(`filters [${screen.filters.map(f => f.key || f.name || f).join(', ')}]`);
           if (screen.actions) parts.push(`actions [${screen.actions.map(a => a.key || a.name || a).join(', ')}]`);
           if (screen.kpis) parts.push(`KPIs [${screen.kpis.map(k => k.label || k.name || k).join(', ')}]`);
           return `${parts.join(' with ')}`;
         }
         break;
       }
       case 'seedData': {
         if (file.category === 'business') {
           const entityName = fileName.replace('SeedData', '');
           const entity = (specEntities?.entities || []).find(e => e.name === entityName);
           if (entity?.seedValues) {
             const names = entity.seedValues.map(v => v.name || v.label || v.code).filter(Boolean).slice(0, 5);
             return `Seeds ${entityName} with ${entity.seedValues.length} values: [${names.join(', ')}]`;
           }
         }
         break;
       }
       case 'test': case 'tests': {
         const linkedBRs = (prd.brToCodeMapping || [])
           .filter(br => br.implementationPoints?.some(ip =>
             ip.layer === 'Domain' || ip.layer === 'Application'
           ))
           .map(br => br.ruleId);
         const linkedUCs = (file.linkedUCs || []);
         const parts = [];
         if (linkedBRs.length > 0) parts.push(`Covers BRs: [${linkedBRs.join(', ')}]`);
         if (linkedUCs.length > 0) parts.push(`Covers UCs: [${linkedUCs.join(', ')}]`);
         if (parts.length > 0) return parts.join('. ');
         break;
       }
     }
     // Fallback: generic AC
     return `File ${file.path || file} exists and compiles`;
   }

   for (const category of categoryOrder) {
     const files = ftc[category] || [];
     for (const file of files) {
       tasks.push({
         id: `T${String(taskId++).padStart(3, '0')}`,
         description: `Create ${file.type || category}: ${(file.path || file).split('/').pop()}`,
         status: 'pending',
         category: category === 'tests' ? 'test' : category,  // normalize
         dependencies: [],  // implicit ordering via category
         acceptance_criteria: deriveAcceptanceCriteria(file, category === 'tests' ? 'test' : category, prd),
         path: file.path || file,
         started_at: null, completed_at: null, iteration: null,
         commit_hash: null, files_changed: [], validation: null, error: null
       });
     }
   }

   prd.tasks = tasks;
   prd.config = { current_iteration: 1, max_iterations: 50 };
   writeJSON(currentPrdPath, prd);
   ```
   → Run CATEGORY COMPLETENESS CHECK (section 4b) — will detect if seedData is missing
   → Skip to section 5 (find next task)
3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5)
4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5

If `{currentPrdPath}` does not exist: continue to section 1b.

### 1b. BA Handoff Quality Gate (BLOCKING when BA artifacts present)

> **CRITICAL:** Before generating ANY tasks, verify the BA handoff is complete.
> A missing handoff means no frontend files, no test plan, no BR-to-code mapping.

> **INTENTIONAL SKIP:** If `sourceFeatureJson` is null (manual start via `{task_description}`, no BA run),
> this quality gate is **intentionally skipped** — the loop proceeds in "manual mode" and generates tasks
> from the task description. This is by design, not a bug. Inform the user in the summary (section 6 of step-00).

```javascript
const sourceFeatureJson = prd?.metadata?.sourceFeatureJson
  || findFile('docs/**/business-analyse/**/index.json')
  || findFile('docs/business/**/business-analyse/**/feature.json');  // legacy fallback

if (!sourceFeatureJson) {
  // Manual mode: no BA artifacts found — skip quality gate
  console.log('Manual mode: no BA artifacts detected — tasks will be generated from task description');
  // Continue to section 2 (Analyze Task Description)
} else {
  const feature = readJSON(sourceFeatureJson);
  const handoff = feature.handoff || {};

  // CHECK 1: Handoff status must be "handed-off"
  if (handoff.status !== 'handed-off') {
    console.error(`BLOCKING: BA HANDOFF INCOMPLETE
handoff.status = "${handoff.status || 'missing'}" (expected "handed-off")
Run /business-analyse-handoff to generate the handoff, then re-run /business-analyse-develop.`);
    STOP;
  }

  // CHECK 2: filesToCreate must have all categories
  const filesToCreate = handoff.filesToCreate || {};
  const requiredCategories = ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests', 'documentation'];
  const missingInHandoff = requiredCategories.filter(c => !filesToCreate[c] || filesToCreate[c].length === 0);

  if (missingInHandoff.length > 0) {
    console.warn(`⚠ Handoff has empty categories: ${missingInHandoff.join(', ')}`);
  }
}
```

## 2. Analyze Task Description

Check for BA handoff source (priority):
1. Per-module PRD files (handled by section 0)
2. Single prd.json already present
3. Markdown handoff: `find . -path "*development-handoff*" -name "*.md"`
4. Direct task from `{task_description}`

Generate **3-30 subtasks** by category (domain → infrastructure → application → api → seedData → frontend → i18n → test → validation).

**seedData category is MANDATORY** — generates navigation entries, permissions, roles, and the IClientSeedDataProvider.
Without seedData tasks, modules are invisible in the UI (no menu, no permissions, no RBAC).

## 3. Create prd.json

Write `{currentPrdPath}` with all tasks (every task MUST have: id, description, status, category, dependencies, acceptance_criteria, started_at, completed_at, iteration, commit_hash, files_changed, validation, error).

Initialize `.ralph/progress.txt`:
```markdown
# Ralph Loop Progress
## Task: {task_description}
## Started: {timestamp}
## Schema: v3.0.0
```

## 4. Validate Task Integrity

- All tasks have required fields
- Dependency references are valid (exist, no forward/circular)
- Task count 3-30

### 4b. Category Completeness Check (BLOCKING)

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

Quick: `checkCategoryCompleteness(prd)` → inject guardrails if needed

---

### 4c. Section Split Detection (Large Module)

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

if (domainTasks.length > 4 && sections.length > 1 && !prd._sectionSplit?.enabled) {
  console.log(`SECTION SPLIT: ${domainTasks.length} domain tasks, ${sections.length} sections → activating`);
  // LOAD references/section-splitting.md — execute sections 2-6
}
```

**Activation:** `> 4 domain tasks` AND `> 1 architecture section`

## 5. Find Current Task

```javascript
function findNextTask(tasks) {
  for (const task of tasks) {
    if (task.status !== 'pending') continue;
    const depsBlocked = task.dependencies.some(d => {
      const dep = tasks.find(t => t.id === d);
      return dep && (dep.status === 'failed' || dep.status === 'blocked');
    });
    if (depsBlocked) { task.status = 'blocked'; task.error = 'Blocked by failed dependency'; continue; }
    const depsOk = task.dependencies.every(d => tasks.find(t => t.id === d)?.status === 'completed');
    if (depsOk) return task;
  }
  return null;
}
```

If no eligible task: check if all completed → step-05, else STOP.

## 6. Show Task Status

```
[{iteration}/{max}] {completed}/{total} done | Task [{id}] {description} ({category})
-> Executing...
```

---

## NEXT STEP:

After task loaded, proceed to `./step-02-execute.md`
