# PRD v3 Transformation: filesToCreate → tasks

> **Used by:** step-01-task.md (section 2b)

## Algorithm: Transform filesToCreate to tasks[]

When PRD v3.0.0 has `implementation.filesToCreate` but NO `tasks[]`, transform all files into a task list with derived acceptance criteria.

### Input

```
prd.implementation.filesToCreate = {
  domain: [ { path: "Domain/Employee.cs", ... }, ... ],
  infrastructure: [ ... ],
  application: [ ... ],
  api: [ ... ],
  seedData: [ ... ],
  frontend: [ ... ],
  tests: [ ... ],
  documentation: [ ... ]
}
```

### Companion Specs (Optional)

Load companion specification files to enrich acceptance criteria:

```javascript
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 */ }
```

### Transformation Logic

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

// Category order: domain → infrastructure → application → api → seedData → frontend → tests → documentation
const categoryOrder = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'tests', 'documentation'];

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);
```

---

## deriveAcceptanceCriteria() Function

Six category-specific branches derive acceptance criteria from companion specs or fallback to generic statement.

### Category: domain

Extract entity attributes and relationships from `specEntities.entities`:

```javascript
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;
}
```

**Fallback:** `File {path} exists and compiles`

### Category: infrastructure

Extract EF configuration from entity spec and attribute names:

```javascript
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;
}
```

**Fallback:** `File {path} exists and compiles`

### Category: application

Link to business rules and use cases via `brToCodeMapping` and `linkedUCs`:

```javascript
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;
}
```

**Fallback:** `File {path} exists and compiles`

### Category: api

Extract endpoints and permissions from specifications:

```javascript
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('. ');
}
```

**Fallback:** `File {path} exists and compiles`

### Category: frontend

Extract screen columns, filters, actions, and KPIs from screen specification:

```javascript
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;
}
```

**Fallback:** `File {path} exists and compiles`

### Category: seedData

Extract seed values for business seed data from entity spec:

```javascript
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;
}
```

**Fallback:** `File {path} exists and compiles`

### Category: test / tests

Link to covered business rules and use cases:

```javascript
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:** `File {path} exists and compiles`

---

## Full Function

```javascript
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`;
}
```
