# Task Transform: Legacy PrdJson → Ralph v2 (DEPRECATED)

> Reference for step-01-task.md — fallback transformation for old FORMAT A PRDs (pre-v3.9.0).
> New PRDs generated by `ss derive-prd` use unified v3 format (`$version: "3.0.0"`)
> with pre-computed tasks. The v3 fast path in sections 0 and 1 bypasses this entirely.
> This function will be removed in a future release.

## FORMAT A Input Structure

```
{ version, source, project, requirements, businessRules, architecture, implementation, seedData }
```

## transformPrdJsonToRalphV2(prdJson, moduleCode)

```javascript
function transformPrdJsonToRalphV2(prdJson, moduleCode) {
  const tasks = [];
  let taskId = 1;

  const layerOrder = [
    { key: "domain", category: "domain" },
    { key: "infrastructure", category: "infrastructure" },
    { key: "application", category: "application" },
    { key: "api", category: "api" },
    { key: "frontend", category: "frontend" },
    { key: "seedData", category: "infrastructure" },
    { key: "tests", category: "test" }
  ];

  const filesToCreate = prdJson.implementation?.filesToCreate || prdJson.filesToCreate || {};
  const lastIdByCategory = {};
  const frontendFiles = [];
  const i18nFiles = [];

  // --- Generate tasks from filesToCreate ---
  for (const layer of layerOrder) {
    const files = filesToCreate[layer.key] || [];
    if (layer.category === "frontend") { frontendFiles.push(...files); continue; }

    for (const fileSpec of files) {
      if (fileSpec.path?.includes('i18n/') || fileSpec.path?.includes('/locales/')) {
        i18nFiles.push(fileSpec); continue;
      }

      const depIds = [];
      if (lastIdByCategory[layer.category]) depIds.push(lastIdByCategory[layer.category]);
      if (layer.category === "api" && lastIdByCategory["application"]) depIds.push(lastIdByCategory["application"]);
      if (layer.category === "test" && lastIdByCategory["api"]) depIds.push(lastIdByCategory["api"]);

      const allFRs = prdJson.requirements?.functionalRequirements || prdJson.functionalRequirements || [];
      const linkedFRs = (fileSpec.linkedFRs || []).map(frId => allFRs.find(f => f.id === frId)?.statement || frId);
      let criteria = linkedFRs.length > 0 ? `Implements: ${linkedFRs.join("; ")}` : `File ${fileSpec.path} created and compiles`;

      const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
      if (layer.category === "api" && permMatrix?.permissions?.length > 0) {
        criteria += "; MANDATORY: [RequirePermission] on every endpoint (NOT [Authorize])";
      }

      tasks.push({
        id: taskId, description: fileSpec.description ? `[${layer.category}] ${fileSpec.description} → ${fileSpec.path}` : `[${layer.category}] Create ${fileSpec.type}: ${fileSpec.path}`,
        status: "pending", category: layer.category, dependencies: [...new Set(depIds)],
        acceptance_criteria: criteria, started_at: null, completed_at: null, iteration: null,
        commit_hash: null, files_changed: { created: [fileSpec.path], modified: [] },
        validation: null, error: null, module: moduleCode
      });
      lastIdByCategory[layer.category] = taskId;
      taskId++;
    }
  }

  // --- 1b. Consolidated frontend task ---
  if (frontendFiles.length > 0) {
    const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
    const ctx = (prdJson.project?.context || 'Business').replace(/^./, c => c.toUpperCase());
    const app = prdJson.project?.application || moduleCode;
    tasks.push({
      id: taskId,
      description: `[frontend] Generate COMPLETE frontend for module ${moduleCode} via MCP (${frontendFiles.length} files)`,
      status: "pending", category: "frontend", dependencies: apiDepId ? [apiDepId] : [],
      acceptance_criteria: `Pages in src/pages/${ctx}/${app}/${moduleCode}/; MCP scaffold_api_client + scaffold_routes; SmartTable for lists; CSS variables ONLY; 4-language i18n; npm run typecheck passes`,
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: frontendFiles.map(f => f.path), modified: [] },
      validation: null, error: null, module: moduleCode,
      _frontendMeta: { wireframes: [...new Set(frontendFiles.flatMap(f => f.linkedWireframes || []))], filesToCreate: frontendFiles }
    });
    lastIdByCategory["frontend"] = taskId; taskId++;
  }

  // --- 1c. Consolidated i18n task ---
  if (i18nFiles.length > 0) {
    tasks.push({
      id: taskId, description: `[i18n] Generate i18n for module ${moduleCode} (4 languages)`,
      status: "pending", category: "i18n", dependencies: lastIdByCategory["frontend"] ? [lastIdByCategory["frontend"]] : [],
      acceptance_criteria: "4 JSON files (fr, en, it, de) with identical keys",
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: i18nFiles.map(f => f.path), modified: [] },
      validation: null, error: null, module: moduleCode
    });
    lastIdByCategory["i18n"] = taskId; taskId++;
  }

  // --- GUARDRAILS: Inject missing layers ---
  const entities = prdJson.architecture?.entities || prdJson.entities || [];
  const apiEndpoints = prdJson.architecture?.apiEndpoints || prdJson.apiEndpoints || [];
  const sections = prdJson.architecture?.sections || prdJson.sections || [];
  const wireframes = prdJson.specification?.uiWireframes || prdJson.uiWireframes || [];
  const dashboards = prdJson.specification?.dashboards || prdJson.dashboards || [];

  // 1d. INJECT missing infrastructure
  if (!tasks.some(t => t.category === 'infrastructure') && entities.length > 0) {
    tasks.push({ id: taskId, description: `[infrastructure] Create EF Core configs and seed data for ${moduleCode} (${entities.length} entities)`,
      status: "pending", category: "infrastructure", dependencies: lastIdByCategory["domain"] ? [lastIdByCategory["domain"]] : [],
      acceptance_criteria: `EF configs for ${entities.length} entities; DbSet in ExtensionsDbContext; DI registration`,
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode });
    lastIdByCategory["infrastructure"] = taskId; taskId++;
  }

  // 1e. INJECT missing API
  if (!tasks.some(t => t.category === 'api') && apiEndpoints.length > 0) {
    const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
    tasks.push({ id: taskId, description: `[api] Create controllers for ${moduleCode} (${apiEndpoints.length} endpoints)`,
      status: "pending", category: "api", dependencies: [lastIdByCategory["application"] || lastIdByCategory["infrastructure"]].filter(Boolean),
      acceptance_criteria: `${apiEndpoints.length} endpoints; ${permMatrix?.permissions?.length > 0 ? "[RequirePermission] on every endpoint" : "Authorization"}; Swagger`,
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode });
    lastIdByCategory["api"] = taskId; taskId++;
  }

  // 1f. INJECT missing frontend
  if (frontendFiles.length === 0 && (sections.length > 0 || wireframes.length > 0 || apiEndpoints.length > 0)) {
    const ctx = (prdJson.project?.context || 'Business').replace(/^./, c => c.toUpperCase());
    const app = prdJson.project?.application || moduleCode;
    const derivedFiles = wireframes.map(wf => ({ path: `web/src/pages/${ctx}/${app}/${moduleCode}/${wf.screen || wf.id}.tsx`, type: 'Page' }));
    if (apiEndpoints.length > 0) derivedFiles.push({ path: `web/src/services/api/${moduleCode}Api.ts`, type: 'ApiService' });

    tasks.push({ id: taskId,
      description: `[frontend] Generate COMPLETE frontend for ${moduleCode} via MCP (${derivedFiles.length} files)`,
      status: "pending", category: "frontend", dependencies: [lastIdByCategory["api"] || lastIdByCategory["application"]].filter(Boolean),
      acceptance_criteria: `Pages in src/pages/${ctx}/${app}/${moduleCode}/; SmartTable for lists; CSS variables ONLY; 4-language i18n; npm run typecheck passes`,
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: derivedFiles.map(f => f.path), modified: [] },
      validation: null, error: null, module: moduleCode,
      _frontendMeta: { wireframes: wireframes.map(w => w.screen || w.id), filesToCreate: derivedFiles, source: "guardrail-derived" }
    });
    lastIdByCategory["frontend"] = taskId; taskId++;

    // Also inject i18n
    if (i18nFiles.length === 0) {
      tasks.push({ id: taskId, description: `[i18n] Generate i18n for ${moduleCode} (4 languages)`,
        status: "pending", category: "i18n", dependencies: [lastIdByCategory["frontend"]],
        acceptance_criteria: "4 JSON files (fr, en, it, de) with identical keys",
        started_at: null, completed_at: null, iteration: null, commit_hash: null,
        files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode });
      lastIdByCategory["i18n"] = taskId; taskId++;
    }
  }

  // 1g. INJECT missing tests
  if (!tasks.some(t => t.category === 'test') && entities.length > 0) {
    const migDepId = lastIdByCategory["migration"] || lastIdByCategory["api"] || lastIdByCategory["application"];
    tasks.push({ id: taskId, description: `[test] Create unit and integration tests for ${moduleCode}`,
      status: "pending", category: "test", dependencies: migDepId ? [migDepId] : [],
      acceptance_criteria: "Domain + service + controller tests; dotnet test passes; coverage >= 80%",
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode });
    lastIdByCategory["test"] = taskId; taskId++;
  }

  // 1h. GUARDRAIL: Core seed data (navigation, permissions, roles)
  const coreSeedData = prdJson.seedData?.core || prdJson.seedDataCore || prdJson.specification?.seedDataCore;
  const hasSeedDataInTasks = tasks.some(t => t.description?.includes("SeedData") || t.description?.includes("seed data"));
  if (coreSeedData && !hasSeedDataInTasks) {
    const infraDepId = lastIdByCategory["infrastructure"] || lastIdByCategory["domain"];
    const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
    const permissions = coreSeedData.permissions || [];
    const rolePerms = coreSeedData.rolePermissions || [];
    tasks.push({
      id: taskId, description: `[infrastructure] Create core seed data for ${moduleCode}: NavigationApplicationSeedData, ApplicationRolesSeedData, NavigationModuleSeedData, PermissionsSeedData, RolesSeedData`,
      status: "pending", category: "infrastructure", dependencies: infraDepId ? [infraDepId] : [],
      acceptance_criteria: `NavigationApplicationSeedData (app-level, once); ApplicationRolesSeedData (4 roles); NavigationModuleSeedData (${navModules.length} nav); PermissionsSeedData (${permissions.length} perms); RolesSeedData (${rolePerms.length} roles); SeedConstants; dotnet build passes`,
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode,
      _seedDataMeta: {
        source: "guardrail-derived", coreSeedData,
        navRoute: prdJson.project?.navRoute || `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
        contextCode: prdJson.project?.context || 'business',
        appCode: prdJson.project?.application, appLabels: prdJson.project?.labels || null
      }
    });
    lastIdByCategory["infrastructure"] = taskId; taskId++;
  }

  // 1i. GUARDRAIL: IClientSeedDataProvider
  const hasProvider = tasks.some(t => t.description?.includes("IClientSeedDataProvider") || t.description?.includes("SeedDataProvider"));
  if (!hasProvider && (filesToCreate["seedData"]?.length > 0 || coreSeedData)) {
    tasks.push({
      id: taskId, description: `[infrastructure] Create IClientSeedDataProvider for core seed data injection`,
      status: "pending", category: "infrastructure",
      dependencies: [lastIdByCategory["infrastructure"] || lastIdByCategory["domain"]].filter(Boolean),
      acceptance_criteria: "SeedNavigationAsync + SeedRolesAsync + SeedPermissionsAsync + SeedRolePermissionsAsync; DI registered; idempotent",
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode,
      _providerMeta: {
        navRoute: prdJson.project?.navRoute || `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
        contextCode: prdJson.project?.context || 'business', appCode: prdJson.project?.application
      }
    });
    lastIdByCategory["infrastructure"] = taskId; taskId++;
  }

  // 1j. GUARDRAIL: EF Core migration (CRITICAL — without this, no DB schema, no tests)
  const hasEntities = entities.length > 0 || tasks.some(t => t.category === 'domain');
  const hasMigration = tasks.some(t => t.description?.toLowerCase().includes('migration'));
  if (hasEntities && !hasMigration) {
    tasks.push({
      id: taskId, description: `[infrastructure] Create EF Core migration for ${moduleCode}`,
      status: "pending", category: "infrastructure",
      dependencies: [lastIdByCategory["infrastructure"]].filter(Boolean),
      acceptance_criteria: "MCP suggest_migration → name; dotnet ef migrations add; dotnet ef database update; dotnet build passes",
      started_at: null, completed_at: null, iteration: null, commit_hash: null,
      files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode,
      _migrationMeta: { entities: entities.map(e => e.name || e.code), moduleCode }
    });
    lastIdByCategory["migration"] = taskId; taskId++;
  }

  // Final validation task
  tasks.push({
    id: taskId, description: `[validation] Build, test, and MCP validate module ${moduleCode}`,
    status: "pending", category: "validation", dependencies: Object.values(lastIdByCategory),
    acceptance_criteria: "dotnet build succeeds; dotnet test passes; MCP validate_conventions clean",
    started_at: null, completed_at: null, iteration: null, commit_hash: null,
    files_changed: { created: [], modified: [] }, validation: null, error: null, module: moduleCode
  });

  return {
    $version: "2.0.0", feature: `${prdJson.project?.module || moduleCode} (${prdJson.project?.application || "app"})`,
    status: "in_progress", created: new Date().toISOString(), updated_at: new Date().toISOString(),
    metadata: { cli_version: "unknown", branch: getCurrentBranch(), project_path: process.cwd(), mcp_servers: { smartstack: true, context7: true }, moduleCode },
    config: { max_iterations: {max_iterations}, completion_promise: "{completion_promise}", current_iteration: 1 },
    source: { type: "ba-handoff", handoff_path: null, feature_json_path: prdJson.source?.featureJson || null, module: moduleCode },
    tasks, history: []
  };
}
```

## Guardrails Summary

| ID | Guardrail | Condition | Injection |
|----|-----------|-----------|-----------|
| 1d | Missing infrastructure | entities exist, no infra tasks | EF Core configs + seed data |
| 1e | Missing API | apiEndpoints exist, no api tasks | Controllers |
| 1f | Missing frontend | sections/wireframes exist, no frontend tasks | Pages + API service |
| 1g | Missing tests | entities exist, no test tasks | Unit + integration tests |
| 1h | Core seed data | coreSeedData exists, no seed tasks | Navigation + Permissions + Roles |
| 1i | IClientSeedDataProvider | seedData files or coreSeedData, no provider task | Provider class |
| 1j | EF Core migration | entities exist, no migration task | Migration (CRITICAL) |
