2026 03 09 S197 Generation Pipeline

Documentation for Plans

S197: Vitto End-to-End Generation Pipeline — Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Fix agent loop stalling, deep-port gmodules1 generation patterns, integrate pipeline visualization into vitto-studio, and enable full conversational generation with interactive/autonomous modes.

Architecture: Three parallel streams — A (backend generation foundation), B (frontend pipeline UI + cross-feature integration), C (conversational generation + chat plugins). Stream A Task 1 (agent loop fix) is the critical path blocker that must complete before other tasks can be validated end-to-end.

Tech Stack: NestJS (backend-vitto-api), Angular 20+ standalone components with signals, RxJS, NGXS, Mongoose/MongoDB, Anthropic Claude API, SSE for real-time events, Jest for backend tests.

Spec: docs/superpowers/specs/2026-03-09-s197-vitto-generation-pipeline-design.md


Chunk 1: Agent Loop Fix (CRITICAL PATH BLOCKER)

Task 1: Add Diagnostic Logging to Agent Loop

Files:

Create APIs/backend-vitto-api/src/app/engine/__tests__/session-197-agent-loop.spec.ts:

import { Test } from '@nestjs/testing';
import { AgentLoopService } from '../agent-loop.service';
import { ToolRegistryService } from '../../tools/tool-registry.service';
import { SmartRouterService } from '../../llm/smart-router.service';
import { ContextAssemblyService } from '../context-assembly.service';
import { Logger } from '@nestjs/common';

describe('S197 — Agent Loop Diagnostics', () => {
  let service: AgentLoopService;
  let logSpy: jest.SpyInstance;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        AgentLoopService,
        { provide: ToolRegistryService, useValue: { getToolsForUser: jest.fn().mockReturnValue([{ name: 'start_module_generation', description: 'Start generation', parameters: {} }]) } },
        { provide: SmartRouterService, useValue: { sendWithFailover: jest.fn().mockResolvedValue({ content: 'test', stopReason: 'end_turn', toolCalls: [] }) } },
        { provide: ContextAssemblyService, useValue: { buildWebSystemPrompt: jest.fn().mockResolvedValue('system prompt') } },
        { provide: 'AGENT_LOOP_OPTIONS', useValue: {} },
      ],
    }).compile();

    service = module.get(AgentLoopService);
    logSpy = jest.spyOn(Logger.prototype, 'log');
  });

  it('should log tools count before sending to LLM', async () => {
    // This test verifies diagnostic logging exists
    expect(logSpy).toBeDefined();
  });
});

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-agent-loop" --no-coverage Expected: Test passes (basic setup)

In APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts, add logging after tools are fetched (after line 117) and before sendMessage (before line 249):

// After line 117 (after tool filtering):
this.logger.log(`[AgentLoop] Tools after filtering: ${tools.length} tools — [${tools.map(t => t.name).join(', ')}]`);

// Before line 249 (before sendWithFailover):
this.logger.log(`[AgentLoop] Sending to LLM — iteration: ${iteration}, tools: ${tools.length}, messages: ${messages.length}, systemPrompt length: ${systemPrompt?.length ?? 0} chars`);

// After line 282 (after response received):
this.logger.log(`[AgentLoop] LLM response — stopReason: ${response.stopReason}, toolCalls: ${response.toolCalls?.length ?? 0}, contentLength: ${response.content?.length ?? 0}`);

In APIs/backend-vitto-api/src/app/llm/providers/claude.provider.ts, add logging in the sendMessage method (around line 41):

// After line 41 (after toAnthropicTools):
if (anthropicTools) {
  this.logger.log(`[ClaudeProvider] Sending ${anthropicTools.length} tools to Anthropic API: [${anthropicTools.map(t => t.name).join(', ')}]`);
} else {
  this.logger.warn(`[ClaudeProvider] NO TOOLS being sent to Anthropic API — tools param is undefined`);
}

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage Expected: All 39 S196 tests pass

git add APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts APIs/backend-vitto-api/src/app/llm/providers/claude.provider.ts APIs/backend-vitto-api/src/app/engine/__tests__/session-197-agent-loop.spec.ts
git commit -m "[S197.2] feat: add diagnostic logging to agent loop and claude provider"

Task 2: Token Budget Enforcement in Context Assembly

Files:

Create APIs/backend-vitto-api/src/app/engine/__tests__/session-197-context-budget.spec.ts:

describe('S197 — Context Assembly Token Budget', () => {
  describe('estimateTokens', () => {
    it('should estimate ~4 chars per token', () => {
      const text = 'a'.repeat(4000); // ~1000 tokens
      const estimate = Math.ceil(text.length / 4);
      expect(estimate).toBe(1000);
    });
  });

  describe('trimToTokenBudget', () => {
    it('should trim sections in priority order when over budget', () => {
      // Priority (lowest first): proactiveHints, pageContext, learningIntelligence, awareness, conversationHistory
      // Core persona + tools never trimmed
      const sections = {
        corePersna: 'A'.repeat(10000),    // 2500 tokens — never trim
        conversationHistory: 'B'.repeat(40000), // 10000 tokens
        awareness: 'C'.repeat(20000),     // 5000 tokens
        learningIntelligence: 'D'.repeat(20000), // 5000 tokens
        pageContext: 'E'.repeat(8000),    // 2000 tokens
        proactiveHints: 'F'.repeat(8000), // 2000 tokens
      };
      // Total: 26500 tokens. Budget: 15000 tokens
      // Should trim: proactiveHints (2000), pageContext (2000), learningIntelligence (5000), awareness (5000) = 14000 trimmed
      // Remaining: 12500 tokens — under budget
    });
  });

  describe('buildWebSystemPrompt with budget', () => {
    it('should never exceed MAX_SYSTEM_PROMPT_TOKENS', () => {
      // The assembled prompt should be capped at 60K tokens (~240K chars)
    });
  });
});

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-context-budget" --no-coverage Expected: Tests compile and basic ones pass, budget enforcement tests fail (function doesn't exist yet)

Add to APIs/backend-vitto-api/src/app/engine/context-assembly.service.ts:

// At top of class:
private readonly MAX_SYSTEM_PROMPT_TOKENS = 60_000;

// New private method:
private estimateTokens(text: string): number {
  return Math.ceil(text.length / 4);
}

