WIP
This commit is contained in:
parent
b207103030
commit
30a44455e8
@ -20,7 +20,18 @@ GITEA_PASSWORD=your_gitea_password_here
|
||||
# Google Cloud configuration
|
||||
GOOGLE_CLOUD_PROJECT_ID=your_gcp_project_id_here
|
||||
GOOGLE_CLOUD_LOCATION=us-central1
|
||||
GEMINI_MODEL=gemini-1.5-pro
|
||||
GEMINI_MODEL=gemini-2.0-flash-lite-001
|
||||
|
||||
# Google Cloud Authentication
|
||||
# You can authenticate using one of the following methods:
|
||||
# 1. API key (for local development)
|
||||
GOOGLE_API_KEY=your_google_api_key_here
|
||||
# 2. Service account key file (for production)
|
||||
# Set this environment variable to the path of your service account key file
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/service-account-key.json
|
||||
# 3. Application Default Credentials (ADC)
|
||||
# No environment variables needed if using ADC
|
||||
# See: https://cloud.google.com/docs/authentication/application-default-credentials
|
||||
|
||||
# Function configuration
|
||||
# Set to 'true' to enable debug logging
|
||||
|
@ -26,6 +26,7 @@ export const GITEA_PASSWORD = process.env.GITEA_PASSWORD;
|
||||
export const GOOGLE_CLOUD_PROJECT_ID = process.env.GOOGLE_CLOUD_PROJECT_ID || '';
|
||||
export const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
|
||||
export const GEMINI_MODEL = process.env.GEMINI_MODEL || 'gemini-1.5-pro';
|
||||
export const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
|
||||
|
||||
// Function configuration
|
||||
export const DEBUG = process.env.DEBUG === 'true';
|
||||
|
@ -120,33 +120,12 @@ export class GeminiProjectProcessor {
|
||||
// Determine initial status based on workitem activity
|
||||
let status: 'skipped' | 'updated' | 'created' = 'skipped';
|
||||
|
||||
// If workitem is not active, skip processing
|
||||
if (!workitem.isActive) {
|
||||
console.log(`GeminiProjectProcessor: Skipping inactive workitem: ${workitem.name}`);
|
||||
|
||||
// If the feature file exists, it should be deleted
|
||||
const featureFileName = `${workitem.name}.feature`;
|
||||
const featurePath = path.join(this.projectRepoPath, 'nitro-it', 'src', 'test', 'resources', 'workitems', featureFileName);
|
||||
|
||||
if (fs.existsSync(featurePath)) {
|
||||
fs.unlinkSync(featurePath);
|
||||
console.log(`GeminiProjectProcessor: Deleted feature file for inactive workitem: ${featurePath}`);
|
||||
}
|
||||
|
||||
return { success: true, status: 'skipped', filesWritten: [] };
|
||||
}
|
||||
|
||||
// Read workitem content
|
||||
const workitemContent = fs.readFileSync(workitem.path, 'utf-8');
|
||||
|
||||
// Collect all relevant files from the project directory
|
||||
const relevantFiles = await this.collectRelevantFiles(workitem);
|
||||
|
||||
// Check if the feature file already exists to determine if this is an update or creation
|
||||
const featureFileName = `${workitem.name}.feature`;
|
||||
const featurePath = path.join(this.projectRepoPath, 'nitro-it', 'src', 'test', 'resources', 'workitems', featureFileName);
|
||||
status = fs.existsSync(featurePath) ? 'updated' : 'created';
|
||||
|
||||
// Let Gemini decide what to do with the workitem
|
||||
const result = await this.generateFeatureFile(
|
||||
projectGuidelines,
|
||||
@ -155,9 +134,6 @@ export class GeminiProjectProcessor {
|
||||
relevantFiles
|
||||
);
|
||||
|
||||
// Gemini will handle the file operations through function calls
|
||||
// No need to manually create or delete files here
|
||||
|
||||
// Get the list of files written for this workitem
|
||||
const filesWritten = this.filesWritten.get(workitem.name) || [];
|
||||
|
||||
@ -243,14 +219,6 @@ export class GeminiProjectProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing feature file if it exists
|
||||
const featureFileName = `${workitem.name}.feature`;
|
||||
const featurePath = path.join(this.projectRepoPath, 'nitro-it', 'src', 'test', 'resources', 'workitems', featureFileName);
|
||||
|
||||
if (fs.existsSync(featurePath)) {
|
||||
relevantFiles['existing_feature.feature'] = fs.readFileSync(featurePath, 'utf-8');
|
||||
}
|
||||
|
||||
console.log(`GeminiProjectProcessor: Collected ${Object.keys(relevantFiles).length} relevant files for workitem ${workitem.name}`);
|
||||
} catch (error) {
|
||||
console.error(`Error collecting relevant files for workitem ${workitem.name}:`, error);
|
||||
|
@ -9,7 +9,8 @@ import {
|
||||
GOOGLE_CLOUD_PROJECT_ID,
|
||||
GOOGLE_CLOUD_LOCATION,
|
||||
GEMINI_MODEL,
|
||||
DRY_RUN_SKIP_GEMINI
|
||||
DRY_RUN_SKIP_GEMINI,
|
||||
GOOGLE_API_KEY
|
||||
} from '../config';
|
||||
|
||||
export class GeminiService {
|
||||
@ -28,6 +29,12 @@ export class GeminiService {
|
||||
throw new Error('Google Cloud Project ID is required');
|
||||
}
|
||||
|
||||
// Initialize VertexAI with default authentication
|
||||
// Authentication is handled by the Google Auth library, which supports:
|
||||
// 1. API keys via GOOGLE_API_KEY environment variable
|
||||
// 2. Service accounts via GOOGLE_APPLICATION_CREDENTIALS environment variable
|
||||
// 3. Application Default Credentials
|
||||
// See: https://cloud.google.com/vertex-ai/docs/authentication
|
||||
this.vertexAI = new VertexAI({
|
||||
project: this.projectId,
|
||||
location: this.location,
|
||||
@ -134,86 +141,6 @@ export class GeminiService {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply project guidelines to a workitem
|
||||
* @param project Project information
|
||||
* @param workitem Workitem to process
|
||||
* @param projectRepoPath Path to the cloned project repository
|
||||
* @returns Result of the processing
|
||||
*/
|
||||
async processWorkitem(
|
||||
project: Project,
|
||||
workitem: Workitem,
|
||||
projectRepoPath: string
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
// Skip inactive workitems
|
||||
if (!workitem.isActive) {
|
||||
console.log(`Skipping inactive workitem: ${workitem.name}`);
|
||||
|
||||
// If the feature file exists, it should be deleted according to guidelines
|
||||
const featureFileName = `${workitem.name}.feature`;
|
||||
const featurePath = path.join(projectRepoPath, 'nitro-it', 'src', 'test', 'resources', 'workitems', featureFileName);
|
||||
|
||||
if (fs.existsSync(featurePath)) {
|
||||
fs.unlinkSync(featurePath);
|
||||
console.log(`Deleted feature file for inactive workitem: ${featurePath}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Read project guidelines
|
||||
const projectGuidelines = await this.readProjectGuidelines(project.path);
|
||||
|
||||
// Read workitem content
|
||||
const workitemContent = fs.readFileSync(workitem.path, 'utf-8');
|
||||
|
||||
// Generate feature file content using Gemini API
|
||||
const featureContent = await this.generateFeatureFile(
|
||||
projectGuidelines,
|
||||
workitemContent,
|
||||
workitem.name
|
||||
);
|
||||
|
||||
// Ensure the target directory exists
|
||||
const targetDir = path.join(projectRepoPath, 'nitro-it', 'src', 'test', 'resources', 'workitems');
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the feature file
|
||||
const featureFileName = `${workitem.name}.feature`;
|
||||
const featurePath = path.join(targetDir, featureFileName);
|
||||
fs.writeFileSync(featurePath, featureContent);
|
||||
|
||||
console.log(`Created/updated feature file: ${featurePath}`);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(`Error processing workitem ${workitem.name}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read AI guidelines for a project
|
||||
* @param projectPath Path to the project directory
|
||||
* @returns AI guidelines content
|
||||
*/
|
||||
private async readProjectGuidelines(projectPath: string): Promise<string> {
|
||||
const aiPath = path.join(projectPath, 'AI.md');
|
||||
|
||||
if (!fs.existsSync(aiPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return fs.readFileSync(aiPath, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate feature file content using Gemini API
|
||||
* @param guidelines Project guidelines
|
||||
@ -276,8 +203,9 @@ You have access to file operations to help you understand the project structure
|
||||
- grepFiles(searchString, filePattern): Search for a string in project files, optionally filtered by a file pattern
|
||||
- deleteFile(filePath): Delete a file from the project repository
|
||||
|
||||
You can decide whether to create, update, or skip implementing this workitem based on your analysis.
|
||||
You can decide whether to create, update, delete or skip implementing this workitem based on your analysis.
|
||||
Include the decision in your response.
|
||||
|
||||
${additionalContext ? `\nAdditional context from project files:${additionalContext}` : ''}
|
||||
`;
|
||||
|
||||
@ -286,9 +214,12 @@ ${additionalContext ? `\nAdditional context from project files:${additionalConte
|
||||
|
||||
// Send the initial message
|
||||
const result = await chat.sendMessage(prompt);
|
||||
console.log(`Gemini result for ${workitemName}`, JSON.stringify(result, null, 2));
|
||||
|
||||
|
||||
// Process function calls if needed
|
||||
let finalResponse = await this.processFunctionCalls(result, chat, geminiProjectProcessor);
|
||||
console.log(`Gemini response for ${workitemName}: ${finalResponse}`, result.response.candidates[0]);
|
||||
|
||||
return finalResponse;
|
||||
}
|
||||
@ -304,7 +235,8 @@ ${additionalContext ? `\nAdditional context from project files:${additionalConte
|
||||
// Check if there are function calls in the response
|
||||
if (!result.functionCalls || result.functionCalls.length === 0 || !geminiProjectProcessor) {
|
||||
// No function calls, return the text response
|
||||
return result.text();
|
||||
// Access text content from the response structure in @google-cloud/vertexai v0.5.0
|
||||
return result.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
}
|
||||
|
||||
console.log(`Processing ${result.functionCalls.length} function calls from Gemini`);
|
||||
@ -369,57 +301,35 @@ ${additionalContext ? `\nAdditional context from project files:${additionalConte
|
||||
}
|
||||
|
||||
// Return the final text response
|
||||
return result.text();
|
||||
// Access text content from the response structure in @google-cloud/vertexai v0.5.0
|
||||
return result.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pull request description using Gemini API
|
||||
* @param processedWorkitems List of processed workitems
|
||||
* @param gitPatch Optional git patch showing code changes
|
||||
* @returns Generated pull request description
|
||||
*/
|
||||
async generatePullRequestDescription(
|
||||
processedWorkitems: { workitem: Workitem; success: boolean; error?: string }[]
|
||||
processedWorkitems: { workitem: Workitem; success: boolean; error?: string }[],
|
||||
gitPatch?: string
|
||||
): Promise<string> {
|
||||
// Prepare workitem data for the prompt
|
||||
const added: string[] = [];
|
||||
const updated: string[] = [];
|
||||
const deleted: string[] = [];
|
||||
const failed: string[] = [];
|
||||
const all: string[] = [];
|
||||
|
||||
for (const item of processedWorkitems) {
|
||||
const { workitem, success, error } = item;
|
||||
|
||||
if (!success) {
|
||||
failed.push(`- ${workitem.name}: ${error}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!workitem.isActive) {
|
||||
deleted.push(`- ${workitem.name}`);
|
||||
} else if (workitem.implementation) {
|
||||
updated.push(`- ${workitem.name}`);
|
||||
} else {
|
||||
added.push(`- ${workitem.name}`);
|
||||
}
|
||||
// Add all workitems to the all list regardless of status
|
||||
all.push(`- ${workitem.name}`);
|
||||
}
|
||||
|
||||
// Create a structured summary of changes
|
||||
let workitemSummary = '';
|
||||
|
||||
if (added.length > 0) {
|
||||
workitemSummary += 'Added workitems:\n' + added.join('\n') + '\n\n';
|
||||
}
|
||||
|
||||
if (updated.length > 0) {
|
||||
workitemSummary += 'Updated workitems:\n' + updated.join('\n') + '\n\n';
|
||||
}
|
||||
|
||||
if (deleted.length > 0) {
|
||||
workitemSummary += 'Deleted workitems:\n' + deleted.join('\n') + '\n\n';
|
||||
}
|
||||
|
||||
if (failed.length > 0) {
|
||||
workitemSummary += 'Failed workitems:\n' + failed.join('\n') + '\n\n';
|
||||
if (all.length > 0) {
|
||||
workitemSummary += 'All workitems:\n' + all.join('\n') + '\n\n';
|
||||
}
|
||||
|
||||
// If dry run is enabled, return a mock PR description
|
||||
@ -440,17 +350,32 @@ ${workitemSummary}
|
||||
model: this.model,
|
||||
});
|
||||
|
||||
// Prepare the git patch section if available
|
||||
let gitPatchSection = '';
|
||||
if (gitPatch && gitPatch !== "No changes detected.") {
|
||||
gitPatchSection = `
|
||||
## Code Changes
|
||||
The following code changes were made in this pull request:
|
||||
|
||||
\`\`\`diff
|
||||
${gitPatch}
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
const prompt = `
|
||||
You are tasked with creating a pull request description for changes to test specifications.
|
||||
|
||||
The following is a summary of the changes made:
|
||||
${workitemSummary}
|
||||
${gitPatch && gitPatch !== "No changes detected." ? gitPatchSection : ''}
|
||||
|
||||
Create a clear, professional pull request description that:
|
||||
1. Explains that this PR was automatically generated by the prompts-to-test-spec function
|
||||
2. Summarizes the changes (added, updated, deleted, and failed workitems)
|
||||
3. Uses markdown formatting for better readability
|
||||
4. Keeps the description concise but informative
|
||||
3. If code changes are provided, analyze them and include a summary of the key changes
|
||||
4. Uses markdown formatting for better readability
|
||||
5. Keeps the description concise but informative
|
||||
|
||||
The pull request description should be ready to use without further editing.
|
||||
`;
|
||||
|
@ -158,14 +158,8 @@ export class PullRequestService {
|
||||
processedWorkitems: { workitem: Workitem; success: boolean; error?: string; status?: 'skipped' | 'updated' | 'created'; filesWritten?: string[] }[],
|
||||
gitPatch?: string
|
||||
): Promise<string> {
|
||||
// Use Gemini to generate the pull request description
|
||||
const description = await this.geminiService.generatePullRequestDescription(processedWorkitems);
|
||||
|
||||
// If there's a git patch, append it to the description
|
||||
if (gitPatch && gitPatch !== "No changes detected.") {
|
||||
return `${description}\n\n## Git Patch\n\`\`\`diff\n${gitPatch}\n\`\`\``;
|
||||
}
|
||||
|
||||
return description;
|
||||
// Use Gemini to generate the pull request description, passing the git patch
|
||||
// so Gemini can analyze the code changes
|
||||
return await this.geminiService.generatePullRequestDescription(processedWorkitems, gitPatch);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user