From 288180942a1dd81b494aa195057f1e3e66967bf8 Mon Sep 17 00:00:00 2001 From: Sma1lboy <541898146chen@gmail.com> Date: Fri, 17 Jan 2025 03:47:29 -0500 Subject: [PATCH 1/4] feat: add BuildNode decorator to various handlers and refactor node data retrieval --- .../__tests__/fullstack-gen.spec.ts | 173 +++----- backend/src/build-system/context.ts | 395 +++++------------ .../handlers/backend/code-generate/index.ts | 27 +- .../backend/file-review/file-review.ts | 16 +- .../backend/requirements-document/index.ts | 16 +- .../database/requirements-document/index.ts | 9 +- .../handlers/database/schemas/schemas.ts | 14 +- .../handlers/file-manager/file-arch/index.ts | 12 +- .../file-manager/file-generate/index.ts | 7 +- .../file-manager/file-structure/index.ts | 9 +- .../handlers/frontend-code-generate/index.ts | 26 +- .../product-requirements-document/prd.ts | 5 +- .../src/build-system/handlers/project-init.ts | 2 + .../build-system/handlers/ux/datamap/index.ts | 9 +- .../handlers/ux/sitemap-document/index.ts | 10 +- .../handlers/ux/sitemap-document/uxsmd.ts | 162 +++---- .../handlers/ux/sitemap-structure/index.ts | 14 +- .../handlers/ux/sitemap-structure/sms-page.ts | 15 +- backend/src/build-system/hanlder-manager.ts | 156 ++++--- backend/src/build-system/monitor.ts | 411 ++++++------------ backend/src/build-system/types.ts | 103 ++--- .../src/build-system/utils/handler-helper.ts | 4 +- 22 files changed, 654 insertions(+), 941 deletions(-) diff --git a/backend/src/build-system/__tests__/fullstack-gen.spec.ts b/backend/src/build-system/__tests__/fullstack-gen.spec.ts index 6f18251b..2e8b6d00 100644 --- a/backend/src/build-system/__tests__/fullstack-gen.spec.ts +++ b/backend/src/build-system/__tests__/fullstack-gen.spec.ts @@ -10,138 +10,81 @@ import { Logger } from '@nestjs/common'; name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ { - id: 'step-0', - name: 'Project Initialization', - parallel: false, - nodes: [ - { - id: 'op:PROJECT::STATE:SETUP', - name: 'Project Folders Setup', - }, - ], + id: 'op:PROJECT::STATE:SETUP', + name: 'Project Folders Setup', }, { - id: 'step-1', - name: 'Initial Analysis', - parallel: false, - nodes: [ - { - id: 'op:PRD', - name: 'Project Requirements Document Node', - }, - ], + id: 'op:PRD', + name: 'Project Requirements Document Node', }, { - id: 'step-2', - name: 'UX Base Document Generation', - parallel: false, - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + id: 'op:UX:SMD', + name: 'UX Sitemap Document Node', + requires: ['op:PRD'], }, { - id: 'step-3', - name: 'Parallel UX Processing', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - requires: ['op:UX:SMD'], - }, - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX DataMap Document Node', - requires: ['op:UX:SMD'], - }, - ], + id: 'op:UX:SMS', + name: 'UX Sitemap Structure Node', + requires: ['op:UX:SMD'], }, { - id: 'step-4', - name: 'Parallel Project Structure', - parallel: true, - nodes: [ - { - id: 'op:DATABASE_REQ', - name: 'Database Requirements Node', - requires: ['op:UX:DATAMAP:DOC'], - }, - { - id: 'op:FILE:STRUCT', - name: 'File Structure Generation', - requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], - options: { - projectPart: 'frontend', - }, - }, - { - id: 'op:UX:SMS:LEVEL2', - name: 'Level 2 UX Sitemap Structure Node details', - requires: ['op:UX:SMS'], - }, - ], + id: 'op:UX:DATAMAP:DOC', + name: 'UX DataMap Document Node', + requires: ['op:UX:SMD'], }, { - id: 'step-5', - name: 'Parallel Implementation', - parallel: true, - nodes: [ - { - id: 'op:DATABASE:SCHEMAS', - name: 'Database Schemas Node', - requires: ['op:DATABASE_REQ'], - }, - { - id: 'op:FILE:ARCH', - name: 'File Arch', - requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], - }, - { - id: 'op:BACKEND:REQ', - name: 'Backend Requirements Node', - requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], - }, - ], + id: 'op:DATABASE_REQ', + name: 'Database Requirements Node', + requires: ['op:UX:DATAMAP:DOC'], }, { - id: 'step-6', - name: 'Final Code Generation', - parallel: false, - nodes: [ - { - id: 'op:BACKEND:CODE', - name: 'Backend Code Generator Node', - requires: [ - 'op:DATABASE:SCHEMAS', - 'op:UX:DATAMAP:DOC', - 'op:BACKEND:REQ', - ], - }, - { - id: 'op:FRONTEND:CODE', - name: 'Frontend Code Generator Node', - }, - ], + id: 'op:FILE:STRUCT', + name: 'File Structure Generation', + requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], + options: { + projectPart: 'frontend', + }, }, - // TODO: code reviewer { - id: 'step-7', - name: 'Backend Code Review', - parallel: false, - nodes: [ - { - id: 'op:BACKEND:FILE:REVIEW', - name: 'Backend File Review Node', - requires: ['op:BACKEND:CODE', 'op:BACKEND:REQ'], - }, + id: 'op:UX:SMS:LEVEL2', + name: 'Level 2 UX Sitemap Structure Node details', + requires: ['op:UX:SMS'], + }, + { + id: 'op:DATABASE:SCHEMAS', + name: 'Database Schemas Node', + requires: ['op:DATABASE_REQ'], + }, + { + id: 'op:FILE:ARCH', + name: 'File Arch', + requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], + }, + { + id: 'op:BACKEND:REQ', + name: 'Backend Requirements Node', + requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], + }, + { + id: 'op:BACKEND:CODE', + name: 'Backend Code Generator Node', + requires: [ + 'op:DATABASE:SCHEMAS', + 'op:UX:DATAMAP:DOC', + 'op:BACKEND:REQ', ], }, + { + id: 'op:FRONTEND:CODE', + name: 'Frontend Code Generator Node', + }, + { + id: 'op:BACKEND:FILE:REVIEW', + name: 'Backend File Review Node', + requires: ['op:BACKEND:CODE', 'op:BACKEND:REQ'], + }, ], }; diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 074ca1ee..ee4e429f 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -1,18 +1,20 @@ import { BuildExecutionState, - BuildNode, BuildResult, BuildSequence, - BuildStep, - NodeOutputMap, + BuildHandlerConstructor, + ExtractHandlerReturnType, + BuildHandler, + ExtractHandlerType, + BuildNode, } from './types'; import { Logger } from '@nestjs/common'; import { VirtualDirectory } from './virtual-dir'; import { v4 as uuidv4 } from 'uuid'; import { BuildMonitor } from './monitor'; -import { BuildHandlerManager } from './hanlder-manager'; import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; import { RetryHandler } from './retry-handler'; +import { BuildHandlerManager } from './hanlder-manager'; /** * Global data keys used throughout the build process @@ -54,7 +56,7 @@ export class BuilderContext { private globalPromises: Set> = new Set(); private logger: Logger; private globalContext: Map = new Map(); - private nodeData: Map = new Map(); + private nodeData: Map = new Map(); private handlerManager: BuildHandlerManager; private retryHandler: RetryHandler; @@ -89,297 +91,152 @@ export class BuilderContext { this.monitor.startSequenceExecution(this.sequence); try { - for (const step of this.sequence.steps) { - await this.executeStep(step); - - const incompletedNodes = step.nodes.filter( - (node) => !this.executionState.completed.has(node.id), - ); - - if (incompletedNodes.length > 0) { - this.logger.warn( - `Step ${step.id} failed to complete nodes: ${incompletedNodes - .map((n) => n.id) - .join(', ')}`, - ); - return; - } - } - - this.logger.log(`Build sequence completed: ${this.sequence.id}`); - this.logger.log('Final execution state:', this.executionState); - } finally { - this.monitor.endSequenceExecution( - this.sequence.id, - this.globalContext.get('projectUUID'), - ); - } - } + const nodes = this.sequence.nodes; + const windowSize = 20; + let windowStart = 0; - /** - * Executes a build step, handling both parallel and sequential node execution - * @param step The build step to execute - * @private - */ - private async executeStep(step: BuildStep): Promise { - this.logger.log(`Executing build step: ${step.id}`); - this.monitor.setCurrentStep(step); - this.monitor.startStepExecution( - step.id, - this.sequence.id, - step.parallel, - step.nodes.length, - ); + while (windowStart < nodes.length) { + const windowEnd = Math.min(windowStart + windowSize, nodes.length); + const windowNodes = nodes.slice(windowStart, windowEnd); - try { - if (step.parallel) { - await this.executeParallelNodes(step); - } else { - await this.executeSequentialNodes(step); - } - } finally { - this.monitor.endStepExecution(step.id, this.sequence.id); - } - } - - /** - * Executes nodes in parallel within a build step - * @param step The build step containing nodes to execute in parallel - * @private - */ - private async executeParallelNodes(step: BuildStep): Promise { - let remainingNodes = [...step.nodes]; - const concurrencyLimit = 20; - - while (remainingNodes.length > 0) { - const executableNodes = remainingNodes.filter((node) => - this.canExecute(node.id), - ); - - if (executableNodes.length > 0) { - for (let i = 0; i < executableNodes.length; i += concurrencyLimit) { - const batch = executableNodes.slice(i, i + concurrencyLimit); + const executableNodes = windowNodes.filter((node) => + this.canExecute(node), + ); - try { - batch.map(async (node) => { - if (this.executionState.completed.has(node.id)) { + if (executableNodes.length > 0) { + await Promise.all( + executableNodes.map(async (node) => { + const handlerClass = node.handler + .constructor as BuildHandlerConstructor; + if (this.executionState.completed.has(handlerClass.name)) { return; } - const currentStep = this.monitor.getCurrentStep(); this.monitor.startNodeExecution( - node.id, + handlerClass.name, this.sequence.id, - currentStep.id, ); - let res; try { - if (!this.canExecute(node.id)) { - this.logger.log( - `Waiting for dependencies of node ${node.id}: ${node.requires?.join( - ', ', - )}`, - ); - this.monitor.incrementNodeRetry( - node.id, - this.sequence.id, - currentStep.id, - ); - return; - } - - this.logger.log(`Executing node ${node.id} in parallel batch`); - res = this.executeNodeById(node.id); - this.globalPromises.add(res); + this.logger.log(`Executing node ${handlerClass.name}`); + await this.executeNode(node); this.monitor.endNodeExecution( - node.id, + handlerClass.name, this.sequence.id, - currentStep.id, true, ); } catch (error) { this.monitor.endNodeExecution( - node.id, + handlerClass.name, this.sequence.id, - currentStep.id, false, error instanceof Error ? error : new Error(String(error)), ); throw error; } - }); - - await Promise.all(this.globalPromises); - const activeModelPromises = this.model.getAllActivePromises(); - if (activeModelPromises.length > 0) { - this.logger.debug( - `Waiting for ${activeModelPromises.length} active LLM requests to complete`, - ); - await Promise.all(activeModelPromises); - } - } catch (error) { - this.logger.error( - `Error executing parallel nodes batch: ${error}`, - error instanceof Error ? error.stack : undefined, - ); - throw error; - } - } - - remainingNodes = remainingNodes.filter( - (node) => !this.executionState.completed.has(node.id), - ); - } else { - await new Promise((resolve) => setTimeout(resolve, 100)); - - const activeModelPromises = this.model.getAllActivePromises(); - if (activeModelPromises.length > 0) { - this.logger.debug( - `Waiting for ${activeModelPromises.length} active LLM requests during retry`, + }), ); - await Promise.all(activeModelPromises); } - } - } - const finalActivePromises = this.model.getAllActivePromises(); - if (finalActivePromises.length > 0) { - this.logger.debug( - `Final wait for ${finalActivePromises.length} remaining LLM requests`, - ); - await Promise.all(finalActivePromises); - } - } - - /** - * Executes nodes sequentially within a build step - * @param step The build step containing nodes to execute sequentially - * @private - */ - private async executeSequentialNodes(step: BuildStep): Promise { - for (const node of step.nodes) { - let retryCount = 0; - const maxRetries = 10; - - while ( - !this.executionState.completed.has(node.id) && - retryCount < maxRetries - ) { - await this.executeNode(node); - - if (!this.executionState.completed.has(node.id)) { - await new Promise((resolve) => setTimeout(resolve, 100)); - retryCount++; - } - } - - if (!this.executionState.completed.has(node.id)) { - this.logger.warn( - `Failed to execute node ${node.id} after ${maxRetries} attempts`, - ); + windowStart += windowSize; } - } - } - private async executeNode(node: BuildNode): Promise { - if (this.executionState.completed.has(node.id)) { - return; - } - - const currentStep = this.monitor.getCurrentStep(); - this.monitor.startNodeExecution(node.id, this.sequence.id, currentStep.id); - - try { - if (!this.canExecute(node.id)) { - this.logger.log( - `Waiting for dependencies of node ${node.id}: ${node.requires?.join( - ', ', - )}`, + const finalActivePromises = this.model.getAllActivePromises(); + if (finalActivePromises.length > 0) { + this.logger.debug( + `Final wait for ${finalActivePromises.length} remaining LLM requests`, ); - this.monitor.incrementNodeRetry( - node.id, - this.sequence.id, - currentStep.id, - ); - return; + await Promise.all(finalActivePromises); } - this.logger.log(`Executing node ${node.id}`); - await this.executeNodeById(node.id); - - this.monitor.endNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - true, - ); - } catch (error) { - this.monitor.endNodeExecution( - node.id, + this.logger.log(`Build sequence completed: ${this.sequence.id}`); + this.logger.log('Final execution state:', this.executionState); + } finally { + this.monitor.endSequenceExecution( this.sequence.id, - currentStep.id, - false, - error instanceof Error ? error : new Error(String(error)), + this.globalContext.get('projectUUID'), ); - throw error; } } /** * Checks if a node can be executed based on its dependencies - * @param nodeId The ID of the node to check + * @param node The node to check * @returns boolean indicating if the node can be executed */ - canExecute(nodeId: string): boolean { - const node = this.findNode(nodeId); - - if (!node) return false; + private canExecute(node: BuildNode): boolean { + const handlerClass = node.handler.constructor as BuildHandlerConstructor; + const handlerName = handlerClass.name; if ( - this.executionState.completed.has(nodeId) || - this.executionState.pending.has(nodeId) + this.executionState.completed.has(handlerName) || + this.executionState.pending.has(handlerName) ) { - //this.logger.debug(`Node ${nodeId} is already completed or pending.`); return false; } - return !node.requires?.some( - (dep) => !this.executionState.completed.has(dep), + if (!node.requires) return true; + + return !node.requires.some( + (reqName) => !this.executionState.completed.has(reqName), ); } - private async executeNodeById( - nodeId: string, - ): Promise> { - const node = this.findNode(nodeId); - if (!node) { - throw new Error(`Node not found: ${nodeId}`); - } - - if (!this.canExecute(nodeId)) { - throw new Error(`Dependencies not met for node: ${nodeId}`); - } + /** + * Executes a specific node + * @param node The node to execute + * @returns Promise resolving to the build result + */ + private async executeNode( + node: BuildNode, + ): Promise>> { + const handlerClass = node.handler.constructor as BuildHandlerConstructor; + const handlerName = handlerClass.name; try { - this.executionState.pending.add(nodeId); - const result = await this.invokeNodeHandler(node); - this.executionState.completed.add(nodeId); - this.logger.log(`${nodeId} is completed`); - this.executionState.pending.delete(nodeId); + this.executionState.pending.add(handlerName); + const result = await this.invokeNodeHandler>(node); + this.executionState.completed.add(handlerName); + this.logger.log(`${handlerName} is completed`); + this.executionState.pending.delete(handlerName); - this.nodeData.set(node.id, result.data); + this.setNodeData(handlerClass, result.data); return result; } catch (error) { - this.executionState.failed.add(nodeId); - this.executionState.pending.delete(nodeId); + this.executionState.failed.add(handlerName); + this.executionState.pending.delete(handlerName); throw error; } } - getExecutionState(): BuildExecutionState { - return { ...this.executionState }; + /** + * Invokes a handler for a specific node + * @param node The node to execute + * @returns Promise resolving to the build result + */ + private async invokeNodeHandler(node: BuildNode): Promise> { + const handlerClass = node.handler.constructor as BuildHandlerConstructor; + this.logger.log(`solving ${handlerClass.name}`); + + if (!this.handlerManager.validateDependencies(handlerClass)) { + throw new Error(`Dependencies not met for handler: ${handlerClass.name}`); + } + + try { + return await node.handler.run(this, node.options); + } catch (e) { + this.logger.error(`retrying ${handlerClass.name}`); + const result = await this.retryHandler.retryMethod( + e, + (node) => this.invokeNodeHandler(node), + [node], + ); + if (result === undefined) { + throw e; + } + return result as BuildResult; + } } /** @@ -406,61 +263,33 @@ export class BuilderContext { } /** - * Retrieves node-specific data - * @param nodeId The ID of the node - * @returns The data associated with the node + * Retrieves node-specific data with type inference + * @param handlerClass The handler class to get data for + * @returns The strongly typed data associated with the handler */ - getNodeData( - nodeId: NodeId, - ): NodeOutputMap[NodeId]; - getNodeData(nodeId: string): any; - getNodeData(nodeId: string) { - return this.nodeData.get(nodeId); + getNodeData( + handlerClass: T, + ): ExtractHandlerReturnType | undefined { + return this.nodeData.get(handlerClass); } /** - * Sets node-specific data - * @param nodeId The ID of the node - * @param data The data to associate with the node + * Sets node-specific data with type checking + * @param handlerClass The handler class to set data for + * @param data The strongly typed data to associate with the handler */ - setNodeData( - nodeId: NodeId, - data: any, + setNodeData( + handlerClass: T, + data: ExtractHandlerReturnType, ): void { - this.nodeData.set(nodeId, data); + this.nodeData.set(handlerClass, data); } - buildVirtualDirectory(jsonContent: string): boolean { - return this.virtualDirectory.parseJsonStructure(jsonContent); - } - - private findNode(nodeId: string): BuildNode | null { - for (const step of this.sequence.steps) { - const node = step.nodes.find((n) => n.id === nodeId); - if (node) return node; - } - return null; + getExecutionState(): BuildExecutionState { + return { ...this.executionState }; } - private async invokeNodeHandler(node: BuildNode): Promise> { - const handler = this.handlerManager.getHandler(node.id); - this.logger.log(`sovling ${node.id}`); - if (!handler) { - throw new Error(`No handler found for node: ${node.id}`); - } - try { - return await handler.run(this, node.options); - } catch (e) { - this.logger.error(`retrying ${node.id}`); - const result = await this.retryHandler.retryMethod( - e, - (node) => this.invokeNodeHandler(node), - [node], - ); - if (result === undefined) { - throw e; - } - return result as unknown as BuildResult; - } + buildVirtualDirectory(jsonContent: string): boolean { + return this.virtualDirectory.parseJsonStructure(jsonContent); } } diff --git a/backend/src/build-system/handlers/backend/code-generate/index.ts b/backend/src/build-system/handlers/backend/code-generate/index.ts index 6fd111c2..964c0970 100644 --- a/backend/src/build-system/handlers/backend/code-generate/index.ts +++ b/backend/src/build-system/handlers/backend/code-generate/index.ts @@ -11,32 +11,31 @@ import { MissingConfigurationError, ResponseParsingError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { UXSMDHandler } from '../../ux/sitemap-document'; +import { UXDatamapHandler } from '../../ux/datamap'; +import { DBSchemaHandler } from '../../database/schemas/schemas'; +import { BackendRequirementHandler } from '../requirements-document'; /** * BackendCodeHandler is responsible for generating the backend codebase * based on the provided sitemap and data mapping documents. */ -export class BackendCodeHandler implements BuildHandler { - readonly id = 'op:BACKEND:CODE'; - /** - * Executes the handler to generate backend code. - * @param context - The builder context containing configuration and utilities. - * @returns A BuildResult containing the generated code and related data. - */ +@BuildNode() +@BuildNodeRequire([UXSMDHandler, UXDatamapHandler, DBSchemaHandler]) +export class BackendCodeHandler implements BuildHandler { async run(context: BuilderContext): Promise> { - // Retrieve project name and database type from context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; const databaseType = context.getGlobalContext('databaseType') || 'Default database type'; - // Retrieve required documents - const sitemapDoc = context.getNodeData('op:UX:SMD'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); - const databaseSchemas = context.getNodeData('op:DATABASE:SCHEMAS'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + const datamapDoc = context.getNodeData(UXDatamapHandler); + const databaseSchemas = context.getNodeData(DBSchemaHandler); const backendRequirementDoc = - context.getNodeData('op:BACKEND:REQ')?.overview || ''; + context.getNodeData(BackendRequirementHandler)?.overview || ''; // Validate required data if (!sitemapDoc || !datamapDoc || !databaseSchemas) { @@ -76,7 +75,7 @@ export class BackendCodeHandler implements BuildHandler { messages: [{ content: backendCodePrompt, role: 'system' }], }, 'generateBackendCode', - this.id, + BackendCodeHandler.name, ); generatedCode = formatResponse(modelResponse); diff --git a/backend/src/build-system/handlers/backend/file-review/file-review.ts b/backend/src/build-system/handlers/backend/file-review/file-review.ts index 458996e6..c8985b3e 100644 --- a/backend/src/build-system/handlers/backend/file-review/file-review.ts +++ b/backend/src/build-system/handlers/backend/file-review/file-review.ts @@ -14,14 +14,18 @@ import { ResponseParsingError, ModelUnavailableError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { BackendRequirementHandler } from '../requirements-document'; +import { BackendCodeHandler } from '../code-generate'; /** * Responsible for reviewing all related source root files and considering modifications * such as package.json, tsconfig.json, .env, etc., in JS/TS projects. * @requires [op:BACKEND:REQ] - BackendRequirementHandler */ +@BuildNode() +@BuildNodeRequire([BackendRequirementHandler, BackendCodeHandler]) export class BackendFileReviewHandler implements BuildHandler { - readonly id = 'op:BACKEND:FILE:REVIEW'; readonly logger: Logger = new Logger('BackendFileModificationHandler'); async run(context: BuilderContext): Promise> { @@ -37,8 +41,10 @@ export class BackendFileReviewHandler implements BuildHandler { project description: ${description}, `; - const backendRequirement = context.getNodeData('op:BACKEND:REQ')?.overview; - const backendCode = [context.getNodeData('op:BACKEND:CODE')]; + const backendRequirement = context.getNodeData( + BackendRequirementHandler, + )?.overview; + const backendCode = [context.getNodeData(BackendCodeHandler)]; if (!backendRequirement) { throw new FileNotFoundError('Backend requirements are missing.'); @@ -75,7 +81,7 @@ export class BackendFileReviewHandler implements BuildHandler { messages, }, 'generateBackendCode', - this.id, + BackendFileReviewHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); @@ -114,7 +120,7 @@ export class BackendFileReviewHandler implements BuildHandler { messages: [{ content: modificationPrompt, role: 'system' }], }, 'generateBackendFile', - this.id, + BackendFileReviewHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); diff --git a/backend/src/build-system/handlers/backend/requirements-document/index.ts b/backend/src/build-system/handlers/backend/requirements-document/index.ts index 08d9644f..98dd4897 100644 --- a/backend/src/build-system/handlers/backend/requirements-document/index.ts +++ b/backend/src/build-system/handlers/backend/requirements-document/index.ts @@ -8,6 +8,10 @@ import { ModelUnavailableError, } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { DatabaseRequirementHandler } from '../../database/requirements-document'; +import { UXDatamapHandler } from '../../ux/datamap'; +import { UXSMDHandler } from '../../ux/sitemap-document'; type BackendRequirementResult = { overview: string; @@ -23,10 +27,12 @@ type BackendRequirementResult = { * BackendRequirementHandler is responsible for generating the backend requirements document. * Core Content Generation: API Endpoints, System Overview */ + +@BuildNode() +@BuildNodeRequire([DatabaseRequirementHandler, UXDatamapHandler, UXSMDHandler]) export class BackendRequirementHandler implements BuildHandler { - readonly id = 'op:BACKEND:REQ'; private readonly logger: Logger = new Logger('BackendRequirementHandler'); async run( @@ -40,9 +46,9 @@ export class BackendRequirementHandler const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const dbRequirements = context.getNodeData('op:DATABASE_REQ'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const dbRequirements = context.getNodeData(DatabaseRequirementHandler); + const datamapDoc = context.getNodeData(UXDatamapHandler); + const sitemapDoc = context.getNodeData(UXSMDHandler); if (!dbRequirements || !datamapDoc || !sitemapDoc) { this.logger.error( @@ -73,7 +79,7 @@ export class BackendRequirementHandler messages: [{ content: overviewPrompt, role: 'system' }], }, 'generateBackendOverviewPrompt', - this.id, + BackendRequirementHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model is unavailable:' + error); diff --git a/backend/src/build-system/handlers/database/requirements-document/index.ts b/backend/src/build-system/handlers/database/requirements-document/index.ts index d749b6a4..677a23ba 100644 --- a/backend/src/build-system/handlers/database/requirements-document/index.ts +++ b/backend/src/build-system/handlers/database/requirements-document/index.ts @@ -8,9 +8,12 @@ import { ModelUnavailableError, } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { UXDatamapHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([UXDatamapHandler]) export class DatabaseRequirementHandler implements BuildHandler { - readonly id = 'op:DATABASE_REQ'; private readonly logger = new Logger('DatabaseRequirementHandler'); async run(context: BuilderContext): Promise> { @@ -19,7 +22,7 @@ export class DatabaseRequirementHandler implements BuildHandler { const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + const datamapDoc = context.getNodeData(UXDatamapHandler); if (!datamapDoc) { this.logger.error('Data mapping document is missing.'); @@ -43,7 +46,7 @@ export class DatabaseRequirementHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateDatabaseRequirementPrompt', - this.id, + DatabaseRequirementHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); diff --git a/backend/src/build-system/handlers/database/schemas/schemas.ts b/backend/src/build-system/handlers/database/schemas/schemas.ts index 69830f5b..1a58c772 100644 --- a/backend/src/build-system/handlers/database/schemas/schemas.ts +++ b/backend/src/build-system/handlers/database/schemas/schemas.ts @@ -17,11 +17,13 @@ import { ModelUnavailableError, ResponseTagError, } from 'src/build-system/errors'; +import { DatabaseRequirementHandler } from '../requirements-document'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([DatabaseRequirementHandler]) export class DBSchemaHandler implements BuildHandler { - readonly id = 'op:DATABASE:SCHEMAS'; private readonly logger: Logger = new Logger('DBSchemaHandler'); - async run(context: BuilderContext): Promise { this.logger.log('Generating Database Schemas...'); @@ -30,7 +32,7 @@ export class DBSchemaHandler implements BuildHandler { context.getGlobalContext('projectName') || 'Default Project Name'; const databaseType = context.getGlobalContext('databaseType') || 'PostgreSQL'; - const dbRequirements = context.getNodeData('op:DATABASE_REQ'); + const dbRequirements = context.getNodeData(DatabaseRequirementHandler); const uuid = context.getGlobalContext('projectUUID'); // 2. Validate database type @@ -68,7 +70,7 @@ export class DBSchemaHandler implements BuildHandler { messages: [{ content: analysisPrompt, role: 'system' }], }, 'analyzeDatabaseRequirements', - this.id, + DBSchemaHandler.name, ); dbAnalysis = formatResponse(analysisResponse); } catch (error) { @@ -93,7 +95,7 @@ export class DBSchemaHandler implements BuildHandler { messages: [{ content: schemaPrompt, role: 'system' }], }, 'generateDatabaseSchema', - this.id, + DBSchemaHandler.name, ); schemaContent = formatResponse(schemaResponse); } catch (error) { @@ -117,7 +119,7 @@ export class DBSchemaHandler implements BuildHandler { messages: [{ content: validationPrompt, role: 'system' }], }, 'validateDatabaseSchema', - this.id, + DBSchemaHandler.name, ); validationResult = formatResponse(validationResponse); } catch (error) { diff --git a/backend/src/build-system/handlers/file-manager/file-arch/index.ts b/backend/src/build-system/handlers/file-manager/file-arch/index.ts index 0f4cf450..fcadede1 100644 --- a/backend/src/build-system/handlers/file-manager/file-arch/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-arch/index.ts @@ -18,9 +18,13 @@ import { buildDependencyGraph, validateAgainstVirtualDirectory, } from 'src/build-system/utils/file_generator_util'; +import { FileStructureHandler } from '../file-structure'; +import { UXDatamapHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([FileStructureHandler, UXDatamapHandler]) export class FileArchGenerateHandler implements BuildHandler { - readonly id = 'op:FILE:ARCH'; private readonly logger: Logger = new Logger('FileArchGenerateHandler'); private virtualDir: VirtualDirectory; @@ -29,8 +33,8 @@ export class FileArchGenerateHandler implements BuildHandler { this.virtualDir = context.virtualDirectory; - const fileStructure = context.getNodeData('op:FILE:STRUCT'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + const fileStructure = context.getNodeData(FileStructureHandler); + const datamapDoc = context.getNodeData(UXDatamapHandler); if (!fileStructure || !datamapDoc) { Logger.error(fileStructure); @@ -54,7 +58,7 @@ export class FileArchGenerateHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateFileArch', - this.id, + FileArchGenerateHandler.name, ); } catch (error) { this.logger.error('Model is unavailable:' + error); diff --git a/backend/src/build-system/handlers/file-manager/file-generate/index.ts b/backend/src/build-system/handlers/file-manager/file-generate/index.ts index eb804726..26a0a2a7 100644 --- a/backend/src/build-system/handlers/file-manager/file-generate/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-generate/index.ts @@ -13,15 +13,18 @@ import { FileWriteError, } from 'src/build-system/errors'; import { getProjectPath } from 'codefox-common'; +import { FileArchGenerateHandler } from '../file-arch'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([FileArchGenerateHandler]) export class FileGeneratorHandler implements BuildHandler { - readonly id = 'op:FILE:GENERATE'; private readonly logger = new Logger('FileGeneratorHandler'); private virtualDir: VirtualDirectory; async run(context: BuilderContext): Promise> { this.virtualDir = context.virtualDirectory; - const fileArchDoc = context.getNodeData('op:FILE:ARCH'); + const fileArchDoc = context.getNodeData(FileArchGenerateHandler); const uuid = context.getGlobalContext('projectUUID'); if (!fileArchDoc) { diff --git a/backend/src/build-system/handlers/file-manager/file-structure/index.ts b/backend/src/build-system/handlers/file-manager/file-structure/index.ts index 6449bcf2..ea77d8a1 100644 --- a/backend/src/build-system/handlers/file-manager/file-structure/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-structure/index.ts @@ -13,11 +13,16 @@ import { ResponseParsingError, MissingConfigurationError, } from 'src/build-system/errors'; +import { UXSMDHandler } from '../../ux/sitemap-document'; +import { UXDatamapHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; /** * FileStructureHandler is responsible for generating the project's file and folder structure * based on the provided documentation. */ +@BuildNode() +@BuildNodeRequire([UXSMDHandler, UXDatamapHandler]) export class FileStructureHandler implements BuildHandler { readonly id = 'op:FILE:STRUCT'; private readonly logger: Logger = new Logger('FileStructureHandler'); @@ -31,8 +36,8 @@ export class FileStructureHandler implements BuildHandler { // Retrieve projectName from context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + const datamapDoc = context.getNodeData(UXDatamapHandler); const projectPart = opts?.projectPart ?? 'frontend'; const framework = context.getGlobalContext('framework') ?? 'react'; diff --git a/backend/src/build-system/handlers/frontend-code-generate/index.ts b/backend/src/build-system/handlers/frontend-code-generate/index.ts index 9ce528cf..112c5a01 100644 --- a/backend/src/build-system/handlers/frontend-code-generate/index.ts +++ b/backend/src/build-system/handlers/frontend-code-generate/index.ts @@ -1,4 +1,3 @@ -// frontend-code.handler.ts import { BuildHandler, BuildResult } from 'src/build-system/types'; import { BuilderContext } from 'src/build-system/context'; import { Logger } from '@nestjs/common'; @@ -11,19 +10,28 @@ import normalizePath from 'normalize-path'; import * as path from 'path'; import { readFile } from 'fs/promises'; -// Utility functions (similar to your parseGenerateTag, removeCodeBlockFences) import { parseGenerateTag } from 'src/build-system/utils/strings'; -// The function from step #1 import { generateFrontEndCodePrompt, generateCSSPrompt } from './prompt'; +import { UXSitemapStructureHandler } from '../ux/sitemap-structure'; +import { UXDatamapHandler } from '../ux/datamap'; +import { BackendRequirementHandler } from '../backend/requirements-document'; +import { FileArchGenerateHandler } from '../file-manager/file-arch'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; /** * FrontendCodeHandler is responsible for generating the frontend codebase * based on the provided sitemap, data mapping documents, backend requirement documents, * frontendDependencyFile, frontendDependenciesContext, . */ +@BuildNode() +@BuildNodeRequire([ + UXSitemapStructureHandler, + UXDatamapHandler, + BackendRequirementHandler, + FileArchGenerateHandler, +]) export class FrontendCodeHandler implements BuildHandler { - readonly id = 'op:FRONTEND:CODE'; readonly logger: Logger = new Logger('FrontendCodeHandler'); private virtualDir: VirtualDirectory; @@ -37,10 +45,12 @@ export class FrontendCodeHandler implements BuildHandler { this.logger.log('Generating Frontend Code...'); // 1. Retrieve the necessary input from context - const sitemapStruct = context.getNodeData('op:UX:SMS'); - const uxDataMapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); - const backendRequirementDoc = context.getNodeData('op:BACKEND:REQ'); - const fileArchDoc = context.getNodeData('op:FILE:ARCH'); + const sitemapStruct = context.getNodeData(UXSitemapStructureHandler); + const uxDataMapDoc = context.getNodeData(UXDatamapHandler); + const backendRequirementDoc = context.getNodeData( + BackendRequirementHandler, + ); + const fileArchDoc = context.getNodeData(FileArchGenerateHandler); // 2. Grab any globally stored context as needed this.virtualDir = context.virtualDirectory; diff --git a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts index b562f8f2..490fd398 100644 --- a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts +++ b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts @@ -10,9 +10,10 @@ import { ModelUnavailableError, ResponseParsingError, } from 'src/build-system/errors'; +import { BuildNode } from 'src/build-system/hanlder-manager'; +@BuildNode() export class PRDHandler implements BuildHandler { - readonly id = 'op:PRD'; readonly logger: Logger = new Logger('PRDHandler'); async run(context: BuilderContext): Promise { @@ -72,7 +73,7 @@ export class PRDHandler implements BuildHandler { context, { messages, model: 'gpt-4o-mini' }, 'generatePRDFromLLM', - this.id, + PRDHandler.name, ); if (!prdContent || prdContent.trim() === '') { throw new ModelUnavailableError( diff --git a/backend/src/build-system/handlers/project-init.ts b/backend/src/build-system/handlers/project-init.ts index 0562a8a9..f51c2bfe 100644 --- a/backend/src/build-system/handlers/project-init.ts +++ b/backend/src/build-system/handlers/project-init.ts @@ -3,6 +3,8 @@ import { BuildHandler, BuildResult } from '../types'; import { Logger } from '@nestjs/common'; import * as path from 'path'; import { buildProjectPath, copyProjectTemplate } from '../utils/files'; +import { BuildNode } from '../hanlder-manager'; +@BuildNode() export class ProjectInitHandler implements BuildHandler { readonly id = 'op:PROJECT::STATE:SETUP'; private readonly logger = new Logger('ProjectInitHandler'); diff --git a/backend/src/build-system/handlers/ux/datamap/index.ts b/backend/src/build-system/handlers/ux/datamap/index.ts index 6513ebb2..a356ebc5 100644 --- a/backend/src/build-system/handlers/ux/datamap/index.ts +++ b/backend/src/build-system/handlers/ux/datamap/index.ts @@ -8,12 +8,15 @@ import { MissingConfigurationError, ModelUnavailableError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { UXSMDHandler } from '../sitemap-document'; /** * Handler for generating the UX Data Map document. */ +@BuildNode() +@BuildNodeRequire([UXSMDHandler]) export class UXDatamapHandler implements BuildHandler { - readonly id = 'op:UX:DATAMAP:DOC'; private readonly logger = new Logger('UXDatamapHandler'); async run(context: BuilderContext): Promise> { @@ -22,7 +25,7 @@ export class UXDatamapHandler implements BuildHandler { // Extract relevant data from the context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const sitemapDoc = context.getNodeData(UXSMDHandler); // Validate required data if (!projectName || typeof projectName !== 'string') { @@ -48,7 +51,7 @@ export class UXDatamapHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateUXDataMap', - this.id, + UXDatamapHandler.name, ); this.logger.log('Successfully generated UX Data Map content.'); diff --git a/backend/src/build-system/handlers/ux/sitemap-document/index.ts b/backend/src/build-system/handlers/ux/sitemap-document/index.ts index 8d78b585..46f46397 100644 --- a/backend/src/build-system/handlers/ux/sitemap-document/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-document/index.ts @@ -4,11 +4,13 @@ import { prompts } from './prompt'; import { Logger } from '@nestjs/common'; import { removeCodeBlockFences } from 'src/build-system/utils/strings'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { PRDHandler } from '../../product-manager/product-requirements-document/prd'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([PRDHandler]) export class UXSMDHandler implements BuildHandler { - readonly id = 'op:UX:SMD'; readonly logger: Logger = new Logger('UXSMDHandler'); - async run(context: BuilderContext): Promise> { this.logger.log('Generating UXSMD...'); @@ -16,7 +18,7 @@ export class UXSMDHandler implements BuildHandler { const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; const platform = context.getGlobalContext('platform') || 'Default Platform'; - const prdContent = context.getNodeData('op:PRD'); + const prdContent = context.getNodeData(PRDHandler); // Generate the prompt dynamically const prompt = prompts.generateUxsmdPrompt(projectName, platform); @@ -84,7 +86,7 @@ export class UXSMDHandler implements BuildHandler { messages: messages, }, 'generateUXSMDFromLLM', - this.id, + UXSMDHandler.name, ); this.logger.log('Received full UXSMD content from LLM server.'); diff --git a/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts b/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts index ff8ac1bb..956cdd10 100644 --- a/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts +++ b/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts @@ -1,92 +1,92 @@ -import { BuildHandler, BuildResult } from 'src/build-system/types'; -import { BuilderContext } from 'src/build-system/context'; -import { prompts } from './prompt'; -import { Logger } from '@nestjs/common'; -import { removeCodeBlockFences } from 'src/build-system/utils/strings'; -import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; -import { - MissingConfigurationError, - ModelUnavailableError, - ResponseParsingError, -} from 'src/build-system/errors'; +// import { BuildHandler, BuildResult } from 'src/build-system/types'; +// import { BuilderContext } from 'src/build-system/context'; +// import { prompts } from './prompt'; +// import { Logger } from '@nestjs/common'; +// import { removeCodeBlockFences } from 'src/build-system/utils/strings'; +// import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +// import { +// MissingConfigurationError, +// ModelUnavailableError, +// ResponseParsingError, +// } from 'src/build-system/errors'; -export class UXSMDHandler implements BuildHandler { - readonly id = 'op:UX:SMD'; - private readonly logger = new Logger('UXSMDHandler'); +// export class UXSMDHandler implements BuildHandler { +// readonly id = 'op:UX:SMD'; +// private readonly logger = new Logger('UXSMDHandler'); - async run(context: BuilderContext): Promise> { - this.logger.log('Generating UXSMD...'); +// async run(context: BuilderContext): Promise> { +// this.logger.log('Generating UXSMD...'); - // Extract project data from the context - const projectName = - context.getGlobalContext('projectName') || 'Default Project Name'; - const platform = context.getGlobalContext('platform') || 'Default Platform'; - const prdContent = context.getNodeData('op:PRD'); +// // Extract project data from the context +// const projectName = +// context.getGlobalContext('projectName') || 'Default Project Name'; +// const platform = context.getGlobalContext('platform') || 'Default Platform'; +// const prdContent = context.getNodeData('); - // Validate required data - if (!projectName || typeof projectName !== 'string') { - throw new MissingConfigurationError('Missing or invalid projectName.'); - } - if (!platform || typeof platform !== 'string') { - throw new MissingConfigurationError('Missing or invalid platform.'); - } - if (!prdContent || typeof prdContent !== 'string') { - throw new MissingConfigurationError('Missing or invalid PRD content.'); - } +// // Validate required data +// if (!projectName || typeof projectName !== 'string') { +// throw new MissingConfigurationError('Missing or invalid projectName.'); +// } +// if (!platform || typeof platform !== 'string') { +// throw new MissingConfigurationError('Missing or invalid platform.'); +// } +// if (!prdContent || typeof prdContent !== 'string') { +// throw new MissingConfigurationError('Missing or invalid PRD content.'); +// } - // Generate the prompt dynamically - const prompt = prompts.generateUxsmdrompt( - projectName, - prdContent, - platform, - ); +// // Generate the prompt dynamically +// const prompt = prompts.generateUxsmdrompt( +// projectName, +// prdContent, +// platform, +// ); - // Send the prompt to the LLM server and process the response +// // Send the prompt to the LLM server and process the response - try { - // Generate UXSMD content using the language model - const uxsmdContent = await this.generateUXSMDFromLLM(context, prompt); +// try { +// // Generate UXSMD content using the language model +// const uxsmdContent = await this.generateUXSMDFromLLM(context, prompt); - if (!uxsmdContent || uxsmdContent.trim() === '') { - this.logger.error('Generated UXSMD content is empty.'); - throw new ResponseParsingError('Generated UXSMD content is empty.'); - } +// if (!uxsmdContent || uxsmdContent.trim() === '') { +// this.logger.error('Generated UXSMD content is empty.'); +// throw new ResponseParsingError('Generated UXSMD content is empty.'); +// } - // Store the generated document in the context - context.setGlobalContext('uxsmdDocument', uxsmdContent); +// // Store the generated document in the context +// context.setGlobalContext('uxsmdDocument', uxsmdContent); - this.logger.log('Successfully generated UXSMD content.'); - return { - success: true, - data: removeCodeBlockFences(uxsmdContent), - }; - } catch (error) { - throw new ResponseParsingError( - 'Failed to generate UXSMD content:' + error, - ); - } - } +// this.logger.log('Successfully generated UXSMD content.'); +// return { +// success: true, +// data: removeCodeBlockFences(uxsmdContent), +// }; +// } catch (error) { +// throw new ResponseParsingError( +// 'Failed to generate UXSMD content:' + error, +// ); +// } +// } - private async generateUXSMDFromLLM( - context: BuilderContext, - prompt: string, - ): Promise { - try { - const uxsmdContent = await chatSyncWithClocker( - context, - { - model: 'gpt-4o-mini', - messages: [{ content: prompt, role: 'system' }], - }, - 'generateUXSMDFromLLM', - this.id, - ); - this.logger.log('Received full UXSMD content from LLM server.'); - return uxsmdContent; - } catch (error) { - throw new ModelUnavailableError( - 'Failed to generate UXSMD content:' + error, - ); - } - } -} +// private async generateUXSMDFromLLM( +// context: BuilderContext, +// prompt: string, +// ): Promise { +// try { +// const uxsmdContent = await chatSyncWithClocker( +// context, +// { +// model: 'gpt-4o-mini', +// messages: [{ content: prompt, role: 'system' }], +// }, +// 'generateUXSMDFromLLM', +// this.id, +// ); +// this.logger.log('Received full UXSMD content from LLM server.'); +// return uxsmdContent; +// } catch (error) { +// throw new ModelUnavailableError( +// 'Failed to generate UXSMD content:' + error, +// ); +// } +// } +// } diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts index e9fb04eb..be0ec0d2 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts @@ -10,10 +10,16 @@ import { ModelUnavailableError, ResponseParsingError, } from 'src/build-system/errors'; +import { UXSMDHandler } from '../sitemap-document'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; -// UXSMS: UX Sitemap Structure +/** + * UXSMS: UX Sitemap Structure + **/ + +@BuildNode() +@BuildNodeRequire([UXSMDHandler]) export class UXSitemapStructureHandler implements BuildHandler { - readonly id = 'op:UX:SMS'; private readonly logger = new Logger('UXSitemapStructureHandler'); async run(context: BuilderContext): Promise> { @@ -22,7 +28,7 @@ export class UXSitemapStructureHandler implements BuildHandler { // Extract relevant data from the context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const sitemapDoc = context.getNodeData(UXSMDHandler); // Validate required parameters if (!projectName || typeof projectName !== 'string') { @@ -85,7 +91,7 @@ export class UXSitemapStructureHandler implements BuildHandler { messages, }, 'generateUXSiteMapStructre', - this.id, + UXSitemapStructureHandler.name, ); if (!uxStructureContent || uxStructureContent.trim() === '') { diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts index 5688cd2e..fa8b03c9 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts @@ -7,11 +7,14 @@ import { MissingConfigurationError, ResponseParsingError, } from 'src/build-system/errors'; +import { BuildNode } from 'src/build-system/hanlder-manager'; +import { UXSMDHandler } from '../sitemap-document'; +import { UXSitemapStructureHandler } from '.'; +@BuildNode() export class UXSitemapStructurePagebyPageHandler implements BuildHandler { - readonly id = 'op:UX:SMS:PAGEBYPAGE'; readonly logger = new Logger('UXSitemapStructurePagebyPageHandler'); async run(context: BuilderContext): Promise> { @@ -19,8 +22,8 @@ export class UXSitemapStructurePagebyPageHandler const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMS'); - const uxStructureDoc = context.getNodeData('op:UX:SMS'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + const uxStructureDoc = context.getNodeData(UXSitemapStructureHandler); // Validate required data if (!projectName || typeof projectName !== 'string') { @@ -37,8 +40,6 @@ export class UXSitemapStructurePagebyPageHandler ); } - const normalizedUxStructureDoc = uxStructureDoc.replace(/\r\n/g, '\n'); - // Extract sections from the UX Structure Document const sections = this.extractAllPageViewSections(uxStructureDoc); const globalSections = this.extractAllGlobalCompSections(uxStructureDoc); @@ -92,7 +93,7 @@ export class UXSitemapStructurePagebyPageHandler const refinedGlobalCompSections = await batchChatSyncWithClock( context, 'generate global components', - this.id, + UXSitemapStructurePagebyPageHandler.name, requests, ); refinedSections.push(refinedGlobalCompSections); @@ -149,7 +150,7 @@ export class UXSitemapStructurePagebyPageHandler const refinedPageViewSections = await batchChatSyncWithClock( context, 'generate global components', - this.id, + UXSitemapStructurePagebyPageHandler.name, page_view_requests, ); refinedSections.push(refinedPageViewSections); diff --git a/backend/src/build-system/hanlder-manager.ts b/backend/src/build-system/hanlder-manager.ts index bec8b0eb..821dbc7d 100644 --- a/backend/src/build-system/hanlder-manager.ts +++ b/backend/src/build-system/hanlder-manager.ts @@ -1,60 +1,17 @@ -import { ProjectInitHandler } from './handlers/project-init'; -import { BuildHandler } from './types'; -import { PRDHandler } from './handlers/product-manager/product-requirements-document/prd'; -import { UXSitemapStructureHandler } from './handlers/ux/sitemap-structure'; -import { UXDatamapHandler } from './handlers/ux/datamap'; -import { UXSMDHandler } from './handlers/ux/sitemap-document'; -import { FileStructureHandler } from './handlers/file-manager/file-structure'; -import { FileArchGenerateHandler } from './handlers/file-manager/file-arch'; -import { BackendCodeHandler } from './handlers/backend/code-generate'; -import { DBSchemaHandler } from './handlers/database/schemas/schemas'; -import { DatabaseRequirementHandler } from './handlers/database/requirements-document'; -import { FileGeneratorHandler } from './handlers/file-manager/file-generate'; -import { BackendRequirementHandler } from './handlers/backend/requirements-document'; -import { BackendFileReviewHandler } from './handlers/backend/file-review/file-review'; -import { UXSitemapStructurePagebyPageHandler } from './handlers/ux/sitemap-structure/sms-page'; -import { FrontendCodeHandler } from './handlers/frontend-code-generate'; +import { BuildHandler, BuildHandlerConstructor } from './types'; /** - * Manages the registration and retrieval of build handlers in the system - * @class BuildHandlerManager - * @description Singleton class responsible for: - * - Maintaining a registry of all build handlers - * - Providing access to specific handlers by ID - * - Managing the lifecycle of built-in handlers - * - Implementing the singleton pattern for global handler management + * 构建处理器管理器 */ export class BuildHandlerManager { private static instance: BuildHandlerManager; - private handlers: Map = new Map(); + private handlers = new Map(); + private dependencies = new Map< + BuildHandlerConstructor, + BuildHandlerConstructor[] + >(); - private constructor() { - this.registerBuiltInHandlers(); - } - - private registerBuiltInHandlers() { - const builtInHandlers: BuildHandler[] = [ - new ProjectInitHandler(), - new PRDHandler(), - new UXSitemapStructureHandler(), - new UXSitemapStructurePagebyPageHandler(), - new UXDatamapHandler(), - new UXSMDHandler(), - new FileStructureHandler(), - new FileArchGenerateHandler(), - new BackendCodeHandler(), - new DBSchemaHandler(), - new DatabaseRequirementHandler(), - new FileGeneratorHandler(), - new BackendRequirementHandler(), - new BackendFileReviewHandler(), - new FrontendCodeHandler(), - ]; - - for (const handler of builtInHandlers) { - this.handlers.set(handler.id, handler); - } - } + private constructor() {} static getInstance(): BuildHandlerManager { if (!BuildHandlerManager.instance) { @@ -63,12 +20,103 @@ export class BuildHandlerManager { return BuildHandlerManager.instance; } - getHandler(nodeId: string): BuildHandler | undefined { - return this.handlers.get(nodeId); + /** + * 注册处理器 + */ + registerHandler( + handlerClass: BuildHandlerConstructor, + ): void { + if (!this.handlers.has(handlerClass)) { + this.handlers.set(handlerClass, new handlerClass()); + } + } + + /** + * 获取处理器实例 + */ + getHandler( + handlerClass: BuildHandlerConstructor, + ): T { + let handler = this.handlers.get(handlerClass); + if (!handler) { + handler = new handlerClass(); + this.handlers.set(handlerClass, handler); + } + return handler as T; + } + + /** + * 注册处理器依赖 + */ + registerDependencies( + handlerClass: BuildHandlerConstructor, + dependencies: BuildHandlerConstructor[], + ): void { + this.dependencies.set(handlerClass, dependencies); + } + + /** + * 获取处理器依赖 + */ + getDependencies( + handlerClass: BuildHandlerConstructor, + ): BuildHandlerConstructor[] { + return this.dependencies.get(handlerClass) || []; } + /** + * 验证处理器依赖 + */ + validateDependencies(handlerClass: BuildHandlerConstructor): boolean { + const dependencies = this.getDependencies(handlerClass); + return dependencies.every((dep) => this.handlers.has(dep)); + } + + /** + * 清除所有注册的处理器 + */ clear(): void { this.handlers.clear(); - this.registerBuiltInHandlers(); + this.dependencies.clear(); } } + +/** + * 处理器装饰器 + */ +export function BuildNode() { + return function (target: T): T { + const manager = BuildHandlerManager.getInstance(); + manager.registerHandler(target); + return target; + }; +} + +/** + * 依赖装饰器 + */ +export function BuildNodeRequire(dependencies: BuildHandlerConstructor[]) { + return function (target: T): T { + const manager = BuildHandlerManager.getInstance(); + manager.registerDependencies(target, dependencies); + return target; + }; +} + +// // 使用示例 +// @BuildNode() +// @NodeRequire([/* 依赖处理器 */]) +// class ExampleHandler implements BuildHandler { +// async run( +// context: BuilderContext, +// opts?: BuildOpts +// ): Promise> { +// return { +// success: true, +// data: "Example result" +// }; +// } +// } + +// // 类型提取示例 +// type ExampleOutput = ExtractHandlerType; // string diff --git a/backend/src/build-system/monitor.ts b/backend/src/build-system/monitor.ts index 01b977d2..a9837f1c 100644 --- a/backend/src/build-system/monitor.ts +++ b/backend/src/build-system/monitor.ts @@ -1,93 +1,73 @@ import { Logger } from '@nestjs/common'; -import { BuildStep, BuildSequence } from './types'; +import { BuildSequence } from './types'; import { ProjectEventLogger } from './logger'; import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; + /** - * Metrics for sequence, step, and node execution + * Node execution metrics */ -export interface BuildReport { - metadata: { - projectId: string; - sequenceId: string; - timestamp: string; - duration: number; - }; - summary: { - spendTime: string[]; - totalSteps: number; - completedSteps: number; - failedSteps: number; - totalNodes: number; - completedNodes: number; - failedNodes: number; - successRate: number; - }; - steps: Array<{ - id: string; - name: string; - duration: number; - parallel: boolean; - status: 'completed' | 'failed'; - nodesTotal: number; - nodesCompleted: number; - nodesFailed: number; - nodes: Array<{ - id: string; - name: string; - duration: number; - status: 'completed' | 'failed' | 'pending'; - retryCount: number; - error?: { - message: string; - stack?: string; - }; - }>; - }>; -} - export interface NodeMetrics { nodeId: string; startTime: number; endTime: number; duration: number; status: 'completed' | 'failed' | 'pending'; - memory?: number; - tokensUsed?: number; retryCount: number; error?: Error; } -export interface StepMetrics { - stepId: string; +/** + * Sequence execution metrics + */ +export interface SequenceMetrics { + sequenceId: string; startTime: number; endTime: number; duration: number; nodeMetrics: Map; - parallel: boolean; + nodesOrder: string[]; totalNodes: number; completedNodes: number; failedNodes: number; + successRate: number; } -export interface SequenceMetrics { - sequenceId: string; - startTime: number; - endTime: number; - duration: number; - stepMetrics: Map; - totalSteps: number; - completedSteps: number; - failedSteps: number; - totalNodes: number; - successRate: number; +/** + * Build execution report structure + */ +export interface BuildReport { + metadata: { + projectId: string; + sequenceId: string; + timestamp: string; + duration: number; + }; + summary: { + spendTime: string[]; + totalNodes: number; + completedNodes: number; + failedNodes: number; + successRate: number; + }; + nodes: Array<{ + id: string; + name: string; + duration: number; + status: 'completed' | 'failed' | 'pending'; + retryCount: number; + clock?: any[]; + error?: { + message: string; + stack?: string; + }; + }>; } export class BuildMonitor { private static instance: BuildMonitor; - private logger: Logger; // TODO: adding more logger + private logger: Logger; private sequenceMetrics: Map = new Map(); private static timeRecorders: Map = new Map(); - private static model = OpenAIModelProvider.getInstance(); private constructor() { @@ -103,11 +83,11 @@ export class BuildMonitor { public static async timeRecorder( generateDuration: number, - id: string, + name: string, step: string, input: string, output: string, - ) { + ): Promise { // eslint-disable-next-line @typescript-eslint/no-require-imports const encoder = require('gpt-3-encoder'); const inputLength = input.length; @@ -117,67 +97,46 @@ export class BuildMonitor { output: encoder.encode(output).length, generateDuration, }; - if (!this.timeRecorders.has(id)) { - this.timeRecorders.set(id, []); + if (!this.timeRecorders.has(name)) { + this.timeRecorders.set(name, []); } - this.timeRecorders.get(id)!.push(value); + this.timeRecorders.get(name)!.push(value); } // Node-level monitoring - startNodeExecution(nodeId: string, sequenceId: string, stepId: string): void { - const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); + startNodeExecution(nodeId: string, sequenceId: string): void { + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId); metrics.startTime = Date.now(); metrics.status = 'pending'; + + // Add node to execution order if not already present + const sequenceMetrics = this.sequenceMetrics.get(sequenceId); + if (sequenceMetrics && !sequenceMetrics.nodesOrder.includes(nodeId)) { + sequenceMetrics.nodesOrder.push(nodeId); + } } endNodeExecution( nodeId: string, sequenceId: string, - stepId: string, success: boolean, error?: Error, ): void { - const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId); metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; metrics.status = success ? 'completed' : 'failed'; if (error) { metrics.error = error; } - } - - incrementNodeRetry(nodeId: string, sequenceId: string, stepId: string): void { - const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); - metrics.retryCount++; - } - // Step-level monitoring - startStepExecution( - stepId: string, - sequenceId: string, - parallel: boolean, - totalNodes: number, - ): void { - const metrics = this.getOrCreateStepMetrics(stepId, sequenceId); - metrics.startTime = Date.now(); - metrics.parallel = parallel; - metrics.totalNodes = totalNodes; + // Update sequence metrics + this.updateSequenceMetrics(sequenceId); } - endStepExecution(stepId: string, sequenceId: string): void { - const metrics = this.getStepMetrics(sequenceId, stepId); - if (metrics) { - metrics.endTime = Date.now(); - metrics.duration = metrics.endTime - metrics.startTime; - - // Calculate completion statistics - metrics.completedNodes = Array.from(metrics.nodeMetrics.values()).filter( - (n) => n.status === 'completed', - ).length; - metrics.failedNodes = Array.from(metrics.nodeMetrics.values()).filter( - (n) => n.status === 'failed', - ).length; - } + incrementNodeRetry(nodeId: string, sequenceId: string): void { + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId); + metrics.retryCount++; } // Sequence-level monitoring @@ -187,14 +146,11 @@ export class BuildMonitor { startTime: Date.now(), endTime: 0, duration: 0, - stepMetrics: new Map(), - totalSteps: sequence.steps.length, - completedSteps: 0, - failedSteps: 0, - totalNodes: sequence.steps.reduce( - (sum, step) => sum + step.nodes.length, - 0, - ), + nodeMetrics: new Map(), + nodesOrder: [], + totalNodes: sequence.nodes.length, + completedNodes: 0, + failedNodes: 0, successRate: 0, }; this.sequenceMetrics.set(sequence.id, metrics); @@ -209,22 +165,12 @@ export class BuildMonitor { metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; - // Calculate final statistics - let completedNodes = 0; - let totalNodes = 0; - - metrics.stepMetrics.forEach((stepMetric) => { - completedNodes += stepMetric.completedNodes; - totalNodes += stepMetric.totalNodes; - }); - - metrics.successRate = (completedNodes / totalNodes) * 100; + this.updateSequenceMetrics(sequenceId); const report = await this.generateStructuredReport( sequenceId, projectUUID, ); - // log the event await ProjectEventLogger.getInstance().logEvent({ timestamp: report.metadata.timestamp, projectId: report.metadata.projectId, @@ -235,126 +181,74 @@ export class BuildMonitor { } } - // Utility methods - private getOrCreateNodeMetrics( - nodeId: string, - sequenceId: string, - stepId: string, - ): NodeMetrics { - const stepMetrics = this.getOrCreateStepMetrics(stepId, sequenceId); - if (!stepMetrics.nodeMetrics.has(nodeId)) { - stepMetrics.nodeMetrics.set(nodeId, { - nodeId, - startTime: 0, - endTime: 0, - duration: 0, - status: 'pending', - retryCount: 0, - }); + private updateSequenceMetrics(sequenceId: string): void { + const metrics = this.sequenceMetrics.get(sequenceId); + if (metrics) { + metrics.completedNodes = Array.from(metrics.nodeMetrics.values()).filter( + (n) => n.status === 'completed', + ).length; + metrics.failedNodes = Array.from(metrics.nodeMetrics.values()).filter( + (n) => n.status === 'failed', + ).length; + metrics.successRate = (metrics.completedNodes / metrics.totalNodes) * 100; } - return stepMetrics.nodeMetrics.get(nodeId)!; } - private getOrCreateStepMetrics( - stepId: string, + private getOrCreateNodeMetrics( + nodeId: string, sequenceId: string, - ): StepMetrics { + ): NodeMetrics { const sequenceMetrics = this.sequenceMetrics.get(sequenceId); if (!sequenceMetrics) { throw new Error(`No metrics found for sequence ${sequenceId}`); } - if (!sequenceMetrics.stepMetrics.has(stepId)) { - sequenceMetrics.stepMetrics.set(stepId, { - stepId, + if (!sequenceMetrics.nodeMetrics.has(nodeId)) { + sequenceMetrics.nodeMetrics.set(nodeId, { + nodeId, startTime: 0, endTime: 0, duration: 0, - nodeMetrics: new Map(), - parallel: false, - totalNodes: 0, - completedNodes: 0, - failedNodes: 0, + status: 'pending', + retryCount: 0, }); } - return sequenceMetrics.stepMetrics.get(stepId)!; - } - - private getStepMetrics( - sequenceId: string, - stepId: string, - ): StepMetrics | undefined { - return this.sequenceMetrics.get(sequenceId)?.stepMetrics.get(stepId); - } - - // Reporting methods - getSequenceMetrics(sequenceId: string): SequenceMetrics | undefined { - return this.sequenceMetrics.get(sequenceId); + return sequenceMetrics.nodeMetrics.get(nodeId)!; } - /** - * Return a structured report for a sequence - * @param sequenceId sequenceId - * @param projectUUID unique identifier for the project - * @returns BuildReport - */ async generateStructuredReport( sequenceId: string, projectUUID: string, ): Promise { - const metrics = this.getSequenceMetrics(sequenceId); + const metrics = this.sequenceMetrics.get(sequenceId); if (!metrics) { throw new Error(`No metrics found for sequence ${sequenceId}`); } - let totalCompletedNodes = 0; - let totalFailedNodes = 0; - - const steps = Array.from(metrics.stepMetrics.entries()).map( - ([stepId, stepMetric]) => { - const nodes = Array.from(stepMetric.nodeMetrics.entries()).map( - ([nodeId, nodeMetric]) => { - const values = BuildMonitor.timeRecorders.get(nodeId); - return { - id: nodeId, - name: nodeId, - duration: nodeMetric.duration, - status: nodeMetric.status, - retryCount: nodeMetric.retryCount, - clock: values, - error: nodeMetric.error - ? { - message: nodeMetric.error.message, - stack: nodeMetric.error.stack, - } - : undefined, - }; - }, - ); - - const completed = nodes.filter((n) => n.status === 'completed').length; - const failed = nodes.filter((n) => n.status === 'failed').length; - - totalCompletedNodes += completed; - totalFailedNodes += failed; + const nodes = metrics.nodesOrder + .map((nodeId) => { + const nodeMetric = metrics.nodeMetrics.get(nodeId); + if (!nodeMetric) return null; + const values = BuildMonitor.timeRecorders.get(nodeId); return { - id: stepId, - name: stepId, - duration: stepMetric.duration, - parallel: stepMetric.parallel, - status: (failed > 0 ? 'failed' : 'completed') as - | 'completed' - | 'failed', - nodesTotal: stepMetric.totalNodes, - nodesCompleted: completed, - nodesFailed: failed, - nodes, + id: nodeId, + name: nodeId, + duration: nodeMetric.duration, + status: nodeMetric.status, + retryCount: nodeMetric.retryCount, + clock: values, + error: nodeMetric.error + ? { + message: nodeMetric.error.message, + stack: nodeMetric.error.stack, + } + : undefined, }; - }, - ); + }) + .filter(Boolean); - const report: BuildReport = { + return { metadata: { projectId: projectUUID, sequenceId: metrics.sequenceId, @@ -363,29 +257,19 @@ export class BuildMonitor { }, summary: { spendTime: Array.from(BuildMonitor.timeRecorders.entries()).map( - ([id, time]) => `Step ${id} duration is ${time} ms`, + ([id, time]) => `Node ${id} duration is ${time} ms`, ), - totalSteps: metrics.totalSteps, - completedSteps: steps.filter((s) => s.status === 'completed').length, - failedSteps: steps.filter((s) => s.status === 'failed').length, totalNodes: metrics.totalNodes, - completedNodes: totalCompletedNodes, - failedNodes: totalFailedNodes, - successRate: (totalCompletedNodes / metrics.totalNodes) * 100, + completedNodes: metrics.completedNodes, + failedNodes: metrics.failedNodes, + successRate: metrics.successRate, }, - steps, + nodes, }; - - return report; } - /** - * Get Report for a sequence as string, using for test - * @param sequenceId sequenceId - * @returns string report - */ - public generateTextReport(sequenceId: string): string { - const metrics = this.getSequenceMetrics(sequenceId); + generateTextReport(sequenceId: string): string { + const metrics = this.sequenceMetrics.get(sequenceId); if (!metrics) { return `No metrics found for sequence ${sequenceId}`; } @@ -394,52 +278,35 @@ export class BuildMonitor { report += `====================================\n`; report += `Total Duration: ${metrics.duration}ms\n`; report += `Success Rate: ${metrics.successRate.toFixed(2)}%\n`; - report += `Total Steps: ${metrics.totalSteps}\n`; - report += `Total Nodes: ${metrics.totalNodes}\n\n`; - - metrics.stepMetrics.forEach((stepMetric, stepId) => { - report += `Step: ${stepId}\n`; - report += ` Duration: ${stepMetric.duration}ms\n`; - report += ` Parallel: ${stepMetric.parallel}\n`; - report += ` Completed Nodes: ${stepMetric.completedNodes}/${stepMetric.totalNodes}\n`; - report += ` Failed Nodes: ${stepMetric.failedNodes}\n\n`; - - stepMetric.nodeMetrics.forEach((nodeMetric, nodeId) => { - report += ` Node: ${nodeId}\n`; - report += ` Status: ${nodeMetric.status}\n`; - report += ` Duration: ${nodeMetric.duration}ms\n`; - report += ` Retries: ${nodeMetric.retryCount}\n`; - const values = BuildMonitor.timeRecorders.get(nodeId); - if (values) { - report += ` Clock:\n`; - values.forEach((value) => { - report += ` ${value.step}:\n`; - report += ` input token: ${value.input}\n`; - report += ` output token: ${value.output}\n`; - report += ` GenerationDuration: ${value.generateDuration}ms\n`; - }); - } - - if (nodeMetric.error) { - report += ` Error: ${nodeMetric.error.message}\n`; - } - report += '\n'; - }); + report += `Total Nodes: ${metrics.totalNodes}\n`; + report += `Completed/Failed: ${metrics.completedNodes}/${metrics.failedNodes}\n\n`; + + metrics.nodesOrder.forEach((nodeId) => { + const nodeMetric = metrics.nodeMetrics.get(nodeId); + if (!nodeMetric) return; + + report += `Node: ${nodeId}\n`; + report += ` Status: ${nodeMetric.status}\n`; + report += ` Duration: ${nodeMetric.duration}ms\n`; + report += ` Retries: ${nodeMetric.retryCount}\n`; + + const values = BuildMonitor.timeRecorders.get(nodeId); + if (values) { + report += ` Clock:\n`; + values.forEach((value) => { + report += ` ${value.step}:\n`; + report += ` Input Token: ${value.input}\n`; + report += ` Output Token: ${value.output}\n`; + report += ` Generation Duration: ${value.generateDuration}ms\n`; + }); + } + + if (nodeMetric.error) { + report += ` Error: ${nodeMetric.error.message}\n`; + } + report += '\n'; }); return report; } - - private currentStep: BuildStep | null = null; - - setCurrentStep(step: BuildStep): void { - this.currentStep = step; - } - - getCurrentStep(): BuildStep { - if (!this.currentStep) { - throw new Error('No current step set'); - } - return this.currentStep; - } } diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index ae1066c5..fc65d9f9 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -1,56 +1,42 @@ import { BuilderContext } from './context'; +// 基础构建接口 export interface BuildBase { - id: string; + handler: BuildHandler; name?: string; description?: string; requires?: string[]; options?: BuildOpts; } +// 构建节点配置 export interface BuildNode extends BuildBase { config?: Record; } -export interface BuildStep { - id: string; - name: string; - description?: string; - parallel?: boolean; - nodes: BuildNode[]; -} - +// 构建序列定义 export interface BuildSequence { id: string; version: string; name: string; description?: string; - //TODO: adding dependencies infos list here - //TODO: adding type for database maybe databaseType?: string; - steps: BuildStep[]; -} - -export interface BuildHandlerContext { - data: Record; - run: (nodeId: string) => Promise; + nodes: BuildNode[]; } -export interface BuildHandlerRegistry { - [key: string]: BuildHandler; -} -export interface BuildContext { - data: Record; - completedNodes: Set; - pendingNodes: Set; +// 构建选项 +export interface BuildOpts { + projectPart?: 'frontend' | 'backend'; } +// 构建结果接口 export interface BuildResult { success: boolean; data?: T; error?: Error; } +// 构建执行状态 export interface BuildExecutionState { completed: Set; pending: Set; @@ -58,55 +44,42 @@ export interface BuildExecutionState { waiting: Set; } -export interface BuildOpts { - projectPart?: 'frontend' | 'backend'; +// 构建上下文 +export interface BuildContext { + data: Record; + completedNodes: Set; + pendingNodes: Set; } -export interface BuildHandler { - // Unique identifier for the handler - id: string; - /** - * - * @param context the context object for the build - * @param model model provider for the build - * @param args the request arguments - */ +// 构建处理器接口 +export interface BuildHandler { run(context: BuilderContext, opts?: BuildOpts): Promise>; } +// 处理器构造函数类型 +export interface BuildHandlerConstructor { + new (): BuildHandler; +} + +// 特定输出类型(仅保留实际需要的具体类型定义) export interface FileStructOutput { - /** - * Tree File Structure: - * src: - * - components: - */ fileStructure: string; - /** - * Example JSON file structure: - * - */ jsonFileStructure: string; } -export interface NodeOutputMap { - 'op:DATABASE_REQ': string; - 'op:PRD': string; - 'op:UX:SMD': string; - 'op:UX:SMS': string; - 'op:UX:SMS:LEVEL2': string; - 'op:UX:DATAMAP:DOC': string; - 'op:FILE:STRUCT': FileStructOutput; - 'op:FILE:ARCH': string; - 'op:FILE:GENERATE': string; - 'op:BACKEND:CODE': string; - 'op:BACKEND:REQ': { - overview: string; - implementation: string; - config: { - language: string; - framework: string; - packages: Record; - }; + +export interface BackendRequirementOutput { + overview: string; + implementation: string; + config: { + language: string; + framework: string; + packages: Record; }; - 'op:DATABASE:SCHEMAS': string; - 'op:BACKEND:FILE:REVIEW': string; } + +// 工具类型:从处理器中提取输出类型 +export type ExtractHandlerType = T extends BuildHandler ? U : never; + +// 工具类型:从处理器构造函数中提取输出类型 +export type ExtractHandlerReturnType BuildHandler> = + ExtractHandlerType>; diff --git a/backend/src/build-system/utils/handler-helper.ts b/backend/src/build-system/utils/handler-helper.ts index f6313b16..99587d80 100644 --- a/backend/src/build-system/utils/handler-helper.ts +++ b/backend/src/build-system/utils/handler-helper.ts @@ -6,7 +6,7 @@ export async function chatSyncWithClocker( context: BuilderContext, input: ChatInput, step: string, - id: string, + name: string, ): Promise { const startTime = new Date(); const modelResponse = await context.model.chatSync(input); @@ -14,7 +14,7 @@ export async function chatSyncWithClocker( const duration = endTime.getTime() - startTime.getTime(); const inputContent = input.messages.map((m) => m.content).join(''); - BuildMonitor.timeRecorder(duration, id, step, inputContent, modelResponse); + BuildMonitor.timeRecorder(duration, name, step, inputContent, modelResponse); return modelResponse; } From d1bfefc86a75aa200b590dabb310f6b085d723c5 Mon Sep 17 00:00:00 2001 From: Sma1lboy <541898146chen@gmail.com> Date: Fri, 17 Jan 2025 03:48:06 -0500 Subject: [PATCH 2/4] refactor: update model names and improve comments in build system handlers --- .../__tests__/test.sms-lvl2.spec.ts | 83 +++------ backend/src/build-system/__tests__/utils.ts | 141 +++++++------- backend/src/build-system/context.ts | 172 ++++++++++-------- .../handlers/ux/sitemap-structure/index.ts | 2 +- .../handlers/ux/sitemap-structure/sms-page.ts | 2 +- backend/src/build-system/monitor.ts | 14 +- backend/src/build-system/types.ts | 56 ++++-- 7 files changed, 237 insertions(+), 233 deletions(-) diff --git a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts index 79bec7d1..10cac481 100644 --- a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts +++ b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts @@ -1,7 +1,14 @@ +import { isIntegrationTest } from 'src/common/utils'; +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { ProjectInitHandler } from '../handlers/project-init'; +import { UXDatamapHandler } from '../handlers/ux/datamap'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { UXSitemapStructureHandler } from '../handlers/ux/sitemap-structure'; +import { UXSitemapStructurePagebyPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; import { BuildSequence } from '../types'; import { executeBuildSequence } from './utils'; -describe('Build Sequence Test', () => { +(isIntegrationTest ? describe : describe.skip)('Build Sequence Test', () => { it('should execute build sequence successfully', async () => { const sequence: BuildSequence = { id: 'test-backend-sequence', @@ -9,69 +16,34 @@ describe('Build Sequence Test', () => { name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ { - id: 'step-0', - name: 'Project Initialization', - parallel: false, - nodes: [ - { - id: 'op:PROJECT::STATE:SETUP', - name: 'Project Folders Setup', - }, - ], + handler: ProjectInitHandler, + name: 'Project Folders Setup', + description: 'Create project folders', }, + { - id: 'step-1', - name: 'Initial Analysis', - parallel: false, - nodes: [ - { - id: 'op:PRD', - name: 'Project Requirements Document Node', - }, - ], + handler: PRDHandler, + name: 'Project Requirements Document Node', }, + + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, + { - id: 'step-2', - name: 'UX Base Document Generation', - parallel: false, - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + handler: UXSitemapStructureHandler, + name: 'UX Sitemap Structure Node', }, { - id: 'step-3', - name: 'Parallel UX Processing', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - requires: ['op:UX:SMD'], - }, - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX DataMap Document Node', - requires: ['op:UX:SMD'], - }, - ], + handler: UXDatamapHandler, + name: 'UX DataMap Document Node', }, { - id: 'step-4', - name: 'Parallel Project Structure', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS:PAGEBYPAGE', - name: 'Level 2 UX Sitemap Structure Node details', - requires: ['op:UX:SMS'], - }, - ], + handler: UXSitemapStructurePagebyPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', }, ], }; @@ -79,6 +51,5 @@ describe('Build Sequence Test', () => { const result = await executeBuildSequence('fullstack-code-gen', sequence); expect(result.success).toBe(true); expect(result.metrics).toBeDefined(); - console.log(`Logs saved to: ${result.logFolderPath}`); }, 300000); }); diff --git a/backend/src/build-system/__tests__/utils.ts b/backend/src/build-system/__tests__/utils.ts index 0aacee8a..6bb24ce9 100644 --- a/backend/src/build-system/__tests__/utils.ts +++ b/backend/src/build-system/__tests__/utils.ts @@ -1,56 +1,13 @@ import { Logger } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; -import { BuildSequence } from '../types'; +import { BuildSequence, BuildHandlerConstructor } from '../types'; import { BuilderContext } from '../context'; import { BuildMonitor } from '../monitor'; -/** - * Utility function to write content to a file in a clean, formatted manner. - * @param handlerName - The name of the handler. - * @param data - The data to be written to the file. - */ -export const writeToFile = ( - rootPath: string, - handlerName: string, - data: string | object, -): void => { - try { - // Sanitize handler name to prevent illegal file names - const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_'); - const filePath = path.join(rootPath, `${sanitizedHandlerName}.md`); - - // Generate clean and readable content - const formattedContent = formatContent(data); - - // Write the formatted content to the file - fs.writeFileSync(filePath, formattedContent, 'utf8'); - Logger.log(`Successfully wrote data for ${handlerName} to ${filePath}`); - } catch (error) { - Logger.error(`Failed to write data for ${handlerName}:`, error); - throw error; - } -}; /** - * Formats the content for writing to the file. - * @param data - The content to format (either a string or an object). - * @returns A formatted string. + * Format object to markdown structure */ -export const formatContent = (data: string | object): string => { - if (typeof data === 'string') { - // Remove unnecessary escape characters and normalize newlines - return data - .replace(/\\n/g, '\n') // Handle escaped newlines - .replace(/\\t/g, '\t'); // Handle escaped tabs - } else if (typeof data === 'object') { - // Pretty-print JSON objects with 2-space indentation - return JSON.stringify(data, null, 2); - } else { - // Convert other types to strings - return String(data); - } -}; - export function objectToMarkdown(obj: any, depth = 1): string { if (!obj || typeof obj !== 'object') { return String(obj); @@ -60,9 +17,7 @@ export function objectToMarkdown(obj: any, depth = 1): string { const prefix = '#'.repeat(depth); for (const [key, value] of Object.entries(obj)) { - if (value === null || value === undefined) { - continue; - } + if (value === null || value === undefined) continue; markdown += `${prefix} ${key}\n`; if (typeof value === 'object' && !Array.isArray(value)) { @@ -86,6 +41,33 @@ export function objectToMarkdown(obj: any, depth = 1): string { return markdown; } +/** + * Write content to file + */ +export const writeToFile = ( + rootPath: string, + handlerName: string, + data: string | object, +): void => { + try { + const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_'); + const filePath = path.join(rootPath, `${sanitizedHandlerName}.md`); + const formattedContent = + typeof data === 'object' + ? JSON.stringify(data, null, 2) + : String(data).replace(/\\n/g, '\n').replace(/\\t/g, '\t'); + + fs.writeFileSync(filePath, formattedContent, 'utf8'); + Logger.log(`Successfully wrote data for ${handlerName} to ${filePath}`); + } catch (error) { + Logger.error(`Failed to write data for ${handlerName}:`, error); + throw error; + } +}; + +/** + * Test result interface + */ interface TestResult { success: boolean; logFolderPath: string; @@ -93,12 +75,15 @@ interface TestResult { metrics?: any; } +/** + * Execute build sequence and record results + */ export async function executeBuildSequence( name: string, sequence: BuildSequence, ): Promise { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const logFolderPath = `./logs/${name.toLocaleLowerCase().replaceAll(' ', '-')}-${timestamp}`; + const logFolderPath = `./logs/${name.toLowerCase().replaceAll(' ', '-')}-${timestamp}`; fs.mkdirSync(logFolderPath, { recursive: true }); const context = new BuilderContext(sequence, 'test-env'); @@ -121,10 +106,9 @@ export async function executeBuildSequence( const metricsJson = { totalDuration: `${sequenceMetrics.duration}ms`, successRate: `${sequenceMetrics.successRate.toFixed(2)}%`, - totalSteps: sequenceMetrics.totalSteps, - completedSteps: sequenceMetrics.completedSteps, - failedSteps: sequenceMetrics.failedSteps, totalNodes: sequenceMetrics.totalNodes, + completedNodes: sequenceMetrics.completedNodes, + failedNodes: sequenceMetrics.failedNodes, startTime: new Date(sequenceMetrics.startTime).toISOString(), endTime: new Date(sequenceMetrics.endTime).toISOString(), }; @@ -139,31 +123,30 @@ export async function executeBuildSequence( console.table(metricsJson); } - for (const step of sequence.steps) { - const stepMetrics = sequenceMetrics?.stepMetrics.get(step.id); - for (const node of step.nodes) { - const resultData = await context.getNodeData(node.id); - const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id); - - if (resultData) { - const content = - typeof resultData === 'object' - ? objectToMarkdown(resultData) - : resultData; - writeToFile(logFolderPath, `${node.name}`, content); - } else { - Logger.error( - `Error: Handler ${node.name} failed to produce result data`, - ); - writeToFile( - logFolderPath, - `${node.name}-error`, - objectToMarkdown({ - error: 'No result data', - metrics: nodeMetrics, - }), - ); - } + // Log node results + for (const node of sequence.nodes) { + const handlerClass = node.handler as BuildHandlerConstructor; + const resultData = context.getNodeData(handlerClass); + const nodeMetrics = sequenceMetrics?.nodeMetrics.get(handlerClass.name); + + if (resultData) { + const content = + typeof resultData === 'object' + ? objectToMarkdown(resultData) + : resultData; + writeToFile(logFolderPath, node.name || handlerClass.name, content); + } else { + Logger.error( + `Error: Handler ${node.name || handlerClass.name} failed to produce result data`, + ); + writeToFile( + logFolderPath, + `${node.name || handlerClass.name}-error`, + objectToMarkdown({ + error: 'No result data', + metrics: nodeMetrics, + }), + ); } } @@ -173,8 +156,8 @@ export async function executeBuildSequence( sequenceName: sequence.name, totalExecutionTime: `${sequenceMetrics?.duration}ms`, successRate: `${sequenceMetrics?.successRate.toFixed(2)}%`, - nodesExecuted: sequenceMetrics?.totalNodes, - completedNodes: sequenceMetrics?.stepMetrics.size, + totalNodes: sequenceMetrics?.totalNodes, + completedNodes: sequenceMetrics?.completedNodes, logFolder: logFolderPath, }; diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index ee4e429f..2a370915 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -86,87 +86,13 @@ export class BuilderContext { ); } - async execute(): Promise { - this.logger.log(`Starting build sequence: ${this.sequence.id}`); - this.monitor.startSequenceExecution(this.sequence); - - try { - const nodes = this.sequence.nodes; - const windowSize = 20; - let windowStart = 0; - - while (windowStart < nodes.length) { - const windowEnd = Math.min(windowStart + windowSize, nodes.length); - const windowNodes = nodes.slice(windowStart, windowEnd); - - const executableNodes = windowNodes.filter((node) => - this.canExecute(node), - ); - - if (executableNodes.length > 0) { - await Promise.all( - executableNodes.map(async (node) => { - const handlerClass = node.handler - .constructor as BuildHandlerConstructor; - if (this.executionState.completed.has(handlerClass.name)) { - return; - } - - this.monitor.startNodeExecution( - handlerClass.name, - this.sequence.id, - ); - - try { - this.logger.log(`Executing node ${handlerClass.name}`); - await this.executeNode(node); - - this.monitor.endNodeExecution( - handlerClass.name, - this.sequence.id, - true, - ); - } catch (error) { - this.monitor.endNodeExecution( - handlerClass.name, - this.sequence.id, - false, - error instanceof Error ? error : new Error(String(error)), - ); - throw error; - } - }), - ); - } - - windowStart += windowSize; - } - - const finalActivePromises = this.model.getAllActivePromises(); - if (finalActivePromises.length > 0) { - this.logger.debug( - `Final wait for ${finalActivePromises.length} remaining LLM requests`, - ); - await Promise.all(finalActivePromises); - } - - this.logger.log(`Build sequence completed: ${this.sequence.id}`); - this.logger.log('Final execution state:', this.executionState); - } finally { - this.monitor.endSequenceExecution( - this.sequence.id, - this.globalContext.get('projectUUID'), - ); - } - } - /** * Checks if a node can be executed based on its dependencies * @param node The node to check * @returns boolean indicating if the node can be executed */ private canExecute(node: BuildNode): boolean { - const handlerClass = node.handler.constructor as BuildHandlerConstructor; + const handlerClass = node.handler; const handlerName = handlerClass.name; if ( @@ -191,9 +117,13 @@ export class BuilderContext { private async executeNode( node: BuildNode, ): Promise>> { - const handlerClass = node.handler.constructor as BuildHandlerConstructor; + const handlerClass = node.handler; // 直接使用,因为它已经是 BuildHandlerConstructor const handlerName = handlerClass.name; + if (!this.canExecute(node)) { + throw new Error(`Dependencies not met for node: ${handlerName}`); + } + try { this.executionState.pending.add(handlerName); const result = await this.invokeNodeHandler>(node); @@ -209,14 +139,13 @@ export class BuilderContext { throw error; } } - /** * Invokes a handler for a specific node * @param node The node to execute * @returns Promise resolving to the build result */ private async invokeNodeHandler(node: BuildNode): Promise> { - const handlerClass = node.handler.constructor as BuildHandlerConstructor; + const handlerClass = node.handler; this.logger.log(`solving ${handlerClass.name}`); if (!this.handlerManager.validateDependencies(handlerClass)) { @@ -224,7 +153,8 @@ export class BuilderContext { } try { - return await node.handler.run(this, node.options); + const handler = this.handlerManager.getHandler(handlerClass); + return await handler.run(this); } catch (e) { this.logger.error(`retrying ${handlerClass.name}`); const result = await this.retryHandler.retryMethod( @@ -292,4 +222,88 @@ export class BuilderContext { buildVirtualDirectory(jsonContent: string): boolean { return this.virtualDirectory.parseJsonStructure(jsonContent); } + + async execute(): Promise { + this.logger.log(`Starting build sequence: ${this.sequence.id}`); + this.monitor.startSequenceExecution(this.sequence); + + try { + const nodes = this.sequence.nodes; + let currentIndex = 0; + + while (currentIndex < nodes.length) { + const currentNode = nodes[currentIndex]; + const handlerClass = currentNode.handler; + const handlerName = handlerClass.name; + + // Check if node is already completed + if (this.executionState.completed.has(handlerName)) { + currentIndex++; + continue; + } + + // Check dependencies + if (!this.checkDependencies(handlerClass)) { + const dependencies = + this.handlerManager.getDependencies(handlerClass); + this.logger.debug( + `Dependencies not met for ${handlerName}: ${dependencies.map((d) => d.name).join(', ')}. Waiting for previous nodes to complete.`, + ); + // Since nodes are ordered by dependency, yield and wait for previous nodes + await new Promise((resolve) => setTimeout(resolve, 1000)); + // Don't increment currentIndex, stay at current node + continue; + } + + // Dependencies satisfied, execute the node + this.monitor.startNodeExecution(handlerName, this.sequence.id); + + try { + this.logger.log(`Executing node ${handlerName}`); + await this.executeNode(currentNode); + + this.monitor.endNodeExecution(handlerName, this.sequence.id, true); + + // Only move to next node after successful execution + currentIndex++; + } catch (error) { + this.monitor.endNodeExecution( + handlerName, + this.sequence.id, + false, + error instanceof Error ? error : new Error(String(error)), + ); + throw error; + } + } + + const finalActivePromises = this.model.getAllActivePromises(); + if (finalActivePromises.length > 0) { + this.logger.debug( + `Final wait for ${finalActivePromises.length} remaining LLM requests`, + ); + await Promise.all(finalActivePromises); + } + + this.logger.log(`Build sequence completed: ${this.sequence.id}`); + this.logger.log('Final execution state:', this.executionState); + } finally { + this.monitor.endSequenceExecution( + this.sequence.id, + this.globalContext.get('projectUUID'), + ); + } + } + + /** + * Checks if all dependencies for a handler are completed + * @param handlerClass The handler class to check dependencies for + * @returns boolean indicating if all dependencies are satisfied + */ + private checkDependencies(handlerClass: BuildHandlerConstructor): boolean { + const dependencies = this.handlerManager.getDependencies(handlerClass); + return dependencies.every((dep) => + this.executionState.completed.has(dep.name), + ); + } } diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts index be0ec0d2..447473ab 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts @@ -87,7 +87,7 @@ export class UXSitemapStructureHandler implements BuildHandler { const uxStructureContent = await chatSyncWithClocker( context, { - model: 'gpt-4o', + model: 'gpt-4o-mini', messages, }, 'generateUXSiteMapStructre', diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts index fa8b03c9..128ad0b8 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts @@ -149,7 +149,7 @@ export class UXSitemapStructurePagebyPageHandler const refinedPageViewSections = await batchChatSyncWithClock( context, - 'generate global components', + 'generate page by page details', UXSitemapStructurePagebyPageHandler.name, page_view_requests, ); diff --git a/backend/src/build-system/monitor.ts b/backend/src/build-system/monitor.ts index a9837f1c..48edd181 100644 --- a/backend/src/build-system/monitor.ts +++ b/backend/src/build-system/monitor.ts @@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common'; import { BuildSequence } from './types'; import { ProjectEventLogger } from './logger'; import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; - +import * as gpt3Encoder from 'gpt-3-encoder'; /** * Node execution metrics */ @@ -88,13 +88,11 @@ export class BuildMonitor { input: string, output: string, ): Promise { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const encoder = require('gpt-3-encoder'); const inputLength = input.length; const value = { step, input: inputLength, - output: encoder.encode(output).length, + output: gpt3Encoder.encode(output).length, generateDuration, }; if (!this.timeRecorders.has(name)) { @@ -309,4 +307,12 @@ export class BuildMonitor { return report; } + /** + * Get metrics for a specific sequence + * @param sequenceId The ID of the sequence + * @returns The metrics for the sequence, or undefined if not found + */ + getSequenceMetrics(sequenceId: string): SequenceMetrics | undefined { + return this.sequenceMetrics.get(sequenceId); + } } diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index fc65d9f9..4a9ef3bf 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -1,20 +1,29 @@ import { BuilderContext } from './context'; -// 基础构建接口 +/** + * Base interface for build configuration + */ export interface BuildBase { - handler: BuildHandler; + /** + * Class reference that implements BuildHandler + */ + handler: new () => BuildHandler; name?: string; description?: string; requires?: string[]; options?: BuildOpts; } -// 构建节点配置 +/** + * Build node configuration + */ export interface BuildNode extends BuildBase { config?: Record; } -// 构建序列定义 +/** + * Build sequence definition + */ export interface BuildSequence { id: string; version: string; @@ -24,19 +33,25 @@ export interface BuildSequence { nodes: BuildNode[]; } -// 构建选项 +/** + * Build options + */ export interface BuildOpts { projectPart?: 'frontend' | 'backend'; } -// 构建结果接口 +/** + * Build result interface + */ export interface BuildResult { success: boolean; data?: T; error?: Error; } -// 构建执行状态 +/** + * Build execution state + */ export interface BuildExecutionState { completed: Set; pending: Set; @@ -44,29 +59,40 @@ export interface BuildExecutionState { waiting: Set; } -// 构建上下文 +/** + * Build context + */ export interface BuildContext { data: Record; completedNodes: Set; pendingNodes: Set; } -// 构建处理器接口 +/** + * Build handler interface + */ export interface BuildHandler { run(context: BuilderContext, opts?: BuildOpts): Promise>; } -// 处理器构造函数类型 +/** + * Build handler constructor type + */ export interface BuildHandlerConstructor { new (): BuildHandler; } -// 特定输出类型(仅保留实际需要的具体类型定义) +/** + * File structure output type + */ export interface FileStructOutput { fileStructure: string; jsonFileStructure: string; } +/** + * Backend requirement output type + */ export interface BackendRequirementOutput { overview: string; implementation: string; @@ -77,9 +103,13 @@ export interface BackendRequirementOutput { }; } -// 工具类型:从处理器中提取输出类型 +/** + * Extract handler type utility + */ export type ExtractHandlerType = T extends BuildHandler ? U : never; -// 工具类型:从处理器构造函数中提取输出类型 +/** + * Extract handler return type utility + */ export type ExtractHandlerReturnType BuildHandler> = ExtractHandlerType>; From 642bcecc008b96093519eb6fc82c9ecaa2ffc2bc Mon Sep 17 00:00:00 2001 From: Sma1lboy <541898146chen@gmail.com> Date: Fri, 17 Jan 2025 22:25:41 -0500 Subject: [PATCH 3/4] feat: enhance build handler system with dependency management and logging --- .../__tests__/fullstack-gen.spec.ts | 82 ++-- .../test.backend-code-generator.spec.ts | 131 ++----- backend/src/build-system/context.ts | 371 ++++++++++++------ .../product-requirements-document/prd.ts | 4 +- .../handlers/ux/sitemap-document/index.ts | 1 + .../handlers/ux/sitemap-structure/sms-page.ts | 3 +- backend/src/build-system/hanlder-manager.ts | 75 ++-- backend/src/build-system/types.ts | 3 +- .../utils/__test__/build-utils.spec.ts | 169 ++++++++ backend/src/build-system/utils/build-utils.ts | 86 ++++ 10 files changed, 621 insertions(+), 304 deletions(-) create mode 100644 backend/src/build-system/utils/__test__/build-utils.spec.ts create mode 100644 backend/src/build-system/utils/build-utils.ts diff --git a/backend/src/build-system/__tests__/fullstack-gen.spec.ts b/backend/src/build-system/__tests__/fullstack-gen.spec.ts index 2e8b6d00..d1bfe665 100644 --- a/backend/src/build-system/__tests__/fullstack-gen.spec.ts +++ b/backend/src/build-system/__tests__/fullstack-gen.spec.ts @@ -2,6 +2,20 @@ import { isIntegrationTest } from 'src/common/utils'; import { BuildSequence } from '../types'; import { executeBuildSequence } from './utils'; import { Logger } from '@nestjs/common'; +import { ProjectInitHandler } from '../handlers/project-init'; +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { UXSitemapStructureHandler } from '../handlers/ux/sitemap-structure'; +import { UXDatamapHandler } from '../handlers/ux/datamap'; +import { DatabaseRequirementHandler } from '../handlers/database/requirements-document'; +import { FileStructureHandler } from '../handlers/file-manager/file-structure'; +import { UXSitemapStructurePagebyPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; +import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; +import { FileArchGenerateHandler } from '../handlers/file-manager/file-arch'; +import { BackendRequirementHandler } from '../handlers/backend/requirements-document'; +import { BackendCodeHandler } from '../handlers/backend/code-generate'; +import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-review'; + (isIntegrationTest ? describe : describe.skip)('Build Sequence Test', () => { it('should execute build sequence successfully', async () => { const sequence: BuildSequence = { @@ -12,85 +26,87 @@ import { Logger } from '@nestjs/common'; databaseType: 'SQLite', nodes: [ { - id: 'op:PROJECT::STATE:SETUP', + handler: ProjectInitHandler, name: 'Project Folders Setup', }, { - id: 'op:PRD', + handler: PRDHandler, name: 'Project Requirements Document Node', }, { - id: 'op:UX:SMD', + handler: UXSMDHandler, name: 'UX Sitemap Document Node', - requires: ['op:PRD'], }, { - id: 'op:UX:SMS', + handler: UXSitemapStructureHandler, name: 'UX Sitemap Structure Node', - requires: ['op:UX:SMD'], + // requires: ['op:UX:SMD'], }, { - id: 'op:UX:DATAMAP:DOC', + handler: UXDatamapHandler, name: 'UX DataMap Document Node', - requires: ['op:UX:SMD'], + // requires: ['op:UX:SMD'], }, { - id: 'op:DATABASE_REQ', + handler: DatabaseRequirementHandler, name: 'Database Requirements Node', - requires: ['op:UX:DATAMAP:DOC'], + // requires: ['op:UX:DATAMAP:DOC'], }, { - id: 'op:FILE:STRUCT', + handler: FileStructureHandler, name: 'File Structure Generation', - requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], + // requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], options: { projectPart: 'frontend', }, }, { - id: 'op:UX:SMS:LEVEL2', + handler: UXSitemapStructurePagebyPageHandler, name: 'Level 2 UX Sitemap Structure Node details', - requires: ['op:UX:SMS'], + // requires: ['op:UX:SMS'], }, { - id: 'op:DATABASE:SCHEMAS', + handler: DBSchemaHandler, name: 'Database Schemas Node', - requires: ['op:DATABASE_REQ'], + // requires: ['op:DATABASE_REQ'], }, { - id: 'op:FILE:ARCH', + handler: FileArchGenerateHandler, name: 'File Arch', - requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], + // requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], }, { - id: 'op:BACKEND:REQ', + handler: BackendRequirementHandler, name: 'Backend Requirements Node', - requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], + // requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], }, { - id: 'op:BACKEND:CODE', + handler: BackendCodeHandler, name: 'Backend Code Generator Node', - requires: [ - 'op:DATABASE:SCHEMAS', - 'op:UX:DATAMAP:DOC', - 'op:BACKEND:REQ', - ], - }, - { - id: 'op:FRONTEND:CODE', - name: 'Frontend Code Generator Node', + // requires: [ + // 'op:DATABASE:SCHEMAS', + // 'op:UX:DATAMAP:DOC', + // 'op:BACKEND:REQ', + // ], }, + // { + // handler:FrontendCodeHandler, + // id: 'op:FRONTEND:CODE', + // name: 'Frontend Code Generator Node', + // }, { - id: 'op:BACKEND:FILE:REVIEW', + handler: BackendFileReviewHandler, name: 'Backend File Review Node', - requires: ['op:BACKEND:CODE', 'op:BACKEND:REQ'], + // requires: ['op:BACKEND:CODE', 'op:BACKEND:REQ'], }, ], }; const result = await executeBuildSequence('fullstack-code-gen', sequence); + + // Assertion: ensure the build sequence runs successfully expect(result.success).toBe(true); expect(result.metrics).toBeDefined(); Logger.log(`Logs saved to: ${result.logFolderPath}`); - }, 300000); + }, 300000); // Set timeout to 5 minutes }); diff --git a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts index a3765b3d..1b3c5d42 100644 --- a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts +++ b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts @@ -1,12 +1,13 @@ -/* eslint-disable no-console */ -import { BuilderContext } from 'src/build-system/context'; import { BuildSequence } from '../types'; import * as fs from 'fs'; -import * as path from 'path'; -import { writeToFile } from './utils'; +import { executeBuildSequence } from './utils'; import { isIntegrationTest } from 'src/common/utils'; -import { Logger } from '@nestjs/common'; - +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { DatabaseRequirementHandler } from '../handlers/database/requirements-document'; +import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; +import { BackendCodeHandler } from '../handlers/backend/code-generate'; +import { ProjectInitHandler } from '../handlers/project-init'; (isIntegrationTest ? describe : describe.skip)( 'Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGenerator', () => { @@ -25,112 +26,50 @@ import { Logger } from '@nestjs/common'; name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + }, { - id: 'step-1', - name: 'Generate PRD', - nodes: [ - { - id: 'op:PRD', - name: 'PRD Generation Node', - }, - ], + handler: PRDHandler, + name: 'PRD Generation Node', }, + { - id: 'step-2', - name: 'Generate UX Sitemap Document', - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + // requires: ['op:PRD'], }, + { - id: 'step-3', - name: 'Generate UX Data Map Document', - nodes: [ - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX Data Map Document Node', - requires: ['op:UX:SMD'], - }, - ], + handler: UXSMDHandler, + name: 'UX Data Map Document Node', + // requires: ['op:UX:SMD'], }, + { - id: 'step-4', - name: 'Generate Database Requirements', - nodes: [ - { - id: 'op:DATABASE_REQ', - name: 'Database Requirements Node', - requires: ['op:UX:DATAMAP:DOC'], - }, - ], + handler: DatabaseRequirementHandler, + name: 'Database Requirements Node', + // requires: ['op:UX:DATAMAP:DOC'], }, + { - id: 'step-5', - name: 'Generate Database Schemas', - nodes: [ - { - id: 'op:DATABASE:SCHEMAS', - name: 'Database Schemas Node', - requires: ['op:DATABASE_REQ'], - }, - ], + handler: DBSchemaHandler, + name: 'Database Schemas Node', + // requires: ['op:DATABASE_REQ'], }, + { - id: 'step-6', - name: 'Generate Backend Code', - nodes: [ - { - id: 'op:BACKEND:CODE', - name: 'Backend Code Generator Node', - requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], - }, - ], + handler: BackendCodeHandler, + name: 'Backend Code Generator Node', + // requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], }, ], }; // Initialize the BuilderContext with the defined sequence and environment - const context = new BuilderContext(sequence, 'test-env'); - - try { - // Execute the build sequence - await context.execute(); - - // Iterate through each step and node to retrieve and log results - for (const step of sequence.steps) { - for (const node of step.nodes) { - const resultData = await context.getNodeData(node.id); - Logger.log(`Result for ${node.name}:`, resultData); - - if (resultData) { - writeToFile(logFolderPath, node.name, resultData); - } else { - Logger.error( - `Handler ${node.name} failed with error:`, - resultData.error, - ); - } - } - } - - Logger.log( - 'Sequence executed successfully. Logs stored in:', - logFolderPath, - ); - } catch (error) { - Logger.error('Error during sequence execution:', error); - fs.writeFileSync( - path.join(logFolderPath, 'error.txt'), - `Error: ${error.message}\n${error.stack}`, - 'utf8', - ); - throw new Error('Sequence execution failed.'); - } + executeBuildSequence('backend code geneerate', sequence); }, 600000, ); // Timeout set to 10 minutes diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 2a370915..a2801e22 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -7,17 +7,19 @@ import { BuildHandler, ExtractHandlerType, BuildNode, -} from './types'; -import { Logger } from '@nestjs/common'; -import { VirtualDirectory } from './virtual-dir'; -import { v4 as uuidv4 } from 'uuid'; -import { BuildMonitor } from './monitor'; -import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; -import { RetryHandler } from './retry-handler'; -import { BuildHandlerManager } from './hanlder-manager'; +} from './types'; // Importing type definitions related to the build process +import { Logger } from '@nestjs/common'; // Logger class from NestJS for logging +import { VirtualDirectory } from './virtual-dir'; // Virtual directory utility for managing virtual file structures +import { v4 as uuidv4 } from 'uuid'; // UUID generator for unique identifiers +import { BuildMonitor } from './monitor'; // Monitor to track the build process +import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; // OpenAI model provider for LLM operations +import { RetryHandler } from './retry-handler'; // Retry handler for retrying failed operations +import { BuildHandlerManager } from './hanlder-manager'; // Manager for building handler classes +import { sortBuildSequence } from './utils/build-utils'; /** - * Global data keys used throughout the build process + * Global data keys used throughout the build process. + * These keys represent different project-related data that can be accessed globally within the build context. * @type GlobalDataKeys */ export type GlobalDataKeys = @@ -30,22 +32,23 @@ export type GlobalDataKeys = | 'frontendPath'; /** - * Generic context data type mapping keys to any value + * A generic context data type that maps keys to any value. + * It allows storing and retrieving project-specific information in a flexible way. * @type ContextData */ type ContextData = Record; /** - * Core build context class that manages the execution of build sequences + * Core build context class responsible for managing the execution of build sequences. * @class BuilderContext - * @description Responsible for: - * - Managing build execution state - * - Handling node dependencies and execution order - * - Managing global and node-specific context data - * - Coordinating with build handlers and monitors - * - Managing virtual directory operations + * @description This class handles: + * - Managing the execution state of the build (completed, pending, failed, waiting). + * - Handling the dependencies and execution order of nodes (steps in the build process). + * - Managing global and node-specific context data. + * - Coordinating with build handlers, monitors, and virtual directory operations. */ export class BuilderContext { + // Keeps track of the execution state of nodes (completed, pending, failed, waiting) private executionState: BuildExecutionState = { completed: new Set(), pending: new Set(), @@ -53,21 +56,40 @@ export class BuilderContext { waiting: new Set(), }; - private globalPromises: Set> = new Set(); + // Logger instance for logging messages during the build process private logger: Logger; + + // Global context for storing project-related data private globalContext: Map = new Map(); + + // Node-specific data storage, keyed by handler constructors private nodeData: Map = new Map(); + // Various services and utilities for managing the build process private handlerManager: BuildHandlerManager; private retryHandler: RetryHandler; private monitor: BuildMonitor; public model: OpenAIModelProvider; public virtualDirectory: VirtualDirectory; + // Tracks running node executions, keyed by handler name + private runningNodes: Map>> = new Map(); + + // Polling interval for checking dependencies or waiting for node execution + private readonly POLL_INTERVAL = 500; + + /** + * Constructor to initialize the BuilderContext. + * Sets up the handler manager, retry handler, model provider, logger, and virtual directory. + * Initializes the global context with default values. + * @param sequence The build sequence containing nodes to be executed. + * @param id Unique identifier for the builder context. + */ constructor( private sequence: BuildSequence, id: string, ) { + // Initialize service instances this.retryHandler = RetryHandler.getInstance(); this.handlerManager = BuildHandlerManager.getInstance(); this.model = OpenAIModelProvider.getInstance(); @@ -75,10 +97,10 @@ export class BuilderContext { this.logger = new Logger(`builder-context-${id}`); this.virtualDirectory = new VirtualDirectory(); - // Initialize global context with default values + // Initialize global context with default project values this.globalContext.set('projectName', sequence.name); this.globalContext.set('description', sequence.description || ''); - this.globalContext.set('platform', 'web'); + this.globalContext.set('platform', 'web'); // Default platform is 'web' this.globalContext.set('databaseType', sequence.databaseType || 'SQLite'); this.globalContext.set( 'projectUUID', @@ -87,14 +109,14 @@ export class BuilderContext { } /** - * Checks if a node can be executed based on its dependencies - * @param node The node to check - * @returns boolean indicating if the node can be executed + * Checks if a node can be executed based on its handler's execution state and dependencies. + * @param node The node whose execution eligibility needs to be checked. + * @returns True if the node can be executed, otherwise false. */ private canExecute(node: BuildNode): boolean { - const handlerClass = node.handler; - const handlerName = handlerClass.name; + const handlerName = node.handler.name; + // Node cannot execute if it's already completed or pending execution if ( this.executionState.completed.has(handlerName) || this.executionState.pending.has(handlerName) @@ -102,61 +124,28 @@ export class BuilderContext { return false; } - if (!node.requires) return true; - - return !node.requires.some( - (reqName) => !this.executionState.completed.has(reqName), - ); + // Check if the node's dependencies are satisfied + return this.checkNodeDependencies(node); } /** - * Executes a specific node - * @param node The node to execute - * @returns Promise resolving to the build result - */ - private async executeNode( - node: BuildNode, - ): Promise>> { - const handlerClass = node.handler; // 直接使用,因为它已经是 BuildHandlerConstructor - const handlerName = handlerClass.name; - - if (!this.canExecute(node)) { - throw new Error(`Dependencies not met for node: ${handlerName}`); - } - - try { - this.executionState.pending.add(handlerName); - const result = await this.invokeNodeHandler>(node); - this.executionState.completed.add(handlerName); - this.logger.log(`${handlerName} is completed`); - this.executionState.pending.delete(handlerName); - - this.setNodeData(handlerClass, result.data); - return result; - } catch (error) { - this.executionState.failed.add(handlerName); - this.executionState.pending.delete(handlerName); - throw error; - } - } - /** - * Invokes a handler for a specific node - * @param node The node to execute - * @returns Promise resolving to the build result + * Invokes a handler for a specific node and returns the result. + * Handles retries in case of failure. + * @param node The node whose handler should be invoked. + * @returns The result of the handler's execution. */ private async invokeNodeHandler(node: BuildNode): Promise> { const handlerClass = node.handler; - this.logger.log(`solving ${handlerClass.name}`); - - if (!this.handlerManager.validateDependencies(handlerClass)) { - throw new Error(`Dependencies not met for handler: ${handlerClass.name}`); - } - + this.logger.log(`[Handler Start] ${handlerClass.name}`); try { + // Get the handler instance and execute it const handler = this.handlerManager.getHandler(handlerClass); - return await handler.run(this); + const result = await handler.run(this); // Invoke handler's run method + this.logger.log(`[Handler Success] ${handlerClass.name}`); + return result; } catch (e) { - this.logger.error(`retrying ${handlerClass.name}`); + // If handler fails, retry the operation + this.logger.error(`[Handler Retry] ${handlerClass.name}`); const result = await this.retryHandler.retryMethod( e, (node) => this.invokeNodeHandler(node), @@ -169,10 +158,12 @@ export class BuilderContext { } } + // Context management methods for global and node-specific data + /** - * Sets data in the global context - * @param key The key to set - * @param value The value to set + * Sets a value in the global context for a specific key. + * @param key The key to identify the context data. + * @param value The value to store in the context. */ setGlobalContext( key: Key, @@ -182,9 +173,9 @@ export class BuilderContext { } /** - * Gets data from the global context - * @param key The key to retrieve - * @returns The value associated with the key, or undefined + * Gets a value from the global context for a specific key. + * @param key The key of the context data. + * @returns The value stored in the context, or undefined if not found. */ getGlobalContext( key: Key, @@ -193,9 +184,9 @@ export class BuilderContext { } /** - * Retrieves node-specific data with type inference - * @param handlerClass The handler class to get data for - * @returns The strongly typed data associated with the handler + * Retrieves node-specific data for a given handler class. + * @param handlerClass The handler constructor whose node data should be retrieved. + * @returns The node-specific data or undefined if not found. */ getNodeData( handlerClass: T, @@ -204,9 +195,9 @@ export class BuilderContext { } /** - * Sets node-specific data with type checking - * @param handlerClass The handler class to set data for - * @param data The strongly typed data to associate with the handler + * Sets node-specific data for a given handler class. + * @param handlerClass The handler constructor whose node data should be set. + * @param data The data to store for the node. */ setNodeData( handlerClass: T, @@ -215,79 +206,188 @@ export class BuilderContext { this.nodeData.set(handlerClass, data); } + /** + * Gets the current execution state of the build process. + * @returns The current execution state, including completed, pending, failed, and waiting nodes. + */ getExecutionState(): BuildExecutionState { return { ...this.executionState }; } + /** + * Builds a virtual directory structure from the given JSON content. + * @param jsonContent The JSON string representing the virtual directory structure. + * @returns True if the structure was successfully parsed, false otherwise. + */ buildVirtualDirectory(jsonContent: string): boolean { return this.virtualDirectory.parseJsonStructure(jsonContent); } + /** + * Starts the execution of a specific node in the build sequence. + * The node will be executed if it's eligible (i.e., not completed or pending). + * @param node The node to execute. + * @returns A promise resolving to the result of the node execution. + */ + private startNodeExecution( + node: BuildNode, + ): Promise>> { + const handlerName = node.handler.name; + + // If the node is already completed, pending, or failed, skip execution + if ( + this.executionState.completed.has(handlerName) || + this.executionState.pending.has(handlerName) || + this.executionState.failed.has(handlerName) + ) { + this.logger.debug(`Node ${handlerName} already executed or in progress`); + return; + } + + // Mark the node as pending execution + this.executionState.pending.add(handlerName); + + // Execute the node handler and update the execution state accordingly + const executionPromise = this.invokeNodeHandler>(node) + .then((result) => { + // Mark the node as completed and update the state + this.executionState.completed.add(handlerName); + this.executionState.pending.delete(handlerName); + + // Store the result of the node execution + this.setNodeData(node.handler, result.data); + this.logger.log(`[Node Completed] ${handlerName}`); + return result; + }) + .catch((error) => { + // Mark the node as failed in case of an error + this.executionState.failed.add(handlerName); + this.executionState.pending.delete(handlerName); + this.logger.error(`[Node Failed] ${handlerName}:`, error); + throw error; + }); + + // Track the running node execution promise + this.runningNodes.set(handlerName, executionPromise); + return executionPromise; + } + + /** + * Executes the entire build sequence by iterating over all nodes in the sequence. + * - The nodes are executed in the given order, respecting dependencies. + * - Each node will only start execution once its dependencies are met. + * - The method waits for all nodes to finish before completing. + * @returns A promise that resolves when the entire build sequence is complete. + */ async execute(): Promise { - this.logger.log(`Starting build sequence: ${this.sequence.id}`); + this.logger.log(`[Sequence Start] ${this.sequence.id}`); + this.logger.debug(`Total nodes to execute: ${this.sequence.nodes.length}`); this.monitor.startSequenceExecution(this.sequence); try { - const nodes = this.sequence.nodes; + const nodes = sortBuildSequence(this.sequence); let currentIndex = 0; + const runningPromises = new Set>(); + // Loop over all nodes and execute them one by one while (currentIndex < nodes.length) { - const currentNode = nodes[currentIndex]; - const handlerClass = currentNode.handler; - const handlerName = handlerClass.name; + this.logger.debug( + `[Execution Status] Current index: ${currentIndex}, Running nodes: ${runningPromises.size}`, + ); - // Check if node is already completed - if (this.executionState.completed.has(handlerName)) { - currentIndex++; - continue; + const currentNode = nodes[currentIndex]; + if (!currentNode?.handler) { + this.logger.error( + `Invalid node at index ${currentIndex}, all node length: ${nodes.length}`, + ); + throw new Error(`Invalid node at index ${currentIndex}`); } - // Check dependencies - if (!this.checkDependencies(handlerClass)) { - const dependencies = - this.handlerManager.getDependencies(handlerClass); + const handlerName = currentNode.handler.name; + this.logger.debug( + `[Node Execution] ${handlerName}, and this can execute: ${this.canExecute(currentNode)}`, + ); + + // If the node cannot be executed yet, wait for dependencies to resolve + if (!this.canExecute(currentNode)) { this.logger.debug( - `Dependencies not met for ${handlerName}: ${dependencies.map((d) => d.name).join(', ')}. Waiting for previous nodes to complete.`, + `[Waiting Dependencies] Paused at node ${handlerName}, will check again in ${this.POLL_INTERVAL}ms`, + ); + await new Promise((resolve) => + setTimeout(resolve, this.POLL_INTERVAL), ); - // Since nodes are ordered by dependency, yield and wait for previous nodes - await new Promise((resolve) => setTimeout(resolve, 1000)); - // Don't increment currentIndex, stay at current node continue; } - // Dependencies satisfied, execute the node - this.monitor.startNodeExecution(handlerName, this.sequence.id); - try { - this.logger.log(`Executing node ${handlerName}`); - await this.executeNode(currentNode); - - this.monitor.endNodeExecution(handlerName, this.sequence.id, true); - - // Only move to next node after successful execution + // Start the execution of the node and track its progress + this.monitor.startNodeExecution(handlerName, this.sequence.id); + const nodePromise = this.startNodeExecution(currentNode) + .then((result) => { + this.monitor.endNodeExecution( + handlerName, + this.sequence.id, + true, // Mark the node execution as successful + ); + runningPromises.delete(nodePromise); // Remove the node from the running list + return result; + }) + .catch((error) => { + this.monitor.endNodeExecution( + handlerName, + this.sequence.id, + false, // Mark the node execution as failed + error instanceof Error ? error : new Error(String(error)), + ); + runningPromises.delete(nodePromise); // Remove the node from the running list + throw error; + }); + + // Add the node promise to the running promises set + runningPromises.add(nodePromise); + this.logger.debug( + `[Node Started] ${handlerName}, Total running: ${runningPromises.size}`, + ); + // Only increase the index if the node has been successfully started currentIndex++; } catch (error) { - this.monitor.endNodeExecution( - handlerName, - this.sequence.id, - false, - error instanceof Error ? error : new Error(String(error)), - ); + this.logger.error(`Failed to start node ${handlerName}:`, error); throw error; } } + // Wait for all running nodes to finish + while (runningPromises.size > 0) { + this.logger.debug( + `[Waiting] Remaining running nodes: ${runningPromises.size}`, + ); + await Promise.all(Array.from(runningPromises)); + // Give other promises a chance to resolve + await new Promise((resolve) => setTimeout(resolve, this.POLL_INTERVAL)); + } + + // Wait for any remaining LLM (Large Language Model) requests to complete const finalActivePromises = this.model.getAllActivePromises(); if (finalActivePromises.length > 0) { this.logger.debug( - `Final wait for ${finalActivePromises.length} remaining LLM requests`, + `[Final Wait] Waiting for ${finalActivePromises.length} remaining LLM requests`, ); await Promise.all(finalActivePromises); + + // Recheck if there are new LLM requests after the first wait + const remainingPromises = this.model.getAllActivePromises(); + if (remainingPromises.length > 0) { + this.logger.debug( + `[Final Check] Waiting for ${remainingPromises.length} additional LLM requests`, + ); + await Promise.all(remainingPromises); + } } - this.logger.log(`Build sequence completed: ${this.sequence.id}`); - this.logger.log('Final execution state:', this.executionState); + this.logger.log(`[Sequence Complete] ${this.sequence.id}`); + this.logger.debug('Final execution state:', this.executionState); } finally { + // End the monitoring of the sequence once all nodes are executed this.monitor.endSequenceExecution( this.sequence.id, this.globalContext.get('projectUUID'), @@ -296,14 +396,33 @@ export class BuilderContext { } /** - * Checks if all dependencies for a handler are completed - * @param handlerClass The handler class to check dependencies for - * @returns boolean indicating if all dependencies are satisfied + * Checks if a node's dependencies have been satisfied. + * Each handler may have a set of dependencies that must be completed before the node can run. + * @param node The node whose dependencies need to be checked. + * @returns True if all dependencies are met, otherwise false. */ - private checkDependencies(handlerClass: BuildHandlerConstructor): boolean { - const dependencies = this.handlerManager.getDependencies(handlerClass); - return dependencies.every((dep) => - this.executionState.completed.has(dep.name), - ); + private checkNodeDependencies(node: BuildNode): boolean { + this.logger.debug(`Checking dependencies for ${node.handler.name}`); + const handlerClass = this.handlerManager.getHandler(node.handler); + + // If the node has no dependencies, it's ready to execute + if (!handlerClass.dependencies?.length) { + this.logger.debug(`No dependencies for ${node.handler.name}`); + return true; + } + + // Check each dependency for the node + for (const dep of handlerClass.dependencies) { + if (!this.executionState.completed.has(dep.name)) { + this.logger.debug( + `Dependency ${dep.name} not met for ${node.handler.name}`, + ); + return false; + } + } + + // All dependencies are met, so the node can execute + this.logger.debug(`All dependencies met for ${node.handler.name}`); + return true; } } diff --git a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts index 490fd398..3cf7888c 100644 --- a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts +++ b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts @@ -13,10 +13,10 @@ import { import { BuildNode } from 'src/build-system/hanlder-manager'; @BuildNode() -export class PRDHandler implements BuildHandler { +export class PRDHandler implements BuildHandler { readonly logger: Logger = new Logger('PRDHandler'); - async run(context: BuilderContext): Promise { + async run(context: BuilderContext): Promise> { this.logger.log('Generating PRD...'); // Extract project data from the context diff --git a/backend/src/build-system/handlers/ux/sitemap-document/index.ts b/backend/src/build-system/handlers/ux/sitemap-document/index.ts index 46f46397..8fadeb3d 100644 --- a/backend/src/build-system/handlers/ux/sitemap-document/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-document/index.ts @@ -19,6 +19,7 @@ export class UXSMDHandler implements BuildHandler { context.getGlobalContext('projectName') || 'Default Project Name'; const platform = context.getGlobalContext('platform') || 'Default Platform'; const prdContent = context.getNodeData(PRDHandler); + this.logger.log('prd in uxsmd', prdContent); // Generate the prompt dynamically const prompt = prompts.generateUxsmdPrompt(projectName, platform); diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts index 128ad0b8..d8019857 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts @@ -7,11 +7,12 @@ import { MissingConfigurationError, ResponseParsingError, } from 'src/build-system/errors'; -import { BuildNode } from 'src/build-system/hanlder-manager'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; import { UXSMDHandler } from '../sitemap-document'; import { UXSitemapStructureHandler } from '.'; @BuildNode() +@BuildNodeRequire([UXSMDHandler, UXSitemapStructureHandler]) export class UXSitemapStructurePagebyPageHandler implements BuildHandler { diff --git a/backend/src/build-system/hanlder-manager.ts b/backend/src/build-system/hanlder-manager.ts index 821dbc7d..7114809f 100644 --- a/backend/src/build-system/hanlder-manager.ts +++ b/backend/src/build-system/hanlder-manager.ts @@ -1,18 +1,19 @@ import { BuildHandler, BuildHandlerConstructor } from './types'; /** - * 构建处理器管理器 + * Build Handler Manager + * This class is a singleton responsible for managing instances of BuildHandlers. */ export class BuildHandlerManager { private static instance: BuildHandlerManager; private handlers = new Map(); - private dependencies = new Map< - BuildHandlerConstructor, - BuildHandlerConstructor[] - >(); private constructor() {} + /** + * Get the singleton instance of BuildHandlerManager + * @returns The instance of BuildHandlerManager + */ static getInstance(): BuildHandlerManager { if (!BuildHandlerManager.instance) { BuildHandlerManager.instance = new BuildHandlerManager(); @@ -21,18 +22,23 @@ export class BuildHandlerManager { } /** - * 注册处理器 + * Register a build handler + * @param handlerClass The constructor of the handler to register */ registerHandler( handlerClass: BuildHandlerConstructor, ): void { if (!this.handlers.has(handlerClass)) { + // Create an instance of the handler if not already registered this.handlers.set(handlerClass, new handlerClass()); } } /** - * 获取处理器实例 + * Get an instance of a registered build handler + * If the handler is not yet registered, it will be instantiated and registered. + * @param handlerClass The constructor of the handler to retrieve + * @returns An instance of the specified build handler */ getHandler( handlerClass: BuildHandlerConstructor, @@ -46,66 +52,44 @@ export class BuildHandlerManager { } /** - * 注册处理器依赖 - */ - registerDependencies( - handlerClass: BuildHandlerConstructor, - dependencies: BuildHandlerConstructor[], - ): void { - this.dependencies.set(handlerClass, dependencies); - } - - /** - * 获取处理器依赖 - */ - getDependencies( - handlerClass: BuildHandlerConstructor, - ): BuildHandlerConstructor[] { - return this.dependencies.get(handlerClass) || []; - } - - /** - * 验证处理器依赖 - */ - validateDependencies(handlerClass: BuildHandlerConstructor): boolean { - const dependencies = this.getDependencies(handlerClass); - return dependencies.every((dep) => this.handlers.has(dep)); - } - - /** - * 清除所有注册的处理器 + * Clear all registered build handlers + * This will remove all handler instances from the manager. */ clear(): void { this.handlers.clear(); - this.dependencies.clear(); } } /** - * 处理器装饰器 + * BuildNode Decorator + * A decorator to register a handler class as a build handler. + * This makes the handler class managed by the BuildHandlerManager. */ export function BuildNode() { return function (target: T): T { const manager = BuildHandlerManager.getInstance(); - manager.registerHandler(target); + manager.registerHandler(target); // Register the handler class return target; }; } /** - * 依赖装饰器 + * BuildNodeRequire Decorator + * A decorator to define dependencies for the handler class. + * This specifies other handler classes that the current handler depends on. + * @param dependencies A list of build handler constructors that this handler depends on. */ export function BuildNodeRequire(dependencies: BuildHandlerConstructor[]) { return function (target: T): T { - const manager = BuildHandlerManager.getInstance(); - manager.registerDependencies(target, dependencies); + target.prototype.dependencies = dependencies || []; // Store the dependencies in the class prototype return target; }; } -// // 使用示例 +// Example usage: + // @BuildNode() -// @NodeRequire([/* 依赖处理器 */]) +// @BuildNodeRequire([/* Dependencies */]) // class ExampleHandler implements BuildHandler { // async run( // context: BuilderContext, @@ -118,5 +102,6 @@ export function BuildNodeRequire(dependencies: BuildHandlerConstructor[]) { // } // } -// // 类型提取示例 -// type ExampleOutput = ExtractHandlerType; // string +// Type extraction example: +// type ExampleOutput = ExtractHandlerType; +// This type will be 'string' in this case, as the handler returns a string. diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index 4a9ef3bf..0c13a4f2 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -10,7 +10,6 @@ export interface BuildBase { handler: new () => BuildHandler; name?: string; description?: string; - requires?: string[]; options?: BuildOpts; } @@ -73,6 +72,8 @@ export interface BuildContext { */ export interface BuildHandler { run(context: BuilderContext, opts?: BuildOpts): Promise>; + + dependencies?: BuildHandlerConstructor[]; } /** diff --git a/backend/src/build-system/utils/__test__/build-utils.spec.ts b/backend/src/build-system/utils/__test__/build-utils.spec.ts new file mode 100644 index 00000000..8e8c21c4 --- /dev/null +++ b/backend/src/build-system/utils/__test__/build-utils.spec.ts @@ -0,0 +1,169 @@ +import { BuildSequence } from 'src/build-system/types'; +import { Logger } from '@nestjs/common'; +import { ProjectInitHandler } from 'src/build-system/handlers/project-init'; +import { PRDHandler } from 'src/build-system/handlers/product-manager/product-requirements-document/prd'; +import { UXSMDHandler } from 'src/build-system/handlers/ux/sitemap-document'; +import { UXSitemapStructureHandler } from 'src/build-system/handlers/ux/sitemap-structure'; +import { UXDatamapHandler } from 'src/build-system/handlers/ux/datamap'; +import { DatabaseRequirementHandler } from 'src/build-system/handlers/database/requirements-document'; +import { FileStructureHandler } from 'src/build-system/handlers/file-manager/file-structure'; +import { UXSitemapStructurePagebyPageHandler } from 'src/build-system/handlers/ux/sitemap-structure/sms-page'; +import { DBSchemaHandler } from 'src/build-system/handlers/database/schemas/schemas'; +import { FileArchGenerateHandler } from 'src/build-system/handlers/file-manager/file-arch'; +import { BackendRequirementHandler } from 'src/build-system/handlers/backend/requirements-document'; +import { BackendCodeHandler } from 'src/build-system/handlers/backend/code-generate'; +import { BackendFileReviewHandler } from 'src/build-system/handlers/backend/file-review/file-review'; +import { sortBuildSequence } from '../build-utils'; + +const logger = new Logger('BuildUtilsTest'); + +describe('Build Sequence Test', () => { + it('should execute build sequence successfully', async () => { + const sequence: BuildSequence = { + id: 'test-backend-sequence', + version: '1.0.0', + name: 'Spotify-like Music Web', + description: 'Users can play music', + databaseType: 'SQLite', + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + }, + { + handler: PRDHandler, + name: 'Project Requirements Document Node', + }, + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, + { + handler: UXSitemapStructureHandler, + name: 'UX Sitemap Structure Node', + }, + { + handler: UXDatamapHandler, + name: 'UX DataMap Document Node', + }, + { + handler: DatabaseRequirementHandler, + name: 'Database Requirements Node', + }, + { + handler: FileStructureHandler, + name: 'File Structure Generation', + options: { + projectPart: 'frontend', + }, + }, + { + handler: UXSitemapStructurePagebyPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', + }, + { + handler: DBSchemaHandler, + name: 'Database Schemas Node', + }, + { + handler: FileArchGenerateHandler, + name: 'File Arch', + }, + { + handler: BackendRequirementHandler, + name: 'Backend Requirements Node', + }, + { + handler: BackendCodeHandler, + name: 'Backend Code Generator Node', + }, + { + handler: BackendFileReviewHandler, + name: 'Backend File Review Node', + }, + ], + }; + + logger.log('Before Sorting:'); + sequence.nodes.forEach((node, index) => { + logger.log(`${index + 1}: ${node.name}`); + }); + + const sortedNodes = sortBuildSequence(sequence); + + logger.log('\nAfter Sorting:'); + sortedNodes.forEach((node, index) => { + logger.log(`${index + 1}: ${node.name}`); + }); + + logger.log('\nBefore/After Comparison (Same Index):'); + sequence.nodes.forEach((node, index) => { + const sortedNode = sortedNodes[index]; + logger.log(`Index ${index + 1}:`); + logger.log(` Before: ${node.name}`); + logger.log(` After: ${sortedNode.name}`); + }); + }, 300000); +}); + +describe('sortBuildSequence Tests', () => { + it('should sort build sequence correctly based on dependencies', () => { + const sequence: BuildSequence = { + id: 'test-backend-sequence', + version: '1.0.0', + name: 'Spotify-like Music Web', + description: 'Users can play music', + databaseType: 'SQLite', + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + description: 'Create project folders', + }, + + { + handler: PRDHandler, + name: 'Project Requirements Document Node', + }, + + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, + + { + handler: UXSitemapStructureHandler, + name: 'UX Sitemap Structure Node', + }, + { + handler: UXDatamapHandler, + name: 'UX DataMap Document Node', + }, + { + handler: UXSitemapStructurePagebyPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', + }, + ], + }; + + logger.log('Before Sorting:'); + sequence.nodes.forEach((node, index) => { + logger.log(`${index + 1}: ${node.name}`); + }); + + const sortedNodes = sortBuildSequence(sequence); + + logger.log('\nAfter Sorting:'); + sortedNodes.forEach((node, index) => { + logger.log(`${index + 1}: ${node.name}`); + }); + + logger.log('\nBefore/After Comparison (Same Index):'); + sequence.nodes.forEach((node, index) => { + const sortedNode = sortedNodes[index]; + logger.log(`Index ${index + 1}:`); + logger.log(` Before: ${node.name}`); + logger.log(` After: ${sortedNode.name}`); + }); + }); +}); diff --git a/backend/src/build-system/utils/build-utils.ts b/backend/src/build-system/utils/build-utils.ts new file mode 100644 index 00000000..c2cbf1d2 --- /dev/null +++ b/backend/src/build-system/utils/build-utils.ts @@ -0,0 +1,86 @@ +import { BuildHandlerManager } from '../hanlder-manager'; +import { BuildSequence, BuildNode, BuildHandlerConstructor } from '../types'; + +/** + * Helper function to sort a BuildSequence based on node dependencies using topological sort. + * It ensures that each node's dependencies are executed before the node itself. + * + * @param sequence The build sequence to be sorted. + * @returns A sorted array of nodes that respects the dependency order. + */ +export function sortBuildSequence(sequence: BuildSequence): BuildNode[] { + const { nodes } = sequence; + + // Map to store the dependencies for each node + const nodeDependencies: Map< + BuildHandlerConstructor, + Set + > = new Map(); + + // Map to store the reverse of the dependency (i.e., which nodes depend on the current node) + const dependentNodes: Map< + BuildHandlerConstructor, + Set + > = new Map(); + + // Initialize the maps + nodes.forEach((node) => { + nodeDependencies.set(node.handler, new Set()); + dependentNodes.set(node.handler, new Set()); + }); + + // Step 1: Analyze the dependencies of each handler (e.g., if a node has a dependency on another) + nodes.forEach((node) => { + const handlerClass = BuildHandlerManager.getInstance().getHandler( + node.handler, + ); + const dependencies = handlerClass.dependencies || []; + + dependencies.forEach((dep) => { + // Add the dependency to the node's dependency list + nodeDependencies.get(handlerClass)?.add(dep); + + // Also add the current node as a dependent of the dependency + dependentNodes.get(dep)?.add(handlerClass); + }); + }); + + // Step 2: Perform topological sort (Kahn's algorithm) + const sortedNodes: BuildNode[] = []; + const queue: BuildHandlerConstructor[] = []; + + // Find all nodes with no dependencies (inDegree = 0) + nodeDependencies.forEach((dependencies, handler) => { + if (dependencies.size === 0) { + queue.push(handler); + } + }); + + // Perform Kahn's algorithm (topological sorting) + while (queue.length > 0) { + const currentHandler = queue.shift()!; + const node = nodes.find((node) => node.handler === currentHandler); + if (node) { + sortedNodes.push(node); + } + + // For each node that depends on the current node, reduce the dependency count + const dependentHandlers = dependentNodes.get(currentHandler) || []; + dependentHandlers.forEach((depHandler) => { + // Remove the current node from the dependency list of the dependent node + nodeDependencies.get(depHandler)?.delete(currentHandler); + + // If a node now has no remaining dependencies, add it to the queue + if (nodeDependencies.get(depHandler)?.size === 0) { + queue.push(depHandler); + } + }); + } + + // Step 3: If the sortedNodes array doesn't contain all nodes, there's a circular dependency + if (sortedNodes.length !== nodes.length) { + throw new Error('Circular dependency detected in the build sequence'); + } + + return sortedNodes; +} From 36a973045e0235978a6bc03fe28a33b8361dd91a Mon Sep 17 00:00:00 2001 From: Sma1lboy <541898146chen@gmail.com> Date: Fri, 17 Jan 2025 22:34:13 -0500 Subject: [PATCH 4/4] refactor: rename handlers for consistency and clarity --- .../__tests__/fullstack-gen.spec.ts | 20 +- .../__tests__/test-generate-doc.spec.ts | 174 +++++++++--------- .../test.backend-code-generator.spec.ts | 4 +- .../__tests__/test.sms-lvl2.spec.ts | 12 +- .../handlers/backend/code-generate/index.ts | 6 +- .../backend/requirements-document/index.ts | 10 +- .../database/requirements-document/index.ts | 10 +- .../handlers/database/schemas/schemas.ts | 6 +- .../handlers/file-manager/file-arch/index.ts | 10 +- .../file-manager/file-generate/index.ts | 6 +- .../file-manager/file-structure/index.ts | 6 +- .../handlers/frontend-code-generate/index.ts | 18 +- .../build-system/handlers/ux/datamap/index.ts | 4 +- .../handlers/ux/sitemap-structure/index.ts | 4 +- .../handlers/ux/sitemap-structure/sms-page.ts | 14 +- .../utils/__test__/build-utils.spec.ts | 26 +-- 16 files changed, 164 insertions(+), 166 deletions(-) diff --git a/backend/src/build-system/__tests__/fullstack-gen.spec.ts b/backend/src/build-system/__tests__/fullstack-gen.spec.ts index d1bfe665..8e011efc 100644 --- a/backend/src/build-system/__tests__/fullstack-gen.spec.ts +++ b/backend/src/build-system/__tests__/fullstack-gen.spec.ts @@ -5,13 +5,13 @@ import { Logger } from '@nestjs/common'; import { ProjectInitHandler } from '../handlers/project-init'; import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; import { UXSMDHandler } from '../handlers/ux/sitemap-document'; -import { UXSitemapStructureHandler } from '../handlers/ux/sitemap-structure'; -import { UXDatamapHandler } from '../handlers/ux/datamap'; -import { DatabaseRequirementHandler } from '../handlers/database/requirements-document'; +import { UXSMSHandler } from '../handlers/ux/sitemap-structure'; +import { UXDMDHandler } from '../handlers/ux/datamap'; +import { DBRequirementHandler } from '../handlers/database/requirements-document'; import { FileStructureHandler } from '../handlers/file-manager/file-structure'; -import { UXSitemapStructurePagebyPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; +import { UXSMSPageByPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; -import { FileArchGenerateHandler } from '../handlers/file-manager/file-arch'; +import { FileFAHandler } from '../handlers/file-manager/file-arch'; import { BackendRequirementHandler } from '../handlers/backend/requirements-document'; import { BackendCodeHandler } from '../handlers/backend/code-generate'; import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-review'; @@ -38,17 +38,17 @@ import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-r name: 'UX Sitemap Document Node', }, { - handler: UXSitemapStructureHandler, + handler: UXSMSHandler, name: 'UX Sitemap Structure Node', // requires: ['op:UX:SMD'], }, { - handler: UXDatamapHandler, + handler: UXDMDHandler, name: 'UX DataMap Document Node', // requires: ['op:UX:SMD'], }, { - handler: DatabaseRequirementHandler, + handler: DBRequirementHandler, name: 'Database Requirements Node', // requires: ['op:UX:DATAMAP:DOC'], }, @@ -61,7 +61,7 @@ import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-r }, }, { - handler: UXSitemapStructurePagebyPageHandler, + handler: UXSMSPageByPageHandler, name: 'Level 2 UX Sitemap Structure Node details', // requires: ['op:UX:SMS'], }, @@ -71,7 +71,7 @@ import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-r // requires: ['op:DATABASE_REQ'], }, { - handler: FileArchGenerateHandler, + handler: FileFAHandler, name: 'File Arch', // requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], }, diff --git a/backend/src/build-system/__tests__/test-generate-doc.spec.ts b/backend/src/build-system/__tests__/test-generate-doc.spec.ts index 7b6303f1..43759553 100644 --- a/backend/src/build-system/__tests__/test-generate-doc.spec.ts +++ b/backend/src/build-system/__tests__/test-generate-doc.spec.ts @@ -1,91 +1,91 @@ -import { isIntegrationTest } from 'src/common/utils'; -import { BuildSequence } from '../types'; -import { executeBuildSequence } from './utils'; -import { Logger } from '@nestjs/common'; +// import { isIntegrationTest } from 'src/common/utils'; +// import { BuildSequence } from '../types'; +// import { executeBuildSequence } from './utils'; +// import { Logger } from '@nestjs/common'; -// TODO: adding integration flag -(isIntegrationTest ? describe : describe.skip)( - 'Sequence: PRD -> UXSD -> UXDD -> UXSS', - () => { - it('should execute the full sequence and log results to individual files', async () => { - const sequence: BuildSequence = { - id: 'test-backend-sequence', - version: '1.0.0', - name: 'Spotify-like Music Web', - description: 'Users can play music', - databaseType: 'SQLite', - steps: [ - { - id: 'step-1', - name: 'Generate PRD', - nodes: [ - { - id: 'op:PRD', - name: 'PRD Generation Node', - }, - ], - }, - { - id: 'step-2', - name: 'Generate UX Sitemap Document', - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - }, - ], - }, - { - id: 'step-3', - name: 'Generate UX Sitemap Structure', - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - }, - ], - }, - { - id: 'step-4', - name: 'UX Data Map Document', - nodes: [ - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX Data Map Document node', - }, - ], - }, - { - id: 'step-5', - name: 'UX SMD LEVEL 2 Page Details', - nodes: [ - { - id: 'op:UX:SMS:LEVEL2', - name: 'UX SMD LEVEL 2 Page Details Node', - }, - ], - }, - ], - }; +// // TODO: adding integration flag +// (isIntegrationTest ? describe : describe.skip)( +// 'Sequence: PRD -> UXSD -> UXDD -> UXSS', +// () => { +// it('should execute the full sequence and log results to individual files', async () => { +// const sequence: BuildSequence = { +// id: 'test-backend-sequence', +// version: '1.0.0', +// name: 'Spotify-like Music Web', +// description: 'Users can play music', +// databaseType: 'SQLite', +// steps: [ +// { +// id: 'step-1', +// name: 'Generate PRD', +// nodes: [ +// { +// id: 'op:PRD', +// name: 'PRD Generation Node', +// }, +// ], +// }, +// { +// id: 'step-2', +// name: 'Generate UX Sitemap Document', +// nodes: [ +// { +// id: 'op:UX:SMD', +// name: 'UX Sitemap Document Node', +// }, +// ], +// }, +// { +// id: 'step-3', +// name: 'Generate UX Sitemap Structure', +// nodes: [ +// { +// id: 'op:UX:SMS', +// name: 'UX Sitemap Structure Node', +// }, +// ], +// }, +// { +// id: 'step-4', +// name: 'UX Data Map Document', +// nodes: [ +// { +// id: 'op:UX:DATAMAP:DOC', +// name: 'UX Data Map Document node', +// }, +// ], +// }, +// { +// id: 'step-5', +// name: 'UX SMD LEVEL 2 Page Details', +// nodes: [ +// { +// id: 'op:UX:SMS:LEVEL2', +// name: 'UX SMD LEVEL 2 Page Details Node', +// }, +// ], +// }, +// ], +// }; - try { - const result = await executeBuildSequence( - 'test-generate-all-ux-part', - sequence, - ); +// try { +// const result = await executeBuildSequence( +// 'test-generate-all-ux-part', +// sequence, +// ); - Logger.log( - 'Sequence completed successfully. Logs stored in:', - result.logFolderPath, - ); +// Logger.log( +// 'Sequence completed successfully. Logs stored in:', +// result.logFolderPath, +// ); - if (!result.success) { - throw result.error; - } - } catch (error) { - Logger.error('Error during sequence execution:', error); - throw error; - } - }, 600000); - }, -); +// if (!result.success) { +// throw result.error; +// } +// } catch (error) { +// Logger.error('Error during sequence execution:', error); +// throw error; +// } +// }, 600000); +// }, +// ); diff --git a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts index 1b3c5d42..75c43f88 100644 --- a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts +++ b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts @@ -4,7 +4,7 @@ import { executeBuildSequence } from './utils'; import { isIntegrationTest } from 'src/common/utils'; import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; import { UXSMDHandler } from '../handlers/ux/sitemap-document'; -import { DatabaseRequirementHandler } from '../handlers/database/requirements-document'; +import { DBRequirementHandler } from '../handlers/database/requirements-document'; import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; import { BackendCodeHandler } from '../handlers/backend/code-generate'; import { ProjectInitHandler } from '../handlers/project-init'; @@ -49,7 +49,7 @@ import { ProjectInitHandler } from '../handlers/project-init'; }, { - handler: DatabaseRequirementHandler, + handler: DBRequirementHandler, name: 'Database Requirements Node', // requires: ['op:UX:DATAMAP:DOC'], }, diff --git a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts index 10cac481..d6c684c6 100644 --- a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts +++ b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts @@ -1,10 +1,10 @@ import { isIntegrationTest } from 'src/common/utils'; import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; import { ProjectInitHandler } from '../handlers/project-init'; -import { UXDatamapHandler } from '../handlers/ux/datamap'; +import { UXDMDHandler } from '../handlers/ux/datamap'; import { UXSMDHandler } from '../handlers/ux/sitemap-document'; -import { UXSitemapStructureHandler } from '../handlers/ux/sitemap-structure'; -import { UXSitemapStructurePagebyPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; +import { UXSMSHandler as UXSMSHandler } from '../handlers/ux/sitemap-structure'; +import { UXSMSPageByPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; import { BuildSequence } from '../types'; import { executeBuildSequence } from './utils'; @@ -34,15 +34,15 @@ import { executeBuildSequence } from './utils'; }, { - handler: UXSitemapStructureHandler, + handler: UXSMSHandler, name: 'UX Sitemap Structure Node', }, { - handler: UXDatamapHandler, + handler: UXDMDHandler, name: 'UX DataMap Document Node', }, { - handler: UXSitemapStructurePagebyPageHandler, + handler: UXSMSPageByPageHandler, name: 'Level 2 UX Sitemap Structure Node details', }, ], diff --git a/backend/src/build-system/handlers/backend/code-generate/index.ts b/backend/src/build-system/handlers/backend/code-generate/index.ts index 964c0970..091d067f 100644 --- a/backend/src/build-system/handlers/backend/code-generate/index.ts +++ b/backend/src/build-system/handlers/backend/code-generate/index.ts @@ -13,7 +13,7 @@ import { } from 'src/build-system/errors'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; import { UXSMDHandler } from '../../ux/sitemap-document'; -import { UXDatamapHandler } from '../../ux/datamap'; +import { UXDMDHandler } from '../../ux/datamap'; import { DBSchemaHandler } from '../../database/schemas/schemas'; import { BackendRequirementHandler } from '../requirements-document'; @@ -23,7 +23,7 @@ import { BackendRequirementHandler } from '../requirements-document'; */ @BuildNode() -@BuildNodeRequire([UXSMDHandler, UXDatamapHandler, DBSchemaHandler]) +@BuildNodeRequire([UXSMDHandler, UXDMDHandler, DBSchemaHandler]) export class BackendCodeHandler implements BuildHandler { async run(context: BuilderContext): Promise> { const projectName = @@ -32,7 +32,7 @@ export class BackendCodeHandler implements BuildHandler { context.getGlobalContext('databaseType') || 'Default database type'; // Retrieve required documents const sitemapDoc = context.getNodeData(UXSMDHandler); - const datamapDoc = context.getNodeData(UXDatamapHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); const databaseSchemas = context.getNodeData(DBSchemaHandler); const backendRequirementDoc = context.getNodeData(BackendRequirementHandler)?.overview || ''; diff --git a/backend/src/build-system/handlers/backend/requirements-document/index.ts b/backend/src/build-system/handlers/backend/requirements-document/index.ts index 98dd4897..50a077e8 100644 --- a/backend/src/build-system/handlers/backend/requirements-document/index.ts +++ b/backend/src/build-system/handlers/backend/requirements-document/index.ts @@ -9,8 +9,8 @@ import { } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; -import { DatabaseRequirementHandler } from '../../database/requirements-document'; -import { UXDatamapHandler } from '../../ux/datamap'; +import { DBRequirementHandler } from '../../database/requirements-document'; +import { UXDMDHandler } from '../../ux/datamap'; import { UXSMDHandler } from '../../ux/sitemap-document'; type BackendRequirementResult = { @@ -29,7 +29,7 @@ type BackendRequirementResult = { */ @BuildNode() -@BuildNodeRequire([DatabaseRequirementHandler, UXDatamapHandler, UXSMDHandler]) +@BuildNodeRequire([DBRequirementHandler, UXDMDHandler, UXSMDHandler]) export class BackendRequirementHandler implements BuildHandler { @@ -46,8 +46,8 @@ export class BackendRequirementHandler const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const dbRequirements = context.getNodeData(DatabaseRequirementHandler); - const datamapDoc = context.getNodeData(UXDatamapHandler); + const dbRequirements = context.getNodeData(DBRequirementHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); const sitemapDoc = context.getNodeData(UXSMDHandler); if (!dbRequirements || !datamapDoc || !sitemapDoc) { diff --git a/backend/src/build-system/handlers/database/requirements-document/index.ts b/backend/src/build-system/handlers/database/requirements-document/index.ts index 677a23ba..87fb0f15 100644 --- a/backend/src/build-system/handlers/database/requirements-document/index.ts +++ b/backend/src/build-system/handlers/database/requirements-document/index.ts @@ -8,12 +8,12 @@ import { ModelUnavailableError, } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; -import { UXDatamapHandler } from '../../ux/datamap'; +import { UXDMDHandler } from '../../ux/datamap'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; @BuildNode() -@BuildNodeRequire([UXDatamapHandler]) -export class DatabaseRequirementHandler implements BuildHandler { +@BuildNodeRequire([UXDMDHandler]) +export class DBRequirementHandler implements BuildHandler { private readonly logger = new Logger('DatabaseRequirementHandler'); async run(context: BuilderContext): Promise> { @@ -22,7 +22,7 @@ export class DatabaseRequirementHandler implements BuildHandler { const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const datamapDoc = context.getNodeData(UXDatamapHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); if (!datamapDoc) { this.logger.error('Data mapping document is missing.'); @@ -46,7 +46,7 @@ export class DatabaseRequirementHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateDatabaseRequirementPrompt', - DatabaseRequirementHandler.name, + DBRequirementHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); diff --git a/backend/src/build-system/handlers/database/schemas/schemas.ts b/backend/src/build-system/handlers/database/schemas/schemas.ts index 1a58c772..2e268120 100644 --- a/backend/src/build-system/handlers/database/schemas/schemas.ts +++ b/backend/src/build-system/handlers/database/schemas/schemas.ts @@ -17,11 +17,11 @@ import { ModelUnavailableError, ResponseTagError, } from 'src/build-system/errors'; -import { DatabaseRequirementHandler } from '../requirements-document'; +import { DBRequirementHandler } from '../requirements-document'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; @BuildNode() -@BuildNodeRequire([DatabaseRequirementHandler]) +@BuildNodeRequire([DBRequirementHandler]) export class DBSchemaHandler implements BuildHandler { private readonly logger: Logger = new Logger('DBSchemaHandler'); async run(context: BuilderContext): Promise { @@ -32,7 +32,7 @@ export class DBSchemaHandler implements BuildHandler { context.getGlobalContext('projectName') || 'Default Project Name'; const databaseType = context.getGlobalContext('databaseType') || 'PostgreSQL'; - const dbRequirements = context.getNodeData(DatabaseRequirementHandler); + const dbRequirements = context.getNodeData(DBRequirementHandler); const uuid = context.getGlobalContext('projectUUID'); // 2. Validate database type diff --git a/backend/src/build-system/handlers/file-manager/file-arch/index.ts b/backend/src/build-system/handlers/file-manager/file-arch/index.ts index fcadede1..acf633f5 100644 --- a/backend/src/build-system/handlers/file-manager/file-arch/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-arch/index.ts @@ -19,12 +19,12 @@ import { validateAgainstVirtualDirectory, } from 'src/build-system/utils/file_generator_util'; import { FileStructureHandler } from '../file-structure'; -import { UXDatamapHandler } from '../../ux/datamap'; +import { UXDMDHandler } from '../../ux/datamap'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; @BuildNode() -@BuildNodeRequire([FileStructureHandler, UXDatamapHandler]) -export class FileArchGenerateHandler implements BuildHandler { +@BuildNodeRequire([FileStructureHandler, UXDMDHandler]) +export class FileFAHandler implements BuildHandler { private readonly logger: Logger = new Logger('FileArchGenerateHandler'); private virtualDir: VirtualDirectory; @@ -34,7 +34,7 @@ export class FileArchGenerateHandler implements BuildHandler { this.virtualDir = context.virtualDirectory; const fileStructure = context.getNodeData(FileStructureHandler); - const datamapDoc = context.getNodeData(UXDatamapHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); if (!fileStructure || !datamapDoc) { Logger.error(fileStructure); @@ -58,7 +58,7 @@ export class FileArchGenerateHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateFileArch', - FileArchGenerateHandler.name, + FileFAHandler.name, ); } catch (error) { this.logger.error('Model is unavailable:' + error); diff --git a/backend/src/build-system/handlers/file-manager/file-generate/index.ts b/backend/src/build-system/handlers/file-manager/file-generate/index.ts index 26a0a2a7..2c45df49 100644 --- a/backend/src/build-system/handlers/file-manager/file-generate/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-generate/index.ts @@ -13,18 +13,18 @@ import { FileWriteError, } from 'src/build-system/errors'; import { getProjectPath } from 'codefox-common'; -import { FileArchGenerateHandler } from '../file-arch'; +import { FileFAHandler } from '../file-arch'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; @BuildNode() -@BuildNodeRequire([FileArchGenerateHandler]) +@BuildNodeRequire([FileFAHandler]) export class FileGeneratorHandler implements BuildHandler { private readonly logger = new Logger('FileGeneratorHandler'); private virtualDir: VirtualDirectory; async run(context: BuilderContext): Promise> { this.virtualDir = context.virtualDirectory; - const fileArchDoc = context.getNodeData(FileArchGenerateHandler); + const fileArchDoc = context.getNodeData(FileFAHandler); const uuid = context.getGlobalContext('projectUUID'); if (!fileArchDoc) { diff --git a/backend/src/build-system/handlers/file-manager/file-structure/index.ts b/backend/src/build-system/handlers/file-manager/file-structure/index.ts index ea77d8a1..5a90fa94 100644 --- a/backend/src/build-system/handlers/file-manager/file-structure/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-structure/index.ts @@ -14,7 +14,7 @@ import { MissingConfigurationError, } from 'src/build-system/errors'; import { UXSMDHandler } from '../../ux/sitemap-document'; -import { UXDatamapHandler } from '../../ux/datamap'; +import { UXDMDHandler } from '../../ux/datamap'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; /** @@ -22,7 +22,7 @@ import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; * based on the provided documentation. */ @BuildNode() -@BuildNodeRequire([UXSMDHandler, UXDatamapHandler]) +@BuildNodeRequire([UXSMDHandler, UXDMDHandler]) export class FileStructureHandler implements BuildHandler { readonly id = 'op:FILE:STRUCT'; private readonly logger: Logger = new Logger('FileStructureHandler'); @@ -37,7 +37,7 @@ export class FileStructureHandler implements BuildHandler { const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; const sitemapDoc = context.getNodeData(UXSMDHandler); - const datamapDoc = context.getNodeData(UXDatamapHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); const projectPart = opts?.projectPart ?? 'frontend'; const framework = context.getGlobalContext('framework') ?? 'react'; diff --git a/backend/src/build-system/handlers/frontend-code-generate/index.ts b/backend/src/build-system/handlers/frontend-code-generate/index.ts index 112c5a01..8eb6a1ae 100644 --- a/backend/src/build-system/handlers/frontend-code-generate/index.ts +++ b/backend/src/build-system/handlers/frontend-code-generate/index.ts @@ -13,10 +13,10 @@ import { readFile } from 'fs/promises'; import { parseGenerateTag } from 'src/build-system/utils/strings'; import { generateFrontEndCodePrompt, generateCSSPrompt } from './prompt'; -import { UXSitemapStructureHandler } from '../ux/sitemap-structure'; -import { UXDatamapHandler } from '../ux/datamap'; +import { UXSMSHandler } from '../ux/sitemap-structure'; +import { UXDMDHandler } from '../ux/datamap'; import { BackendRequirementHandler } from '../backend/requirements-document'; -import { FileArchGenerateHandler } from '../file-manager/file-arch'; +import { FileFAHandler } from '../file-manager/file-arch'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; /** @@ -26,10 +26,10 @@ import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; */ @BuildNode() @BuildNodeRequire([ - UXSitemapStructureHandler, - UXDatamapHandler, + UXSMSHandler, + UXDMDHandler, BackendRequirementHandler, - FileArchGenerateHandler, + FileFAHandler, ]) export class FrontendCodeHandler implements BuildHandler { readonly logger: Logger = new Logger('FrontendCodeHandler'); @@ -45,12 +45,12 @@ export class FrontendCodeHandler implements BuildHandler { this.logger.log('Generating Frontend Code...'); // 1. Retrieve the necessary input from context - const sitemapStruct = context.getNodeData(UXSitemapStructureHandler); - const uxDataMapDoc = context.getNodeData(UXDatamapHandler); + const sitemapStruct = context.getNodeData(UXSMSHandler); + const uxDataMapDoc = context.getNodeData(UXDMDHandler); const backendRequirementDoc = context.getNodeData( BackendRequirementHandler, ); - const fileArchDoc = context.getNodeData(FileArchGenerateHandler); + const fileArchDoc = context.getNodeData(FileFAHandler); // 2. Grab any globally stored context as needed this.virtualDir = context.virtualDirectory; diff --git a/backend/src/build-system/handlers/ux/datamap/index.ts b/backend/src/build-system/handlers/ux/datamap/index.ts index a356ebc5..178ddd56 100644 --- a/backend/src/build-system/handlers/ux/datamap/index.ts +++ b/backend/src/build-system/handlers/ux/datamap/index.ts @@ -16,7 +16,7 @@ import { UXSMDHandler } from '../sitemap-document'; */ @BuildNode() @BuildNodeRequire([UXSMDHandler]) -export class UXDatamapHandler implements BuildHandler { +export class UXDMDHandler implements BuildHandler { private readonly logger = new Logger('UXDatamapHandler'); async run(context: BuilderContext): Promise> { @@ -51,7 +51,7 @@ export class UXDatamapHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateUXDataMap', - UXDatamapHandler.name, + UXDMDHandler.name, ); this.logger.log('Successfully generated UX Data Map content.'); diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts index 447473ab..f86cad65 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts @@ -19,7 +19,7 @@ import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; @BuildNode() @BuildNodeRequire([UXSMDHandler]) -export class UXSitemapStructureHandler implements BuildHandler { +export class UXSMSHandler implements BuildHandler { private readonly logger = new Logger('UXSitemapStructureHandler'); async run(context: BuilderContext): Promise> { @@ -91,7 +91,7 @@ export class UXSitemapStructureHandler implements BuildHandler { messages, }, 'generateUXSiteMapStructre', - UXSitemapStructureHandler.name, + UXSMSHandler.name, ); if (!uxStructureContent || uxStructureContent.trim() === '') { diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts index d8019857..45224e49 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/sms-page.ts @@ -9,13 +9,11 @@ import { } from 'src/build-system/errors'; import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; import { UXSMDHandler } from '../sitemap-document'; -import { UXSitemapStructureHandler } from '.'; +import { UXSMSHandler } from '.'; @BuildNode() -@BuildNodeRequire([UXSMDHandler, UXSitemapStructureHandler]) -export class UXSitemapStructurePagebyPageHandler - implements BuildHandler -{ +@BuildNodeRequire([UXSMDHandler, UXSMSHandler]) +export class UXSMSPageByPageHandler implements BuildHandler { readonly logger = new Logger('UXSitemapStructurePagebyPageHandler'); async run(context: BuilderContext): Promise> { @@ -24,7 +22,7 @@ export class UXSitemapStructurePagebyPageHandler const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; const sitemapDoc = context.getNodeData(UXSMDHandler); - const uxStructureDoc = context.getNodeData(UXSitemapStructureHandler); + const uxStructureDoc = context.getNodeData(UXSMSHandler); // Validate required data if (!projectName || typeof projectName !== 'string') { @@ -94,7 +92,7 @@ export class UXSitemapStructurePagebyPageHandler const refinedGlobalCompSections = await batchChatSyncWithClock( context, 'generate global components', - UXSitemapStructurePagebyPageHandler.name, + UXSMSPageByPageHandler.name, requests, ); refinedSections.push(refinedGlobalCompSections); @@ -151,7 +149,7 @@ export class UXSitemapStructurePagebyPageHandler const refinedPageViewSections = await batchChatSyncWithClock( context, 'generate page by page details', - UXSitemapStructurePagebyPageHandler.name, + UXSMSPageByPageHandler.name, page_view_requests, ); refinedSections.push(refinedPageViewSections); diff --git a/backend/src/build-system/utils/__test__/build-utils.spec.ts b/backend/src/build-system/utils/__test__/build-utils.spec.ts index 8e8c21c4..5923d1e1 100644 --- a/backend/src/build-system/utils/__test__/build-utils.spec.ts +++ b/backend/src/build-system/utils/__test__/build-utils.spec.ts @@ -3,13 +3,13 @@ import { Logger } from '@nestjs/common'; import { ProjectInitHandler } from 'src/build-system/handlers/project-init'; import { PRDHandler } from 'src/build-system/handlers/product-manager/product-requirements-document/prd'; import { UXSMDHandler } from 'src/build-system/handlers/ux/sitemap-document'; -import { UXSitemapStructureHandler } from 'src/build-system/handlers/ux/sitemap-structure'; -import { UXDatamapHandler } from 'src/build-system/handlers/ux/datamap'; -import { DatabaseRequirementHandler } from 'src/build-system/handlers/database/requirements-document'; +import { UXSMSHandler } from 'src/build-system/handlers/ux/sitemap-structure'; +import { UXDMDHandler } from 'src/build-system/handlers/ux/datamap'; +import { DBRequirementHandler } from 'src/build-system/handlers/database/requirements-document'; import { FileStructureHandler } from 'src/build-system/handlers/file-manager/file-structure'; -import { UXSitemapStructurePagebyPageHandler } from 'src/build-system/handlers/ux/sitemap-structure/sms-page'; +import { UXSMSPageByPageHandler } from 'src/build-system/handlers/ux/sitemap-structure/sms-page'; import { DBSchemaHandler } from 'src/build-system/handlers/database/schemas/schemas'; -import { FileArchGenerateHandler } from 'src/build-system/handlers/file-manager/file-arch'; +import { FileFAHandler } from 'src/build-system/handlers/file-manager/file-arch'; import { BackendRequirementHandler } from 'src/build-system/handlers/backend/requirements-document'; import { BackendCodeHandler } from 'src/build-system/handlers/backend/code-generate'; import { BackendFileReviewHandler } from 'src/build-system/handlers/backend/file-review/file-review'; @@ -39,15 +39,15 @@ describe('Build Sequence Test', () => { name: 'UX Sitemap Document Node', }, { - handler: UXSitemapStructureHandler, + handler: UXSMSHandler, name: 'UX Sitemap Structure Node', }, { - handler: UXDatamapHandler, + handler: UXDMDHandler, name: 'UX DataMap Document Node', }, { - handler: DatabaseRequirementHandler, + handler: DBRequirementHandler, name: 'Database Requirements Node', }, { @@ -58,7 +58,7 @@ describe('Build Sequence Test', () => { }, }, { - handler: UXSitemapStructurePagebyPageHandler, + handler: UXSMSPageByPageHandler, name: 'Level 2 UX Sitemap Structure Node details', }, { @@ -66,7 +66,7 @@ describe('Build Sequence Test', () => { name: 'Database Schemas Node', }, { - handler: FileArchGenerateHandler, + handler: FileFAHandler, name: 'File Arch', }, { @@ -132,15 +132,15 @@ describe('sortBuildSequence Tests', () => { }, { - handler: UXSitemapStructureHandler, + handler: UXSMSHandler, name: 'UX Sitemap Structure Node', }, { - handler: UXDatamapHandler, + handler: UXDMDHandler, name: 'UX DataMap Document Node', }, { - handler: UXSitemapStructurePagebyPageHandler, + handler: UXSMSPageByPageHandler, name: 'Level 2 UX Sitemap Structure Node details', }, ],