// New private method — trims sections by priority (lowest priority first):
private trimToTokenBudget(sections: { key: string; content: string; priority: number }[]): Map<string, string> {
  const sorted = [...sections].sort((a, b) => b.priority - a.priority); // highest priority first
  const result = new Map<string, string>();
  let totalTokens = 0;

  for (const section of sorted) {
    const sectionTokens = this.estimateTokens(section.content);
    if (totalTokens + sectionTokens <= this.MAX_SYSTEM_PROMPT_TOKENS) {
      result.set(section.key, section.content);
      totalTokens += sectionTokens;
    } else {
      // Try to fit a truncated version
      const remainingBudget = this.MAX_SYSTEM_PROMPT_TOKENS - totalTokens;
      if (remainingBudget > 200) { // minimum useful content
        const truncatedChars = remainingBudget * 4;
        result.set(section.key, section.content.slice(0, truncatedChars) + '\n[...truncated to fit token budget]');
        totalTokens += remainingBudget;
      }
      this.logger.warn(`[ContextAssembly] Trimmed section "${section.key}" (${sectionTokens} tokens) — budget: ${totalTokens}/${this.MAX_SYSTEM_PROMPT_TOKENS}`);
    }
  }

  this.logger.log(`[ContextAssembly] Final system prompt: ${totalTokens} estimated tokens (budget: ${this.MAX_SYSTEM_PROMPT_TOKENS})`);
  return result;
}

In buildWebSystemPrompt (line 395), wrap the section concatenation with budget enforcement. After all parallel fetches complete (around line 431), before the final string assembly:

// Define sections with priorities (10 = highest, 1 = lowest)
const promptSections = [
  { key: 'persona', content: personaOverlay || '', priority: 10 },
  { key: 'toolDescriptions', content: toolDescriptions || '', priority: 10 },
  { key: 'intentHints', content: intentHints || '', priority: 9 },
  { key: 'knowledge', content: knowledgeContext || '', priority: 8 },
  { key: 'dossier', content: dossierContext || '', priority: 7 },
  { key: 'conversationHistory', content: conversationLines || '', priority: 6 },
  { key: 'awareness', content: awarenessContext || '', priority: 5 },
  { key: 'learningIntelligence', content: learningIntel || '', priority: 4 },
  { key: 'pageContext', content: pageContext || '', priority: 3 },
  { key: 'execInstructions', content: execInstructions || '', priority: 8 },
  { key: 'proactiveHints', content: proactiveHints || '', priority: 1 },
];

const trimmed = this.trimToTokenBudget(promptSections.filter(s => s.content));
// Use trimmed.get('persona') etc. instead of raw variables

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage Expected: All S197 tests pass

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage Expected: All 39 tests pass

git add APIs/backend-vitto-api/src/app/engine/context-assembly.service.ts APIs/backend-vitto-api/src/app/engine/__tests__/session-197-context-budget.spec.ts
git commit -m "[S197.3] feat: add token budget enforcement to context assembly (60K cap)"

Task 3: Tool Presence Assertion

Files:

Extend session-197-agent-loop.spec.ts:

describe('Tool presence assertion', () => {
  it('should log error when tools array is empty after filtering', () => {
    const tools = [];
    // After filtering, if tools is empty, agent loop should warn and include essentials
    expect(tools.length).toBe(0); // placeholder — real test after implementation
  });

  it('should include start_module_generation as essential fallback tool', () => {
    const essentialTools = ['start_module_generation', 'task_complete', 'recall_memory'];
    expect(essentialTools).toContain('start_module_generation');
  });
});

After line 117 in agent-loop.service.ts, add:

// Tool presence assertion
if (!tools || tools.length === 0) {
  this.logger.error(`[AgentLoop] CRITICAL: No tools available after filtering! Layer: ${options.layer}, Filter: ${options.toolFilter?.join(',') ?? 'none'}`);
  // Fallback: get essential tools without layer/filter restrictions
  const essentialToolNames = ['start_module_generation', 'task_complete', 'recall_memory', 'search_decisions'];
  const allTools = this.toolRegistry.getAllTools();
  tools = allTools.filter(t => essentialToolNames.includes(t.name));
  this.logger.warn(`[AgentLoop] Fallback: loaded ${tools.length} essential tools`);
}

Check if getAllTools() exists. If not, add to ToolRegistryService:

getAllTools(): ToolDefinition[] {
  return Array.from(this.tools.values()).map(handler => ({
    name: handler.name,
    description: handler.description,
    parameters: handler.parameters,
  }));
}

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage Expected: All pass

Run: npx nx build backend-vitto-api --skip-nx-cache Expected: Build succeeds

git add APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts APIs/backend-vitto-api/src/app/tools/tool-registry.service.ts APIs/backend-vitto-api/src/app/engine/__tests__/session-197-agent-loop.spec.ts
git commit -m "[S197.4] feat: add tool presence assertion with essential fallback set"

Chunk 2: Template Registry + Composables (Stream A)

Task 4: Template Types and Registry Service

Files:

Create APIs/backend-vitto-api/src/app/module-generation/templates/template-types.ts:

export type TemplateType = 'crud' | 'dashboard' | 'analytics' | 'integration' | 'basic';

export interface TemplateVariable {
  entity: string;       // e.g., 'player'
  Entity: string;       // e.g., 'Player'
  entities: string;     // e.g., 'players'
  Entities: string;     // e.g., 'Players'
  ENTITY: string;       // e.g., 'PLAYER'
  PascalCaseName: string; // e.g., 'PlayerManagement'
  moduleName: string;   // e.g., 'player-management'
}

export interface TemplateFile {
  relativePath: string;  // e.g., 'models/player.model.ts'
  content: string;       // Template content with {{variables}}
  language: string;      // 'typescript' | 'html' | 'scss'
}

export interface TemplateDefinition {
  type: TemplateType;
  displayName: string;
  description: string;
  files: TemplateFile[];
  tokenReduction: number; // estimated percentage
}

export interface GeneratedTemplate {
  type: TemplateType;
  variables: TemplateVariable;
  files: { path: string; content: string }[];
  promptContext: string;  // Context string for LLM prompts
}

Create APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-template-registry.spec.ts:

import { TemplateRegistryService } from '../templates/template-registry.service';

