-
Notifications
You must be signed in to change notification settings - Fork 3
feat(backend): adding backend code generator tuning #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3e6e8ab
30d1fba
e3e09e7
1a6d05f
8c11e9c
9ed8053
744530d
799a093
10c4a3f
65b16f4
7bedb65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/* eslint-disable no-console */ | ||
import { BuilderContext } from 'src/build-system/context'; | ||
import { BuildResult, BuildSequence } from '../types'; | ||
import { BuildSequenceExecutor } from '../executor'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
|
||
describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGenerator', () => { | ||
// Generate a unique folder with a timestamp | ||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); | ||
const logFolderPath = `./logs/backend_code_generator-${timestamp}`; | ||
fs.mkdirSync(logFolderPath, { recursive: true }); | ||
|
||
/** | ||
* Utility function to extract content within <GENERATE> tags and write to .md files. | ||
* @param handlerName - The name of the handler/node. | ||
* @param data - The data returned by the handler/node. | ||
*/ | ||
const writeMarkdownToFile = (handlerName: string, data: BuildResult) => { | ||
try { | ||
// Extract "data" field and ensure it's a string | ||
const content: string = data?.data; | ||
if (typeof content !== 'string') { | ||
throw new Error(`Invalid data format for handler: ${handlerName}`); | ||
} | ||
|
||
const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_'); | ||
const filePath = path.join(logFolderPath, `${sanitizedHandlerName}.md`); | ||
fs.writeFileSync(filePath, content, 'utf8'); | ||
console.log(`Logged ${handlerName} result data to ${filePath}`); | ||
} catch (error) { | ||
console.error(`Failed to write markdown for ${handlerName}:`, error); | ||
throw error; | ||
} | ||
}; | ||
|
||
it('should execute the backend code generation sequence and log results to individual files', async () => { | ||
// Define the build sequence up to Backend Code Generator | ||
const sequence: BuildSequence = { | ||
id: 'test-backend-sequence', | ||
version: '1.0.0', | ||
name: 'Test PRD to Backend Code Generation Sequence', | ||
description: | ||
'Testing sequence execution from PRD to Backend Code Generation', | ||
steps: [ | ||
{ | ||
id: 'step-1', | ||
name: 'Generate PRD', | ||
nodes: [ | ||
{ | ||
id: 'op:PRD::STATE:GENERATE', | ||
name: 'PRD Generation Node', | ||
type: 'ANALYSIS', | ||
subType: 'PRD', | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 'step-2', | ||
name: 'Generate UX Sitemap Document', | ||
nodes: [ | ||
{ | ||
id: 'op:UXSMD::STATE:GENERATE', | ||
name: 'UX Sitemap Document Node', | ||
type: 'UX', | ||
subType: 'SITEMAP', | ||
requires: ['op:PRD::STATE:GENERATE'], | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 'step-3', | ||
name: 'Generate UX Data Map Document', | ||
nodes: [ | ||
{ | ||
id: 'op:UX_DATAMAP::STATE:GENERATE', | ||
name: 'UX Data Map Document Node', | ||
type: 'UX', | ||
subType: 'DATAMAP', | ||
requires: ['op:UXSMD::STATE:GENERATE'], | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 'step-4', | ||
name: 'Generate Database Requirements', | ||
nodes: [ | ||
{ | ||
id: 'op:DATABASE_REQ::STATE:GENERATE', | ||
name: 'Database Requirements Node', | ||
type: 'DATABASE', | ||
subType: 'SCHEMAS', | ||
requires: ['op:UX_DATAMAP::STATE:GENERATE'], | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 'step-5', | ||
name: 'Generate Database Schemas', | ||
nodes: [ | ||
{ | ||
id: 'op:DATABASE:SCHEMAS', | ||
name: 'Database Schemas Node', | ||
type: 'DATABASE', | ||
subType: 'SCHEMAS', | ||
requires: ['op:DATABASE_REQ::STATE:GENERATE'], | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 'step-6', | ||
name: 'Generate Backend Code', | ||
nodes: [ | ||
{ | ||
id: 'op:BACKEND_CODE::STATE:GENERATE', | ||
name: 'Backend Code Generator Node', | ||
type: 'BACKEND', | ||
requires: [ | ||
'op:DATABASE:SCHEMAS', | ||
'op:UX_DATAMAP::STATE:GENERATE', | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
// Initialize the BuilderContext with the defined sequence and environment | ||
const context = new BuilderContext(sequence, 'test-env'); | ||
|
||
// Set input data for context | ||
context.setData('projectName', 'Spotify-like Music Web'); | ||
context.setData('description', 'Users can play music'); | ||
context.setData('platform', 'web'); | ||
context.setData('databaseType', 'SQLite'); // Can be 'PostgreSQL', 'MongoDB', etc., based on your needs | ||
|
||
try { | ||
// Execute the build sequence | ||
await BuildSequenceExecutor.executeSequence(sequence, context); | ||
|
||
// 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.getResult(node.id); | ||
console.log(`Result for ${node.name}:`, resultData); | ||
|
||
if (resultData && resultData.success) { | ||
writeMarkdownToFile(node.name, resultData); | ||
} else if (resultData && !resultData.success) { | ||
console.error( | ||
`Handler ${node.name} failed with error:`, | ||
resultData.error, | ||
); | ||
// Optionally, you can log this to a separate file or handle it as needed | ||
} | ||
} | ||
} | ||
|
||
console.log( | ||
'Sequence executed successfully. Logs stored in:', | ||
logFolderPath, | ||
); | ||
} catch (error) { | ||
console.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.'); | ||
} | ||
}, 600000); // Timeout set to 10 minutes | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { BuildHandler, BuildResult } from 'src/build-system/types'; | ||
import { BuilderContext } from 'src/build-system/context'; | ||
import { generateBackendCodePrompt } from './prompt'; | ||
import { Logger } from '@nestjs/common'; | ||
import { | ||
parseGenerateTag, | ||
removeCodeBlockFences, | ||
} from 'src/build-system/utils/database-utils'; | ||
|
||
/** | ||
* Defines the expected order and types of arguments for BackendCodeHandler. | ||
* | ||
* @param sitemapDoc - The sitemap documentation as a string. | ||
* @param DatamapDoc - The data analysis document as a string. | ||
Comment on lines
+13
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use consistent camelCase naming for 'datamapDoc' The parameter 'DatamapDoc' should be renamed to 'datamapDoc' to follow camelCase naming conventions and maintain consistency throughout the code. Apply this diff to rename the parameter and its usages: /**
- * @param DatamapDoc - The data analysis document as a string.
+ * @param datamapDoc - The data analysis document as a string.
* @param currentFile - (Optional) The name of the current file. Defaults to 'backend.js'.
* @param dependencyFile - (Optional) The name of the dependency file. Defaults to 'dependencies.json'.
*/
-type BackendCodeArgs = [
- sitemapDoc: string,
- DatamapDoc: string,
+type BackendCodeArgs = [
+ sitemapDoc: string,
+ datamapDoc: string,
currentFile?: string,
dependencyFile?: string,
];
// Destructure arguments with default values for optional parameters
const [
sitemapDoc,
- DatamapDoc,
+ datamapDoc,
currentFile = 'backend.js',
dependencyFile = 'dependencies.json',
] = args as BackendCodeArgs;
// Generate the prompt using the provided documents and project name
const backendCodePrompt = generateBackendCodePrompt(
projectName,
sitemapDoc,
- DatamapDoc,
+ datamapDoc,
currentFile,
dependencyFile,
); Also applies to: 18-23, 55-60, 70-70 |
||
* @param currentFile - (Optional) The name of the current file. Defaults to 'backend.js'. | ||
* @param dependencyFile - (Optional) The name of the dependency file. Defaults to 'dependencies.json'. | ||
*/ | ||
type BackendCodeArgs = [ | ||
sitemapDoc: string, | ||
DatamapDoc: string, | ||
currentFile?: string, | ||
dependencyFile?: string, | ||
]; | ||
|
||
/** | ||
* 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::STATE:GENERATE'; | ||
readonly logger: Logger = new Logger('BackendCodeHandler'); | ||
|
||
/** | ||
* Executes the handler to generate backend code. | ||
* @param context - The builder context containing configuration and utilities. | ||
* @param args - The variadic arguments required for generating the backend code. | ||
* @returns A BuildResult containing the generated code and related data. | ||
*/ | ||
async run(context: BuilderContext, ...args: any[]): Promise<BuildResult> { | ||
this.logger.log('Generating Backend Codebase...'); | ||
|
||
// Retrieve projectName from context | ||
const projectName = | ||
context.getData('projectName') || 'Default Project Name'; | ||
this.logger.debug(`Project Name: ${projectName}`); | ||
const databaseType = | ||
context.getData('databaseType') || 'Default database type'; | ||
this.logger.debug(`Database Type: ${databaseType}`); | ||
|
||
// Validate and extract args | ||
if (!args || !Array.isArray(args)) { | ||
throw new Error( | ||
'Backend code generation requires specific configuration arguments as an array.', | ||
); | ||
} | ||
|
||
// Destructure arguments with default values for optional parameters | ||
const [ | ||
sitemapDoc, | ||
DatamapDoc, | ||
currentFile = 'backend.js', | ||
dependencyFile = 'dependencies.json', | ||
] = args as BackendCodeArgs; | ||
Comment on lines
+58
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate required arguments before proceeding Ensure that the required arguments Apply this diff to add validation: + // Validate required arguments
+ if (!sitemapDoc || !datamapDoc) {
+ throw new Error('sitemapDoc and datamapDoc are required arguments.');
+ }
|
||
|
||
this.logger.debug( | ||
'Sitemap Documentation and Data Analysis Document are provided.', | ||
); | ||
|
||
// Generate the prompt using the provided documents and project name | ||
const backendCodePrompt = generateBackendCodePrompt( | ||
projectName, | ||
sitemapDoc, | ||
DatamapDoc, | ||
databaseType, | ||
currentFile, | ||
dependencyFile, | ||
); | ||
|
||
// Log the prompt generation | ||
this.logger.debug('Generated backend code prompt.'); | ||
|
||
try { | ||
// Invoke the language model to generate the backend code | ||
const modelResponse = await context.model.chatSync( | ||
{ | ||
content: backendCodePrompt, | ||
}, | ||
'gpt-4o-mini', // Specify the model variant as needed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make model name configurable The model name 'gpt-4o-mini' is hardcoded. Consider making it configurable through the context or environment variables. - 'gpt-4o-mini', // Specify the model variant as needed
+ context.getData('modelName') || 'gpt-4o-mini', // Use configured model or fallback
|
||
); | ||
|
||
const generatedCode = removeCodeBlockFences( | ||
parseGenerateTag(modelResponse), | ||
); | ||
|
||
// Optionally, you can process or validate the generated code here | ||
this.logger.debug('Backend code generated and parsed successfully.'); | ||
|
||
return { | ||
success: true, | ||
data: generatedCode, | ||
}; | ||
} catch (error) { | ||
this.logger.error('Error during backend code generation:', error); | ||
return { | ||
success: false, | ||
error: new Error('Failed to generate backend code.'), | ||
}; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Validation is indeed missing in critical sequence steps
The codebase inspection reveals that validation is currently minimal:
DBSchemaHandler
lacks schema validation mechanismsBackendCodeHandler
only has a comment suggesting optional validationKey locations requiring validation:
backend/src/build-system/handlers/database/schemas/schemas.ts
: Add schema validation for generated database schemasbackend/src/build-system/handlers/backend/code-generate/index.ts
: Implement validation for generated backend code🔗 Analysis chain
Consider adding validation for critical sequence steps.
While the sequence is well-structured, consider adding validation for critical steps, especially for database schema generation and backend code generation steps, to ensure the generated artifacts meet the expected format and structure.
Let me analyze the broader context of handler implementations and validation mechanisms in the codebase to provide a more accurate assessment of the review comment's concern about validation.
Let me analyze the actual implementations of these critical handlers to determine if the review comment's concern about validation is already addressed or if it's indeed missing.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 1103
Script:
Length of output: 1896
Script:
Length of output: 1943