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:
Modify:
APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts:108-285Modify:
APIs/backend-vitto-api/src/app/llm/providers/claude.provider.ts:33-60Test:
APIs/backend-vitto-api/src/app/engine/__tests__/session-197-agent-loop.spec.tsStep 1: Write failing test — tools are logged before API call
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();
});
});
- Step 2: Run test to verify it compiles
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-agent-loop" --no-coverage
Expected: Test passes (basic setup)
- Step 3: Add diagnostic logging to agent-loop.service.ts
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}`);
- Step 4: Add tool logging to claude.provider.ts
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`);
}
- Step 5: Run existing tests to verify no regressions
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage
Expected: All 39 S196 tests pass
- Step 6: Commit
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:
Modify:
APIs/backend-vitto-api/src/app/engine/context-assembly.service.ts:395-572Test:
APIs/backend-vitto-api/src/app/engine/__tests__/session-197-context-budget.spec.tsStep 1: Write failing test — system prompt respects token budget
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)
});
});
});
- Step 2: Run test to verify it fails
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)
- Step 3: Add token estimation and budget trimming to context-assembly.service.ts
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;
}
- Step 4: Integrate budget trimming into buildWebSystemPrompt
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
- Step 5: Run tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage
Expected: All S197 tests pass
- Step 6: Run S196 regression tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage
Expected: All 39 tests pass
- Step 7: Commit
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:
Modify:
APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts:112-120Modify:
APIs/backend-vitto-api/src/app/tools/tool-registry.service.ts:101-149Test:
APIs/backend-vitto-api/src/app/engine/__tests__/session-197-agent-loop.spec.ts(extend)Step 1: Write failing test — empty tools triggers fallback
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');
});
});
- Step 2: Add tool assertion to agent-loop.service.ts
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`);
}
- Step 3: Add getAllTools method to tool-registry.service.ts if missing
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,
}));
}
- Step 4: Run tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage
Expected: All pass
- Step 5: Build check
Run: npx nx build backend-vitto-api --skip-nx-cache
Expected: Build succeeds
- Step 6: Commit
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.tsCreate:
APIs/backend-vitto-api/src/app/module-generation/templates/template-registry.service.tsTest:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-template-registry.spec.tsStep 1: Create template types
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
}
- Step 2: Write failing test for TemplateRegistryService
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');
});
});
});
- Step 3: Run test to verify it fails
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-template-registry" --no-coverage
Expected: FAIL — module not found
- Step 4: Implement TemplateRegistryService
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';`,
},
],
};
}
}
- Step 5: Run tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-template-registry" --no-coverage
Expected: All pass
- Step 6: Commit
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/composables/composable-registry.service.tsTest:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-composables.spec.tsStep 1: Write failing test
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');
});
});
- Step 2: Implement ComposableRegistryService
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'),
});
}
}
- Step 3: Run tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197-composables" --no-coverage
Expected: All pass
- Step 4: Commit
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:
Modify:
APIs/backend-vitto-api/src/app/prompts/module-gen/system-analyze.prompt.mdModify:
APIs/backend-vitto-api/src/app/prompts/module-gen/system-decompose.prompt.mdModify:
APIs/backend-vitto-api/src/app/prompts/module-gen/system-plan.prompt.mdModify:
APIs/backend-vitto-api/src/app/prompts/module-gen/implement-visual.prompt.mdModify:
APIs/backend-vitto-api/src/app/prompts/module-gen/implement.prompt.mdStep 1: Read current prompt files
Read all 5 prompt files to understand their current structure.
- Step 2: Add critical failure rules section to each prompt
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
- Step 3: Add template-awareness instructions to implement prompts
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
- Step 4: Build check
Run: npx nx build backend-vitto-api --skip-nx-cache
Expected: Build succeeds
- Step 5: Commit
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:
Modify:
APIs/backend-vitto-api/src/app/module-generation/context/generation-context.service.ts:66-122Modify:
APIs/backend-vitto-api/src/app/module-generation/module-generation.module.ts:46-84Test:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-pipeline-wiring.spec.tsStep 1: Write failing test — generation context includes template data
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
});
});
- Step 2: Add TemplateRegistryService and ComposableRegistryService to module-generation.module.ts
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,
- Step 3: Inject into generation-context.service.ts
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();
- Step 4: Run tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage
Expected: All pass
- Step 5: Build check
Run: npx nx build backend-vitto-api --skip-nx-cache
Expected: Build succeeds
- Step 6: Commit
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:
Create:
APIs/backend-vitto-api/src/app/module-generation/context/project-context-cache.service.tsTest:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-context-cache.spec.tsStep 1: Write failing test
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');
});
});
- Step 2: Implement ProjectContextCacheService
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(', ')}`;
}
}
- Step 3: Run tests and commit
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:
Create:
APIs/backend-vitto-api/src/app/module-generation/checkpoint/checkpoint.schema.tsCreate:
APIs/backend-vitto-api/src/app/module-generation/checkpoint/phase-checkpoint.service.tsTest:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-checkpoints.spec.tsStep 1: Create checkpoint schema
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);
- Step 2: Write failing test and implement PhaseCheckpointService
Service should support: saveCheckpoint(sessionId, phase, data), getCheckpoint(sessionId, phase), getLatestCheckpoint(sessionId), resumeFromCheckpoint(sessionId).
- Step 3: Run tests and commit
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:
Create:
APIs/backend-vitto-api/src/app/module-generation/validation/phase-validation.service.tsTest:
APIs/backend-vitto-api/src/app/module-generation/__tests__/session-197-validation.spec.tsStep 1: Write failing test
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', () => { /* ... */ });
});
Step 2: Implement PhaseValidationService with rule sets per phase
Step 3: Run tests and commit
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:
Modify:
APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.ts:91-142Step 1: Add new SSE event types
After existing events (line 142), add emission of:
pipeline.status— full pipeline state on each phase transitionphase.file.list— files generated in current phasephase.token.usage— token consumption per phasephase.mastery.update— mastery score change after phase completionStep 2: Integrate checkpoint saving into phase executor
After each phase completes, save checkpoint via PhaseCheckpointService.
Step 3: Run tests and build check
Step 4: Commit
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:
Create:
libs/libs-structural/vitto-studio/src/lib/services/pipeline-state.service.tsStep 1: Create signal-based pipeline state service
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 { /* ... */ }
}
- Step 2: Commit
Task 13: Pipeline Visualizer Component (vitto-studio)
Files:
Create:
libs/libs-structural/vitto-studio/src/lib/components/pipeline-visualizer/pipeline-visualizer.component.tsCreate:
libs/libs-structural/vitto-studio/src/lib/components/pipeline-visualizer/pipeline-visualizer.component.htmlCreate:
libs/libs-structural/vitto-studio/src/lib/components/pipeline-visualizer/pipeline-visualizer.component.scssCreate:
libs/libs-structural/vitto-studio/src/lib/components/pipeline-visualizer/pipeline-node.component.tsStep 1: Create PipelineNodeComponent
Renders a single DAG node with: name, status icon, mastery badge, progress bar, file count.
- Step 2: Create PipelineVisualizerComponent
DAG layout (left 60%) + detail panel (right 40%). Uses PipelineStateService signals for reactive rendering. SVG edges between nodes showing dependencies.
- Step 3: Create SCSS with node state styles
Pending (dim), running (pulsing border + glow), complete (green), failed (red), paused (amber).
- Step 4: Commit
Task 14: Register Pipeline Panel in Dock Layout
Files:
Create:
libs/libs-structural/vitto-studio/src/lib/panels/pipeline-visualizer-panel.component.tsModify:
libs/libs-structural/vitto-studio/src/lib/config/studio-dock-panels.config.ts:29-48Modify:
libs/libs-structural/vitto-studio/src/lib/services/studio-generation-listener.service.ts:51-116Modify:
libs/libs-structural/vitto-studio/src/index.tsStep 1: Create dock panel wrapper
Step 2: Register in STUDIO_DOCK_PANELS array (line 29-48)
Step 3: Wire new SSE events into StudioGenerationListenerService
Add handlers for: pipeline.status, phase.file.list, phase.token.usage, phase.mastery.update — route to PipelineStateService.
Step 4: Export from index.ts
Step 5: Build check
Run: npx nx build gograb-ai --skip-nx-cache
Expected: Build succeeds
- Step 6: Commit
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:
Modify:
libs/libs-structural/vitto-studio/src/lib/components/studio-toolbar/studio-toolbar.component.tsModify:
libs/libs-structural/vitto-studio/src/lib/components/studio-toolbar/studio-toolbar.component.htmlStep 1: Inject PipelineStateService into toolbar
Step 2: Add phase name + progress % to toolbar template
Step 3: Add click handler to focus pipeline panel in dock layout
Step 4: Commit
Chunk 5: Workflow-Generation Integration (Stream B)
Task 16: Add Generation Step Types to Workflow Builder
Files:
Modify:
libs/libs-ai/vitto-hub/src/lib/components/workflow-builder-page/workflow-builder-page.component.ts:72-79Step 1: Extend step type definitions
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' },
- Step 2: Add generation step category filter in toolbar
Add a "Generation" category tab alongside existing step types in the step toolbar.
- Step 3: Add playbook-to-workflow conversion
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;
}
- Step 4: Commit
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:
Modify:
libs/libs-ai/vitto-intelligence/src/lib/components/playbook-creator/playbook-creator.component.ts:38-43Modify:
libs/libs-ai/vitto-intelligence/src/lib/services/vitto-intelligence-api.service.tsStep 1: Add generatePlaybook endpoint to API service
generatePlaybookFromDescription(description: string): Observable<Partial<JobPlaybookSummary>> {
return this.http.post<Partial<JobPlaybookSummary>>(`${this.vittoApiUrl}/users/playbooks/generate`, { description });
}
- Step 2: Wire onLetVittoHelp to call the API
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),
});
}
- Step 3: Add backend endpoint (backend-vitto-api)
In users.controller.ts, add:
@Post('playbooks/generate')
@Roles('executive', 'admin')
async generatePlaybook(@Body() dto: { description: string }) {
return this.jobMasteryService.generatePlaybookFromDescription(dto.description);
}
- Step 4: Implement generatePlaybookFromDescription in JobMasteryService
Uses LLM to generate playbook fields from natural language description.
Step 5: Build check
Step 6: Commit
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:
Modify:
libs/libs-ai/vitto-chat/src/lib/plugins/mastery.plugin.ts:86-93Step 1: Implement enrichMessage with mastery context
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,
}),
},
};
}
- Step 2: Wire slash command navigation handlers
// 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 } }),
- Step 3: Commit
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:
Modify:
libs/libs-structural/vitto-studio/src/lib/plugins/studio.plugin.ts:56-99Step 1: Add new slash commands
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),
},
- Step 2: Add interaction mode prompt to /generate command
Modify existing /generate handler to first ask about interaction mode:
// In generate handler:
handler: (args) => {
this.config.onGenerateRequested?.(args, { promptInteractionMode: true });
},
- Step 3: Commit
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:
Modify:
APIs/backend-vitto-api/src/app/engine/autonomy.service.tsModify:
APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.tsStep 1: Add generation interaction mode to autonomy service
getGenerationMode(userId: string): 'interactive' | 'autonomous' {
// Check user dossier for preferred workflow mode
// Default to 'autonomous' for first-time users
}
- Step 2: Add checkpoint gates in phase executor for interactive mode
When mode is 'interactive', emit checkpoint.awaiting_input SSE event between phases. Phase executor pauses until checkpoint.resume message received.
- Step 3: Commit
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
- Step 1: Backend build
Run: npx nx build backend-vitto-api --skip-nx-cache
Expected: Build succeeds
- Step 2: Frontend build
Run: npx nx build gograb-ai --skip-nx-cache
Expected: Build succeeds
- Step 3: S196 regression tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-196" --no-coverage
Expected: All 39 tests pass
- Step 4: S195 regression tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-195" --no-coverage
Expected: All 21 tests pass
- Step 5: S197 tests
Run: cd APIs/backend-vitto-api && npx jest --testPathPattern="session-197" --no-coverage
Expected: All new tests pass
- Step 6: Lint check
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
- Step 1: Export new components from vitto-studio index.ts
- Step 2: Register new providers in module-generation.module.ts (if not already done)
- Step 3: Final commit
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:
- Agent 1: Tasks 1-3 (critical path), then Tasks 8-11
- Agent 2: Tasks 4-7 (template + composables)
- Agent 3: Tasks 12-16 (frontend pipeline)
- Agent 4: Tasks 17-20 (chat + conversational)
- Agent 5: Tasks 21-22 (integration verification)