describe('S197 — TemplateRegistryService', () => {
  let service: TemplateRegistryService;

  beforeEach(() => {
    service = new TemplateRegistryService();
  });

  describe('getTemplate', () => {
    it('should return crud template definition', () => {
      const template = service.getTemplate('crud');
      expect(template).toBeDefined();
      expect(template.type).toBe('crud');
      expect(template.files.length).toBeGreaterThan(0);
    });

    it('should return all 5 template types', () => {
      const types: string[] = ['crud', 'dashboard', 'analytics', 'integration', 'basic'];
      for (const type of types) {
        expect(service.getTemplate(type as any)).toBeDefined();
      }
    });
  });

  describe('generateFromTemplate', () => {
    it('should interpolate template variables in file content', () => {
      const result = service.generateFromTemplate('crud', {
        entity: 'player',
        Entity: 'Player',
        entities: 'players',
        Entities: 'Players',
        ENTITY: 'PLAYER',
        PascalCaseName: 'PlayerManagement',
        moduleName: 'player-management',
      });

      expect(result.files.length).toBeGreaterThan(0);
      // No unresolved {{variables}} in output
      for (const file of result.files) {
        expect(file.content).not.toContain('{{');
        expect(file.content).not.toContain('}}');
      }
    });

    it('should include entity model with correct interface name', () => {
      const result = service.generateFromTemplate('crud', {
        entity: 'player',
        Entity: 'Player',
        entities: 'players',
        Entities: 'Players',
        ENTITY: 'PLAYER',
        PascalCaseName: 'PlayerManagement',
        moduleName: 'player-management',
      });

      const modelFile = result.files.find(f => f.path.includes('model'));
      expect(modelFile).toBeDefined();
      expect(modelFile.content).toContain('export interface Player');
    });

    it('should generate promptContext for LLM', () => {
      const result = service.generateFromTemplate('crud', {
        entity: 'player',
        Entity: 'Player',
        entities: 'players',
        Entities: 'Players',
        ENTITY: 'PLAYER',
        PascalCaseName: 'PlayerManagement',
        moduleName: 'player-management',
      });

      expect(result.promptContext).toContain('Pre-generated template files');
      expect(result.promptContext).toContain('player');
    });
  });

  describe('buildVariables', () => {
    it('should derive all variable forms from entity name', () => {
      const vars = service.buildVariables('team-player', 'Team Player Management');
      expect(vars.entity).toBe('teamPlayer');
      expect(vars.Entity).toBe('TeamPlayer');
      expect(vars.entities).toBe('teamPlayers');
      expect(vars.ENTITY).toBe('TEAM_PLAYER');
      expect(vars.PascalCaseName).toBe('TeamPlayerManagement');
      expect(vars.moduleName).toBe('team-player-management');
    });
  });
});

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-template-registry" --no-coverage Expected: FAIL — module not found

Create APIs/backend-vitto-api/src/app/module-generation/templates/template-registry.service.ts:

import { Injectable } from '@nestjs/common';
import { TemplateType, TemplateDefinition, TemplateVariable, GeneratedTemplate, TemplateFile } from './template-types';

@Injectable()
export class TemplateRegistryService {
  private readonly templates = new Map<TemplateType, TemplateDefinition>();

  constructor() {
    this.registerBuiltInTemplates();
  }

  getTemplate(type: TemplateType): TemplateDefinition {
    const template = this.templates.get(type);
    if (!template) throw new Error(`Template type "${type}" not found`);
    return template;
  }

  getAvailableTypes(): TemplateType[] {
    return Array.from(this.templates.keys());
  }

  buildVariables(entitySlug: string, displayName: string): TemplateVariable {
    const camelCase = entitySlug.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
    const pascalCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
    const plural = camelCase.endsWith('s') ? camelCase : camelCase + 's';
    const pluralPascal = plural.charAt(0).toUpperCase() + plural.slice(1);
    const snakeUpper = entitySlug.replace(/-/g, '_').toUpperCase();
    const modulePascal = displayName.replace(/\s+/g, '');

    return {
      entity: camelCase,
      Entity: pascalCase,
      entities: plural,
      Entities: pluralPascal,
      ENTITY: snakeUpper,
      PascalCaseName: modulePascal,
      moduleName: entitySlug + '-management',
    };
  }

  generateFromTemplate(type: TemplateType, variables: TemplateVariable): GeneratedTemplate {
    const template = this.getTemplate(type);
    const files = template.files.map(file => ({
      path: this.interpolate(file.relativePath, variables),
      content: this.interpolate(file.content, variables),
    }));

    const promptContext = this.buildPromptContext(type, variables, files);

    return { type, variables, files, promptContext };
  }

  getTemplatePromptContext(type: TemplateType, variables: TemplateVariable): string {
    const generated = this.generateFromTemplate(type, variables);
    return generated.promptContext;
  }

  private interpolate(text: string, vars: TemplateVariable): string {
    return text
      .replace(/\{\{entity\}\}/g, vars.entity)
      .replace(/\{\{Entity\}\}/g, vars.Entity)
      .replace(/\{\{entities\}\}/g, vars.entities)
      .replace(/\{\{Entities\}\}/g, vars.Entities)
      .replace(/\{\{ENTITY\}\}/g, vars.ENTITY)
      .replace(/\{\{PascalCaseName\}\}/g, vars.PascalCaseName)
      .replace(/\{\{moduleName\}\}/g, vars.moduleName);
  }

  private buildPromptContext(type: TemplateType, variables: TemplateVariable, files: { path: string; content: string }[]): string {
    const fileList = files.map(f => `- ${f.path}`).join('\n');
    return [
      `## Pre-generated template files (${type} template)`,
      `Entity: ${variables.Entity} (${variables.entity})`,
      `Module: ${variables.moduleName}`,
      ``,
      `The following files have been pre-generated. ENHANCE them with domain-specific logic — do NOT rewrite from scratch:`,
      fileList,
      ``,
      `Use composable functions: useCrudList(), useCrudForm(), useCrudDetail()`,
      `All components must be standalone, OnPush, using inject() and signals.`,
    ].join('\n');
  }

  private registerBuiltInTemplates(): void {
    this.templates.set('crud', this.buildCrudTemplate());
    this.templates.set('dashboard', this.buildDashboardTemplate());
    this.templates.set('analytics', this.buildAnalyticsTemplate());
    this.templates.set('integration', this.buildIntegrationTemplate());
    this.templates.set('basic', this.buildBasicTemplate());
  }

  private buildCrudTemplate(): TemplateDefinition {
    return {
      type: 'crud',
      displayName: 'CRUD Module',
      description: 'Complete CRUD operations with list, detail, and form views',
      tokenReduction: 45,
      files: [
        {
          relativePath: 'models/{{entity}}.model.ts',
          language: 'typescript',
          content: [
            `export interface {{Entity}} {`,
            `  id: string;`,
            `  name: string;`,
            `  description?: string;`,
            `  createdAt: Date;`,
            `  updatedAt: Date;`,
            `  // TODO: Add domain-specific fields`,
            `}`,
            ``,
            `export interface {{Entity}}Filter {`,
            `  search?: string;`,
            `  sortBy?: string;`,
            `  sortOrder?: 'asc' | 'desc';`,
            `  page?: number;`,
            `  limit?: number;`,
            `}`,
          ].join('\n'),
        },
        {
          relativePath: 'models/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.model';`,
        },
        {
          relativePath: 'services/{{entity}}.repository.ts',
          language: 'typescript',
          content: [
            `import { Injectable } from '@angular/core';`,
            `import { BaseCrudApiService } from '@libs-core/api';`,
            `import { {{Entity}} } from '../models';`,
            ``,
            `@Injectable({ providedIn: 'root' })`,
            `export class {{Entity}}Repository extends BaseCrudApiService<{{Entity}}> {`,
            `  protected override readonly baseUrl = '/api/{{entities}}';`,
            `  protected override readonly entityName = '{{Entity}}';`,
            `}`,
          ].join('\n'),
        },
        {
          relativePath: 'services/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.repository';`,
        },
        {
          relativePath: 'components/{{entity}}-list/{{entity}}-list.component.ts',
          language: 'typescript',
          content: [
            `import { Component, ChangeDetectionStrategy, inject } from '@angular/core';`,
            `import { {{Entity}}Repository } from '../../services';`,
            `import { {{Entity}} } from '../../models';`,
            ``,
            `@Component({`,
            `  selector: 'app-{{entity}}-list',`,
            `  standalone: true,`,
            `  templateUrl: './{{entity}}-list.component.html',`,
            `  styleUrls: ['./{{entity}}-list.component.scss'],`,
            `  changeDetection: ChangeDetectionStrategy.OnPush,`,
            `})`,
            `export class {{Entity}}ListComponent {`,
            `  private readonly repo = inject({{Entity}}Repository);`,
            `  // TODO: Wire useCrudList() composable`,
            `}`,
          ].join('\n'),
        },
        {
          relativePath: 'components/{{entity}}-detail/{{entity}}-detail.component.ts',
          language: 'typescript',
          content: [
            `import { Component, ChangeDetectionStrategy, inject } from '@angular/core';`,
            `import { {{Entity}}Repository } from '../../services';`,
            ``,
            `@Component({`,
            `  selector: 'app-{{entity}}-detail',`,
            `  standalone: true,`,
            `  templateUrl: './{{entity}}-detail.component.html',`,
            `  styleUrls: ['./{{entity}}-detail.component.scss'],`,
            `  changeDetection: ChangeDetectionStrategy.OnPush,`,
            `})`,
            `export class {{Entity}}DetailComponent {`,
            `  private readonly repo = inject({{Entity}}Repository);`,
            `  // TODO: Wire useCrudDetail() composable`,
            `}`,
          ].join('\n'),
        },
        {
          relativePath: 'components/{{entity}}-form/{{entity}}-form.component.ts',
          language: 'typescript',
          content: [
            `import { Component, ChangeDetectionStrategy, inject } from '@angular/core';`,
            `import { {{Entity}}Repository } from '../../services';`,
            ``,
            `@Component({`,
            `  selector: 'app-{{entity}}-form',`,
            `  standalone: true,`,
            `  templateUrl: './{{entity}}-form.component.html',`,
            `  styleUrls: ['./{{entity}}-form.component.scss'],`,
            `  changeDetection: ChangeDetectionStrategy.OnPush,`,
            `})`,
            `export class {{Entity}}FormComponent {`,
            `  private readonly repo = inject({{Entity}}Repository);`,
            `  // TODO: Wire useCrudForm() composable`,
            `}`,
          ].join('\n'),
        },
      ],
    };
  }

  private buildDashboardTemplate(): TemplateDefinition {
    return {
      type: 'dashboard',
      displayName: 'Dashboard Module',
      description: 'Dashboard with stats widgets and charts',
      tokenReduction: 35,
      files: [
        {
          relativePath: 'models/{{entity}}.model.ts',
          language: 'typescript',
          content: `export interface {{Entity}}Stats {\n  total: number;\n  active: number;\n  recent: number;\n}\n\nexport interface {{Entity}}ChartData {\n  labels: string[];\n  values: number[];\n}`,
        },
        {
          relativePath: 'models/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.model';`,
        },
        {
          relativePath: 'components/{{entity}}-dashboard/{{entity}}-dashboard.component.ts',
          language: 'typescript',
          content: `import { Component, ChangeDetectionStrategy } from '@angular/core';\n\n@Component({\n  selector: 'app-{{entity}}-dashboard',\n  standalone: true,\n  templateUrl: './{{entity}}-dashboard.component.html',\n  styleUrls: ['./{{entity}}-dashboard.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class {{Entity}}DashboardComponent {\n  // TODO: Add dashboard widgets\n}`,
        },
      ],
    };
  }

  private buildAnalyticsTemplate(): TemplateDefinition {
    return {
      type: 'analytics',
      displayName: 'Analytics Module',
      description: 'Analytics-focused module with data visualization',
      tokenReduction: 30,
      files: [
        {
          relativePath: 'models/{{entity}}.model.ts',
          language: 'typescript',
          content: `export interface {{Entity}}Metric {\n  name: string;\n  value: number;\n  trend: 'up' | 'down' | 'stable';\n  period: string;\n}`,
        },
        {
          relativePath: 'models/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.model';`,
        },
      ],
    };
  }

  private buildIntegrationTemplate(): TemplateDefinition {
    return {
      type: 'integration',
      displayName: 'Integration Module',
      description: 'System integration module with API connectors',
      tokenReduction: 25,
      files: [
        {
          relativePath: 'models/{{entity}}.model.ts',
          language: 'typescript',
          content: `export interface {{Entity}}Config {\n  endpoint: string;\n  apiKey?: string;\n  enabled: boolean;\n}`,
        },
        {
          relativePath: 'models/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.model';`,
        },
      ],
    };
  }

  private buildBasicTemplate(): TemplateDefinition {
    return {
      type: 'basic',
      displayName: 'Basic Module',
      description: 'Minimal scaffold with model and single component',
      tokenReduction: 15,
      files: [
        {
          relativePath: 'models/{{entity}}.model.ts',
          language: 'typescript',
          content: `export interface {{Entity}} {\n  id: string;\n  name: string;\n}`,
        },
        {
          relativePath: 'models/index.ts',
          language: 'typescript',
          content: `export * from './{{entity}}.model';`,
        },
      ],
    };
  }
}

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-template-registry" --no-coverage Expected: All pass

git add APIs/backend-vitto-api/src/app/module-generation/templates/
git commit -m "[S197.5] feat: add template registry with 5 template types (crud, dashboard, analytics, integration, basic)"

Task 5: Composable Registry Service

Files:

Create APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-composables.spec.ts:

import { ComposableRegistryService } from '../composables/composable-registry.service';

describe('S197 — ComposableRegistryService', () => {
  let service: ComposableRegistryService;

  beforeEach(() => {
    service = new ComposableRegistryService();
  });

  it('should return useCrudList composable definition', () => {
    const composable = service.getComposable('useCrudList');
    expect(composable).toBeDefined();
    expect(composable.name).toBe('useCrudList');
    expect(composable.usage).toContain('useCrudList');
  });

  it('should return useCrudForm composable definition', () => {
    const composable = service.getComposable('useCrudForm');
    expect(composable).toBeDefined();
  });

  it('should return useCrudDetail composable definition', () => {
    const composable = service.getComposable('useCrudDetail');
    expect(composable).toBeDefined();
  });

  it('should generate prompt context with all composable docs', () => {
    const context = service.getPromptContext();
    expect(context).toContain('useCrudList');
    expect(context).toContain('useCrudForm');
    expect(context).toContain('useCrudDetail');
  });
});

Create APIs/backend-vitto-api/src/app/module-generation/composables/composable-registry.service.ts:

import { Injectable } from '@nestjs/common';

export interface ComposableDefinition {
  name: string;
  description: string;
  usage: string;
  configInterface: string;
  shellComponent: string;
}

@Injectable()
export class ComposableRegistryService {
  private readonly composables = new Map<string, ComposableDefinition>();

  constructor() {
    this.registerBuiltIn();
  }

  getComposable(name: string): ComposableDefinition {
    const composable = this.composables.get(name);
    if (!composable) throw new Error(`Composable "${name}" not found`);
    return composable;
  }

  getAll(): ComposableDefinition[] {
    return Array.from(this.composables.values());
  }

  getPromptContext(): string {
    const sections = this.getAll().map(c => [
      `### ${c.name}`,
      c.description,
      `Shell component: ${c.shellComponent}`,
      `Usage:`,
      '```typescript',
      c.usage,
      '```',
      `Config interface:`,
      '```typescript',
      c.configInterface,
      '```',
    ].join('\n'));

    return [
      '## Available Composable Functions',
      'Use these composables to configure shell components. Do NOT create raw table/form/detail components.',
      '',
      ...sections,
    ].join('\n\n');
  }

  private registerBuiltIn(): void {
    this.composables.set('useCrudList', {
      name: 'useCrudList',
      description: 'Configures a CRUD list view with table/card display, filters, pagination, and bulk actions.',
      shellComponent: 'GgsCrudListShellComponent',
      usage: [
        `readonly crud = useCrudList<Player>({`,
        `  loadFn: (params) => this.repo.getList(params),`,
        `  basePath: '/players',`,
        `  entityName: 'Player',`,
        `  deleteFn: (ids) => this.repo.delete(ids[0]),`,
        `  columns: [`,
        `    { field: 'name', header: 'Name', sortable: true },`,
        `    { field: 'position', header: 'Position' },`,
        `    { field: 'team', header: 'Team' },`,
        `  ],`,
        `  displayMode: 'table', // or 'cards'`,
        `  filters: [`,
        `    { field: 'position', type: 'select', options: ['GK', 'DEF', 'MID', 'FWD'] },`,
        `  ],`,
        `});`,
      ].join('\n'),
      configInterface: [
        `interface CrudListConfig<T> {`,
        `  loadFn: (params: ListParams) => Observable<PaginatedResult<T>>;`,
        `  basePath: string;`,
        `  entityName: string;`,
        `  deleteFn?: (ids: string[]) => Observable<void>;`,
        `  columns: ColumnDef[];`,
        `  displayMode?: 'table' | 'cards';`,
        `  filters?: FilterDef[];`,
        `  bulkActions?: BulkAction[];`,
        `  pageSize?: number;`,
        `}`,
      ].join('\n'),
    });

    this.composables.set('useCrudForm', {
      name: 'useCrudForm',
      description: 'Configures a CRUD form with field definitions, validation, and layout.',
      shellComponent: 'GgsCrudFormShellComponent',
      usage: [
        `readonly form = useCrudForm<Player>({`,
        `  loadFn: (id) => this.repo.getById(id),`,
        `  saveFn: (data) => this.repo.save(data),`,
        `  basePath: '/players',`,
        `  fields: [`,
        `    { name: 'name', type: 'text', label: 'Player Name', required: true },`,
        `    { name: 'position', type: 'select', label: 'Position', options: ['GK', 'DEF', 'MID', 'FWD'] },`,
        `    { name: 'birthDate', type: 'date', label: 'Date of Birth' },`,
        `    { name: 'team', type: 'entity-lookup', label: 'Team', entityType: 'teams' },`,
        `  ],`,
        `  layout: 'two-column',`,
        `});`,
      ].join('\n'),
      configInterface: [
        `interface CrudFormConfig<T> {`,
        `  loadFn: (id: string) => Observable<T>;`,
        `  saveFn: (data: Partial<T>) => Observable<T>;`,
        `  basePath: string;`,
        `  fields: FieldDef[];`,
        `  layout?: 'single-column' | 'two-column' | 'tabs';`,
        `  sections?: FormSection[];`,
        `}`,
      ].join('\n'),
    });

    this.composables.set('useCrudDetail', {
      name: 'useCrudDetail',
      description: 'Configures a CRUD detail view with sections, related entities, and action buttons.',
      shellComponent: 'GgsCrudDetailShellComponent',
      usage: [
        `readonly detail = useCrudDetail<Player>({`,
        `  loadFn: (id) => this.repo.getById(id),`,
        `  basePath: '/players',`,
        `  sections: [`,
        `    { title: 'Basic Info', fields: ['name', 'position', 'team'] },`,
        `    { title: 'Stats', fields: ['goals', 'assists', 'matches'] },`,
        `  ],`,
        `  relatedEntities: [`,
        `    { name: 'injuries', loadFn: (id) => this.injuryRepo.getByPlayer(id) },`,
        `  ],`,
        `  actions: [`,
        `    { label: 'Edit', icon: 'edit', route: './edit' },`,
        `    { label: 'Delete', icon: 'trash', action: (entity) => this.repo.delete(entity.id) },`,
        `  ],`,
        `});`,
      ].join('\n'),
      configInterface: [
        `interface CrudDetailConfig<T> {`,
        `  loadFn: (id: string) => Observable<T>;`,
        `  basePath: string;`,
        `  sections: DetailSection[];`,
        `  relatedEntities?: RelatedEntityDef[];`,
        `  actions?: ActionDef[];`,
        `}`,
      ].join('\n'),
    });
  }
}

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-composables" --no-coverage Expected: All pass

git add APIs/backend-vitto-api/src/app/module-generation/composables/
git commit -m "[S197.6] feat: add composable registry (useCrudList, useCrudForm, useCrudDetail)"

Task 6: Enhance Phase Prompts with gmodules1 Rules

Files:

Read all 5 prompt files to understand their current structure.

Append to each prompt file a new section:

## Critical Rules (from past generation failures — MUST follow)

### Component Rules
- `<fa-icon>` requires tuple: `[icon]="['fas', 'icon-name']"` — NOT string
- `<ggs-button>` accepts string: `icon="icon-name"` — NOT tuple
- Entity lookups: NEVER use text input for foreign keys — use `GgsEntityLookupComponent`
- `GgsTable` requires `config` + `data` inputs (NOT `columns`)

### TypeScript Rules
- No `any` type — use specific types or SafeAny
- No optional chaining on non-nullable types (causes NG8107)
- DomainEvent uses `eventName` property
- BaseRepository returns `T` directly (not Observable<T>)

### Routing Rules
- Parameterized routes (`:id`) MUST come AFTER static routes
- Single-entity modules: routes are direct children of basePath
- Multi-entity modules: sub-groups as direct children (no extra empty-path wrapper)
- basePath MUST be module's own entity ID (not system base route)

### Module Integration Rules
- Every `*-mock-api.ts` file MUST have named re-export in `mocks/index.ts`
- Mock data must match model exactly (no extra fields)
- Barrel export chain: src/index.ts → src/lib/index.ts → individual files

### Composable Rules
- ALWAYS use useCrudList() for list views — never raw table
- ALWAYS use useCrudForm() for forms — never raw form builder
- ALWAYS use useCrudDetail() for detail views
- Components must be standalone, OnPush, using inject() and signals

Append to implement-visual.prompt.md and implement.prompt.md:

## Template Pre-Generation

Pre-generated template files are provided in the context. Your job is to ENHANCE them with domain-specific logic:
- Do NOT rewrite the entire file — modify the TODOs and add domain logic
- Keep the component structure (standalone, OnPush, inject())
- Wire the composable functions with domain-specific configuration
- Add domain fields to the model interface
- Add domain routes to the route definitions

Run: npx nx build backend-vitto-api --skip-nx-cache Expected: Build succeeds

git add APIs/backend-vitto-api/src/app/prompts/module-gen/
git commit -m "[S197.7] feat: enhance phase prompts with gmodules1 failure rules + template instructions"

Task 7: Wire Template Registry into Generation Pipeline

Files:

describe('S197 — Pipeline Wiring', () => {
  it('should include template prompt context in generation context', async () => {
    // Mock TemplateRegistryService and verify it's called during context building
  });

  it('should include composable prompt context in generation context', async () => {
    // Mock ComposableRegistryService and verify prompt context is included
  });
});

Add to providers array in module-generation.module.ts:

import { TemplateRegistryService } from './templates/template-registry.service';
import { ComposableRegistryService } from './composables/composable-registry.service';

// In providers array:
TemplateRegistryService,
ComposableRegistryService,

In generation-context.service.ts, inject the new services and include their context in the prompt:

// Add to constructor:
private readonly templateRegistry: TemplateRegistryService,
private readonly composableRegistry: ComposableRegistryService,

// In buildGenerationContext, add after mastery context:
const templateType = this.detectTemplateType(moduleDescription); // 'crud' by default
const variables = this.templateRegistry.buildVariables(entitySlug, displayName);
const templateContext = this.templateRegistry.getTemplatePromptContext(templateType, variables);
const composableContext = this.composableRegistry.getPromptContext();

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage Expected: All pass

Run: npx nx build backend-vitto-api --skip-nx-cache Expected: Build succeeds

git add APIs/backend-vitto-api/src/app/module-generation/context/generation-context.service.ts APIs/backend-vitto-api/src/app/module-generation/module-generation.module.ts APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-pipeline-wiring.spec.ts
git commit -m "[S197.8] feat: wire template registry + composables into generation pipeline"

Chunk 3: Context Cache + Checkpoints + Validation (Stream A)

Task 8: Project Context Cache Service

Files:

import { ProjectContextCacheService } from '../context/project-context-cache.service';

describe('S197 — ProjectContextCacheService', () => {
  let service: ProjectContextCacheService;

  beforeEach(() => {
    service = new ProjectContextCacheService();
  });

  it('should cache project context with 30min TTL', () => {
    service.set('space-123', { modules: ['mod-a'], dependencies: [] });
    const cached = service.get('space-123');
    expect(cached).toBeDefined();
    expect(cached.modules).toEqual(['mod-a']);
  });

  it('should return null for expired cache', () => {
    service.set('space-123', { modules: [] }, 0); // TTL 0 = expired
    const cached = service.get('space-123');
    expect(cached).toBeNull();
  });

  it('should invalidate cache for specific space', () => {
    service.set('space-123', { modules: [] });
    service.invalidate('space-123');
    expect(service.get('space-123')).toBeNull();
  });

  it('should provide sibling module context', () => {
    service.set('space-123', { modules: ['users', 'teams'], dependencies: ['users→teams'] });
    const siblings = service.getSiblingContext('space-123', 'players');
    expect(siblings).toContain('users');
    expect(siblings).toContain('teams');
  });
});
import { Injectable, Logger } from '@nestjs/common';

interface CachedContext {
  modules: string[];
  dependencies: string[];
  structure?: Record<string, string[]>;
  cachedAt: number;
  ttlMs: number;
}

@Injectable()
export class ProjectContextCacheService {
  private readonly logger = new Logger(ProjectContextCacheService.name);
  private readonly cache = new Map<string, CachedContext>();
  private readonly DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes

  set(spaceId: string, data: Omit<CachedContext, 'cachedAt' | 'ttlMs'>, ttlMs?: number): void {
    this.cache.set(spaceId, { ...data, cachedAt: Date.now(), ttlMs: ttlMs ?? this.DEFAULT_TTL_MS });
  }

  get(spaceId: string): Omit<CachedContext, 'cachedAt' | 'ttlMs'> | null {
    const cached = this.cache.get(spaceId);
    if (!cached) return null;
    if (Date.now() - cached.cachedAt > cached.ttlMs) {
      this.cache.delete(spaceId);
      return null;
    }
    return { modules: cached.modules, dependencies: cached.dependencies, structure: cached.structure };
  }

  invalidate(spaceId: string): void {
    this.cache.delete(spaceId);
    this.logger.log(`[ContextCache] Invalidated cache for space ${spaceId}`);
  }

  getSiblingContext(spaceId: string, currentModule: string): string {
    const ctx = this.get(spaceId);
    if (!ctx) return '';
    const siblings = ctx.modules.filter(m => m !== currentModule);
    if (!siblings.length) return '';
    return `Existing modules in this space: ${siblings.join(', ')}. Dependencies: ${ctx.dependencies.join(', ')}`;
  }
}
git add APIs/backend-vitto-api/src/app/module-generation/context/project-context-cache.service.ts APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-context-cache.spec.ts
git commit -m "[S197.9] feat: add project context cache with 30min TTL"

Task 9: Phase Checkpoint Service

Files:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema({ timestamps: true, collection: 'generation-checkpoints' })
export class GenerationCheckpoint extends Document {
  @Prop({ required: true }) sessionId: string;
  @Prop({ required: true }) phase: string;
  @Prop({ required: true }) status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'paused';
  @Prop({ type: Object }) state: Record<string, unknown>;
  @Prop({ type: [Object] }) filesGenerated: { path: string; content: string }[];
  @Prop({ type: Object }) entityModel: Record<string, unknown>;
  @Prop({ type: [Object] }) decisions: { question: string; answer: string }[];
  @Prop() tokensUsed: number;
  @Prop() error?: string;
}

export const GenerationCheckpointSchema = SchemaFactory.createForClass(GenerationCheckpoint);

Service should support: saveCheckpoint(sessionId, phase, data), getCheckpoint(sessionId, phase), getLatestCheckpoint(sessionId), resumeFromCheckpoint(sessionId).

git add APIs/backend-vitto-api/src/app/module-generation/checkpoint/
git commit -m "[S197.10] feat: add phase checkpoint service with Mongoose persistence"

Task 10: Phase Validation Service

Files:

describe('S197 — PhaseValidationService', () => {
  it('should validate spec phase: specification_complete', () => { /* ... */ });
  it('should validate implement phase: components_complete, signals_used, inject_used', () => { /* ... */ });
  it('should validate finalize phase: build_passes, lint_passes', () => { /* ... */ });
  it('should return validation result with pass/fail per rule', () => { /* ... */ });
});
git add APIs/backend-vitto-api/src/app/module-generation/validation/
git commit -m "[S197.11] feat: add phase validation service with gmodules1 rules"

Task 11: Emit New SSE Events from Phase Executor

Files:

After existing events (line 142), add emission of:

After each phase completes, save checkpoint via PhaseCheckpointService.

git add APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.ts
git commit -m "[S197.12] feat: emit pipeline SSE events + checkpoint saves in phase executor"

Chunk 4: Pipeline Visualizer Panel (Stream B)

Task 12: Pipeline State Service (vitto-studio)

Files:

import { Injectable, signal, computed } from '@angular/core';

export type PhaseStatus = 'pending' | 'running' | 'complete' | 'failed' | 'skipped' | 'paused';

export interface PipelineNode {
  id: string;
  name: string;
  skillId: string;
  status: PhaseStatus;
  progress: number;  // 0-100
  filesGenerated: number;
  filesTotal: number;
  tokensUsed: number;
  tokensBudget: number;
  masteryLevel: string;
  masteryScore: number;
  startedAt?: number;
  completedAt?: number;
  error?: string;
  logLines: string[];
  dependencies: string[];  // node IDs this depends on
  parallel: boolean;
}

export interface PipelineState {
  sessionId: string;
  status: 'idle' | 'running' | 'completed' | 'failed' | 'paused';
  nodes: PipelineNode[];
  totalProgress: number;
  totalTokens: number;
  startedAt?: number;
  completedAt?: number;
}

@Injectable()
export class PipelineStateService {
  readonly state = signal<PipelineState>({ sessionId: '', status: 'idle', nodes: [], totalProgress: 0, totalTokens: 0 });
  readonly activeNode = computed(() => this.state().nodes.find(n => n.status === 'running'));
  readonly isRunning = computed(() => this.state().status === 'running');

  initFromPlaybook(sessionId: string, orchestrationSteps: any[]): void { /* ... */ }
  handleSSEEvent(event: { type: string; data: any }): void { /* ... */ }
  selectNode(nodeId: string): void { /* ... */ }
  // Editor mode methods:
  reorderNodes(fromIndex: number, toIndex: number): void { /* ... */ }
  toggleParallel(nodeId: string): void { /* ... */ }
  addNode(node: Partial<PipelineNode>): void { /* ... */ }
  removeNode(nodeId: string): void { /* ... */ }
}

Task 13: Pipeline Visualizer Component (vitto-studio)

Files:

Renders a single DAG node with: name, status icon, mastery badge, progress bar, file count.

DAG layout (left 60%) + detail panel (right 40%). Uses PipelineStateService signals for reactive rendering. SVG edges between nodes showing dependencies.

Pending (dim), running (pulsing border + glow), complete (green), failed (red), paused (amber).

Task 14: Register Pipeline Panel in Dock Layout

Files:

Add handlers for: pipeline.status, phase.file.list, phase.token.usage, phase.mastery.update — route to PipelineStateService.

Run: npx nx build gograb-ai --skip-nx-cache Expected: Build succeeds

git add libs/libs-structural/vitto-studio/src/lib/components/pipeline-visualizer/ libs/libs-structural/vitto-studio/src/lib/panels/pipeline-visualizer-panel.component.ts libs/libs-structural/vitto-studio/src/lib/config/studio-dock-panels.config.ts libs/libs-structural/vitto-studio/src/lib/services/ libs/libs-structural/vitto-studio/src/index.ts
git commit -m "[S197.13] feat: add pipeline visualizer panel to vitto-studio dock layout"

Task 15: Toolbar Generation Indicator Enhancement

Files:


Chunk 5: Workflow-Generation Integration (Stream B)

Task 16: Add Generation Step Types to Workflow Builder

Files:

At line 72-79, add generation step types:

// Existing: trigger, action, condition, response, delay, loop
// New:
{ type: 'analyze', label: 'Analyze', icon: 'search', color: '#4ecca3', category: 'generation' },
{ type: 'plan', label: 'Plan', icon: 'sitemap', color: '#3498db', category: 'generation' },
{ type: 'generate', label: 'Generate', icon: 'code', color: '#e94560', category: 'generation' },
{ type: 'verify', label: 'Verify', icon: 'check-double', color: '#2ecc71', category: 'generation' },
{ type: 'checkpoint', label: 'Checkpoint', icon: 'hand-paper', color: '#f0a500', category: 'generation' },

Add a "Generation" category tab alongside existing step types in the step toolbar.

importFromPlaybook(playbook: JobPlaybookSummary): void {
  const steps = playbook.orchestration?.steps?.map((step, i) => ({
    id: `gen-step-${i}`,
    type: this.mapOrchestrationType(step.type),
    name: step.name,
    config: step,
    position: { x: 400, y: 100 + i * 120 },
  }));
  this.workflow().steps = steps;
}
git add libs/libs-ai/vitto-hub/src/lib/components/workflow-builder-page/
git commit -m "[S197.14] feat: add generation step types + playbook import to workflow builder"

Chunk 6: Conversational Generation + Chat Plugins (Stream C)

Task 17: Wire PlaybookCreator "Let Vitto Help"

Files:

generatePlaybookFromDescription(description: string): Observable<Partial<JobPlaybookSummary>> {
  return this.http.post<Partial<JobPlaybookSummary>>(`${this.vittoApiUrl}/users/playbooks/generate`, { description });
}

Replace the stub at line 38-43:

onLetVittoHelp(): void {
  const description = this.naturalDescription();
  if (!description?.trim()) return;

  this.vittoHelping.set(true);
  this.apiService.generatePlaybookFromDescription(description).subscribe({
    next: (generated) => {
      if (generated.jobType) this.jobType.set(generated.jobType);
      if (generated.displayName) this.displayName.set(generated.displayName);
      if (generated.category) this.category.set(generated.category);
      if (generated.description) this.description.set(generated.description);
      this.vittoHelping.set(false);
    },
    error: () => this.vittoHelping.set(false),
  });
}

In users.controller.ts, add:

@Post('playbooks/generate')
@Roles('executive', 'admin')
async generatePlaybook(@Body() dto: { description: string }) {
  return this.jobMasteryService.generatePlaybookFromDescription(dto.description);
}

Uses LLM to generate playbook fields from natural language description.

git add libs/libs-ai/vitto-intelligence/src/lib/components/playbook-creator/ libs/libs-ai/vitto-intelligence/src/lib/services/ APIs/backend-vitto-api/src/app/users/
git commit -m "[S197.15] feat: wire PlaybookCreator 'Let Vitto Help' to LLM generation"

Task 18: Wire Mastery Chat Plugin

Files:

Replace empty enrichMessage (lines 86-93):

enrichMessage(msg: ChatMessage): ChatMessage {
  const masteryContext = this.config?.getActiveMasteryContext?.();
  return {
    ...msg,
    metadata: {
      ...msg.metadata,
      ...(masteryContext && {
        masteryJobType: masteryContext.jobType,
        masteryLevel: masteryContext.level,
        masteryScore: masteryContext.score,
        personalPitfalls: masteryContext.pitfalls,
        preferredPatterns: masteryContext.patterns,
      }),
    },
  };
}
// In plugin command handlers:
'/mastery': () => this.config.router.navigate(['/vitto/mastery']),
'/dossier': () => this.config.router.navigate(['/vitto/dossier']),
'/new-job': (args) => this.config.router.navigate(['/vitto/mastery/new'], { queryParams: { description: args } }),
git add libs/libs-ai/vitto-chat/src/lib/plugins/mastery.plugin.ts
git commit -m "[S197.16] feat: wire mastery chat plugin enrichMessage + command navigation"

Task 19: Enhance Studio Chat Plugin

Files:

Add to command definitions (after line 99):

{
  command: '/modify',
  description: 'Modify last generated module',
  handler: (args) => this.config.onModifyRequested?.(args),
},
{
  command: '/pipeline',
  description: 'Show pipeline status',
  handler: () => this.config.onPipelineRequested?.(),
},
{
  command: '/review',
  description: 'Start file review',
  handler: () => this.config.onReviewRequested?.(),
},
{
  command: '/teach',
  description: 'Teach Vitto a preference',
  handler: (args) => this.config.onTeachRequested?.(args),
},

Modify existing /generate handler to first ask about interaction mode:

// In generate handler:
handler: (args) => {
  this.config.onGenerateRequested?.(args, { promptInteractionMode: true });
},
git add libs/libs-structural/vitto-studio/src/lib/plugins/studio.plugin.ts
git commit -m "[S197.17] feat: add /modify, /pipeline, /review, /teach commands to studio plugin"

Task 20: Interaction Mode in Generation Flow

Files:

getGenerationMode(userId: string): 'interactive' | 'autonomous' {
  // Check user dossier for preferred workflow mode
  // Default to 'autonomous' for first-time users
}

When mode is 'interactive', emit checkpoint.awaiting_input SSE event between phases. Phase executor pauses until checkpoint.resume message received.

git add APIs/backend-vitto-api/src/app/engine/autonomy.service.ts APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.ts
git commit -m "[S197.18] feat: add interactive/autonomous generation mode with checkpoint gates"

Chunk 7: Final Integration + Build Verification

Task 21: Full Build Verification

Run: npx nx build backend-vitto-api --skip-nx-cache Expected: Build succeeds

Run: npx nx build gograb-ai --skip-nx-cache Expected: Build succeeds

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage Expected: All 39 tests pass

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-195" --no-coverage Expected: All 21 tests pass

Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage Expected: All new tests pass

Run: npx nx lint vitto-intelligence --skip-nx-cache && npx nx lint vitto-studio --skip-nx-cache Expected: No errors

Task 22: Update Exports and Module Registration

git commit -m "[S197.19] chore: final integration — all builds passing, all tests green"

Parallel Execution Guide

Tasks can be parallelized as follows:

Critical Path (must be sequential):
  Task 1 → Task 2 → Task 3 (Agent Loop Fix)

After Critical Path completes, these streams run in parallel:

Stream A (Backend):
  Task 4 → Task 5 → Task 6 → Task 7 → Task 8 → Task 9 → Task 10 → Task 11

Stream B (Frontend):
  Task 12 → Task 13 → Task 14 → Task 15 → Task 16

Stream C (Conversational):
  Task 17 → Task 18 → Task 19 → Task 20

All streams converge at:
  Task 21 → Task 22 (Final Integration)

Sub-agent assignment recommendation: