diff --git a/src/functions/prompts-to-test-spec/src/services/__tests__/processor-service.test.ts b/src/functions/prompts-to-test-spec/src/services/__tests__/processor-service.test.ts index 831acc5..370865b 100644 --- a/src/functions/prompts-to-test-spec/src/services/__tests__/processor-service.test.ts +++ b/src/functions/prompts-to-test-spec/src/services/__tests__/processor-service.test.ts @@ -2,12 +2,16 @@ import * as fs from 'fs'; import * as path from 'path'; import { ProcessorService } from '../processor-service'; import { ProjectService } from '../project-service'; -import { RepositoryService } from '../repository-service'; import { Project, Workitem, ProcessResult } from '../../types'; +import { + RepositoryService as SharedRepositoryService, + PullRequestService as SharedPullRequestService, + GeminiService +} from 'shared-functions'; // Mock dependencies jest.mock('../project-service'); -jest.mock('../repository-service'); +jest.mock('shared-functions'); jest.mock('../../config', () => ({ validateConfig: jest.fn(), getMainRepoCredentials: jest.fn().mockReturnValue({ type: 'token', token: 'mock-token' }), @@ -18,19 +22,24 @@ jest.mock('../../config', () => ({ GOOGLE_CLOUD_LOCATION: 'mock-location', GEMINI_MODEL: 'mock-model', USE_LOCAL_REPO: false, - DRY_RUN_SKIP_COMMITS: false + DRY_RUN_SKIP_COMMITS: false, + DRY_RUN_SKIP_GEMINI: false })); describe('ProcessorService', () => { let processorService: ProcessorService; let mockProjectService: jest.Mocked; - let mockRepositoryService: jest.Mocked; + let mockSharedRepositoryService: jest.Mocked; + let mockSharedPullRequestService: jest.Mocked; + let mockGeminiService: jest.Mocked; beforeEach(() => { jest.clearAllMocks(); processorService = new ProcessorService(); mockProjectService = ProjectService.prototype as jest.Mocked; - mockRepositoryService = RepositoryService.prototype as jest.Mocked; + mockSharedRepositoryService = SharedRepositoryService.prototype as jest.Mocked; + mockSharedPullRequestService = SharedPullRequestService.prototype as jest.Mocked; + mockGeminiService = GeminiService.prototype as jest.Mocked; }); describe('updateWorkitemFilesWithPullRequestUrls', () => { @@ -83,7 +92,7 @@ describe('ProcessorService', () => { await (processorService as any).updateWorkitemFilesWithPullRequestUrls(results, mainRepoPath); // Verify the method calls - expect(mockRepositoryService.createBranch).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.createBranch).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/update-workitem-pr-urls-\d{4}-\d{2}-\d{2}/) ); @@ -98,12 +107,12 @@ describe('ProcessorService', () => { 'https://github.com/org/test-project/pull/123' ); - expect(mockRepositoryService.commitChanges).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.commitChanges).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/Update workitem files with pull request URLs: \d{4}-\d{2}-\d{2}/) ); - expect(mockRepositoryService.pushChanges).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.pushChanges).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/update-workitem-pr-urls-\d{4}-\d{2}-\d{2}/), expect.anything() @@ -158,7 +167,7 @@ describe('ProcessorService', () => { await (processorService as any).updateWorkitemFilesWithPullRequestUrls(results, mainRepoPath); // Verify the method calls - expect(mockRepositoryService.createBranch).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.createBranch).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/update-workitem-pr-urls-\d{4}-\d{2}-\d{2}/) ); @@ -174,12 +183,12 @@ describe('ProcessorService', () => { 'https://github.com/org/test-project/pull/123' ); - expect(mockRepositoryService.commitChanges).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.commitChanges).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/Update workitem files with pull request URLs: \d{4}-\d{2}-\d{2}/) ); - expect(mockRepositoryService.pushChanges).toHaveBeenCalledWith( + expect(mockSharedRepositoryService.pushChanges).toHaveBeenCalledWith( mainRepoPath, expect.stringMatching(/update-workitem-pr-urls-\d{4}-\d{2}-\d{2}/), expect.anything() @@ -208,10 +217,10 @@ describe('ProcessorService', () => { await (processorService as any).updateWorkitemFilesWithPullRequestUrls(results, mainRepoPath); // Verify the method calls - expect(mockRepositoryService.createBranch).toHaveBeenCalled(); + expect(mockSharedRepositoryService.createBranch).toHaveBeenCalled(); expect(mockProjectService.updateWorkitemWithPullRequestUrl).not.toHaveBeenCalled(); - expect(mockRepositoryService.commitChanges).not.toHaveBeenCalled(); - expect(mockRepositoryService.pushChanges).not.toHaveBeenCalled(); + expect(mockSharedRepositoryService.commitChanges).not.toHaveBeenCalled(); + expect(mockSharedRepositoryService.pushChanges).not.toHaveBeenCalled(); }); it('should handle errors when updating workitem files', async () => { @@ -251,10 +260,10 @@ describe('ProcessorService', () => { await (processorService as any).updateWorkitemFilesWithPullRequestUrls(results, mainRepoPath); // Verify the method calls - expect(mockRepositoryService.createBranch).toHaveBeenCalled(); + expect(mockSharedRepositoryService.createBranch).toHaveBeenCalled(); expect(mockProjectService.updateWorkitemWithPullRequestUrl).toHaveBeenCalled(); - expect(mockRepositoryService.commitChanges).not.toHaveBeenCalled(); - expect(mockRepositoryService.pushChanges).not.toHaveBeenCalled(); + expect(mockSharedRepositoryService.commitChanges).not.toHaveBeenCalled(); + expect(mockSharedRepositoryService.pushChanges).not.toHaveBeenCalled(); }); }); }); diff --git a/src/functions/prompts-to-test-spec/src/services/__tests__/project-service-log.test.ts b/src/functions/prompts-to-test-spec/src/services/__tests__/project-service-log.test.ts index a6d801e..695461b 100644 --- a/src/functions/prompts-to-test-spec/src/services/__tests__/project-service-log.test.ts +++ b/src/functions/prompts-to-test-spec/src/services/__tests__/project-service-log.test.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { ProjectService } from '../project-service'; +import { WorkitemImplementationStatus } from '../../types'; // Mock fs and path modules jest.mock('fs'); @@ -52,8 +53,9 @@ Some existing log content. isActive: true }; - const status = 'created'; - const files = ['file1.ts', 'file2.ts']; + const status: WorkitemImplementationStatus = 'create'; + const filesWritten = ['file1.ts', 'file2.ts']; + const filesRemoved: string[] = []; // Mock fs.existsSync to return true for workitem file (fs.existsSync as jest.Mock).mockReturnValue(true); @@ -67,7 +69,7 @@ Some existing log content. actualContent = content; }); - await projectService.updateWorkitemWithImplementationLog(workitem, status, files); + await projectService.updateWorkitemWithImplementationLog(workitem, status, filesWritten, filesRemoved); // Verify that fs.existsSync and fs.readFileSync were called with the expected arguments expect(fs.existsSync).toHaveBeenCalledWith('path/to/workitem.md'); @@ -94,9 +96,9 @@ This is a description of the workitem. ### Log -${mockTimestamp} - Workitem has been implemented. Created files: -- file1.ts -- file2.ts +${mockTimestamp} - Workitem has been implemented. +- Created file1.ts +- Created file2.ts Some existing log content. @@ -125,8 +127,9 @@ This is a description of the workitem. isActive: true }; - const status = 'updated'; - const files = ['file1.ts', 'file2.ts']; + const status: WorkitemImplementationStatus = 'update'; + const filesWritten = ['file1.ts', 'file2.ts']; + const filesRemoved: string[] = []; // Mock fs.existsSync to return true for workitem file (fs.existsSync as jest.Mock).mockReturnValue(true); @@ -140,7 +143,7 @@ This is a description of the workitem. actualContent = content; }); - await projectService.updateWorkitemWithImplementationLog(workitem, status, files); + await projectService.updateWorkitemWithImplementationLog(workitem, status, filesWritten, filesRemoved); // Verify that fs.existsSync and fs.readFileSync were called with the expected arguments expect(fs.existsSync).toHaveBeenCalledWith('path/to/workitem.md'); @@ -166,12 +169,11 @@ This is a description of the workitem. - [x] Active - ### Log -${mockTimestamp} - Workitem has been updated. Modified files: -- file1.ts -- file2.ts +${mockTimestamp} - Workitem has been updated. +- Created file1.ts +- Created file2.ts `; expect(actualContentFromMock).toEqual(expectedContent); }); @@ -200,8 +202,9 @@ Some existing log content. isActive: true }; - const status = 'deleted'; - const files = ['file1.ts', 'file2.ts']; + const status: WorkitemImplementationStatus = 'delete'; + const filesWritten: string[] = []; + const filesRemoved = ['file1.ts', 'file2.ts']; // Mock fs.existsSync to return true for workitem file (fs.existsSync as jest.Mock).mockReturnValue(true); @@ -215,7 +218,7 @@ Some existing log content. actualContent = content; }); - await projectService.updateWorkitemWithImplementationLog(workitem, status, files); + await projectService.updateWorkitemWithImplementationLog(workitem, status, filesWritten, filesRemoved); // Verify that fs.existsSync and fs.readFileSync were called with the expected arguments expect(fs.existsSync).toHaveBeenCalledWith('path/to/workitem.md'); @@ -242,9 +245,9 @@ This is a description of the workitem. ### Log -${mockTimestamp} - Workitem has been deleted. Removed files: -- file1.ts -- file2.ts +${mockTimestamp} - Workitem has been deleted. +- Removed file1.ts +- Removed file2.ts Some existing log content. @@ -276,8 +279,9 @@ Some existing log content. isActive: true }; - const status = 'created'; - const files: string[] = []; + const status: WorkitemImplementationStatus = 'create'; + const filesWritten: string[] = []; + const filesRemoved: string[] = []; // Mock fs.existsSync to return true for workitem file (fs.existsSync as jest.Mock).mockReturnValue(true); @@ -291,7 +295,7 @@ Some existing log content. actualContent = content; }); - await projectService.updateWorkitemWithImplementationLog(workitem, status, files); + await projectService.updateWorkitemWithImplementationLog(workitem, status, filesWritten, filesRemoved); // Verify that fs.existsSync and fs.readFileSync were called with the expected arguments expect(fs.existsSync).toHaveBeenCalledWith('path/to/workitem.md'); @@ -318,8 +322,7 @@ This is a description of the workitem. ### Log -${mockTimestamp} - Workitem has been implemented. Created files: -No files were affected. +${mockTimestamp} - Workitem has been implemented. Some existing log content. @@ -338,13 +341,14 @@ Some existing log content. isActive: true }; - const status = 'created'; - const files = ['file1.ts', 'file2.ts']; + const status: WorkitemImplementationStatus = 'create'; + const filesWritten = ['file1.ts', 'file2.ts']; + const filesRemoved: string[] = []; // Mock fs.existsSync to return false for workitem file (fs.existsSync as jest.Mock).mockReturnValue(false); - await expect(projectService.updateWorkitemWithImplementationLog(workitem, status, files)) + await expect(projectService.updateWorkitemWithImplementationLog(workitem, status, filesWritten, filesRemoved)) .rejects.toThrow('Workitem file not found: path/to/workitem.md'); expect(fs.existsSync).toHaveBeenCalledWith('path/to/workitem.md'); diff --git a/src/functions/prompts-to-test-spec/src/services/processor-service.ts b/src/functions/prompts-to-test-spec/src/services/processor-service.ts index 2263e6c..6eba094 100644 --- a/src/functions/prompts-to-test-spec/src/services/processor-service.ts +++ b/src/functions/prompts-to-test-spec/src/services/processor-service.ts @@ -2,11 +2,15 @@ * Service for orchestrating the entire process */ import * as path from 'path'; +import * as os from 'os'; import {ProcessResult, Project, RepoCredentials} from '../types'; -import {RepositoryService} from './repository-service'; +import { + RepositoryService as SharedRepositoryService, + PullRequestService as SharedPullRequestService, + GeminiService +} from 'shared-functions'; import {ProjectService} from './project-service'; -import {PullRequestService} from './pull-request-service'; -import {GeminiProjectProcessor} from './gemini-project-processor'; +import {ProjectWorkitemsService} from './project-workitems-service'; import { DRY_RUN_SKIP_COMMITS, getGiteaCredentials, @@ -14,13 +18,18 @@ import { getMainRepoCredentials, MAIN_REPO_URL, USE_LOCAL_REPO, - validateConfig + validateConfig, + GOOGLE_CLOUD_PROJECT_ID, + GOOGLE_CLOUD_LOCATION, + GEMINI_MODEL, + DRY_RUN_SKIP_GEMINI } from '../config'; export class ProcessorService { - private repositoryService: RepositoryService; + private sharedRepositoryService: SharedRepositoryService; private projectService: ProjectService; - private pullRequestService: PullRequestService; + private sharedPullRequestService: SharedPullRequestService; + private geminiService: GeminiService; private mainRepoUrl: string; private mainRepoCredentials: RepoCredentials; private giteaCredentials?: RepoCredentials; @@ -31,9 +40,16 @@ export class ProcessorService { validateConfig(); // Initialize services - this.repositoryService = new RepositoryService(); + const repoBaseDir = path.join(os.tmpdir(), 'prompts-to-test-spec'); + this.sharedRepositoryService = new SharedRepositoryService(repoBaseDir); this.projectService = new ProjectService(); - this.pullRequestService = new PullRequestService(); + this.sharedPullRequestService = new SharedPullRequestService(); + this.geminiService = new GeminiService( + GOOGLE_CLOUD_PROJECT_ID, + GOOGLE_CLOUD_LOCATION, + GEMINI_MODEL, + DRY_RUN_SKIP_GEMINI + ); // Get main repository URL and credentials only if not using local repo if (!USE_LOCAL_REPO) { @@ -98,7 +114,7 @@ export class ProcessorService { console.log(`Resolved local repository path: ${mainRepoPath}`); } else { console.log(`Cloning main repository: ${this.mainRepoUrl}`); - mainRepoPath = await this.repositoryService.cloneMainRepository( + mainRepoPath = await this.sharedRepositoryService.cloneMainRepository( this.mainRepoUrl, this.mainRepoCredentials ); @@ -165,7 +181,7 @@ export class ProcessorService { // Create a new branch for the changes const branchName = `update-workitem-pr-urls-${new Date().toISOString().split('T')[0]}`; - await this.repositoryService.createBranch(mainRepoPath, branchName); + await this.sharedRepositoryService.createBranch(mainRepoPath, branchName); // Update each workitem file with its pull request URL for (const result of results) { @@ -193,13 +209,13 @@ export class ProcessorService { // Commit and push changes if any workitems were updated if (updatedAnyWorkitem) { console.log('Committing changes to workitem files...'); - await this.repositoryService.commitChanges( + await this.sharedRepositoryService.commitChanges( mainRepoPath, `Update workitem files with pull request URLs: ${new Date().toISOString().split('T')[0]}` ); console.log('Pushing changes to main repository...'); - await this.repositoryService.pushChanges(mainRepoPath, branchName, this.mainRepoCredentials); + await this.sharedRepositoryService.pushChanges(mainRepoPath, branchName, this.mainRepoCredentials); console.log('Successfully updated workitem files with pull request URLs'); } else { console.log('No workitem files were updated'); @@ -230,14 +246,14 @@ export class ProcessorService { // Clone the project repository console.log(`Cloning project repository: ${project.repoUrl}`); - const projectRepoPath = await this.repositoryService.cloneProjectRepository(project, credentials); + const projectRepoPath = await this.sharedRepositoryService.cloneProjectRepository(project, credentials); - // Create a GeminiProjectProcessor to handle the project - const geminiProjectProcessor = new GeminiProjectProcessor(); + // Create a ProjectWorkitemsService to handle the project + const projectWorkitemsService = new ProjectWorkitemsService(); - // Let Gemini operate within the project - console.log(`Letting Gemini operate within project: ${project.name}`); - const result = await geminiProjectProcessor.processProject(project, projectRepoPath); + // Process workitems within the project + console.log(`Processing workitems within project: ${project.name}`); + const result = await projectWorkitemsService.processProject(project, projectRepoPath); // If no workitems were processed or there was an error, return early if (result.processedWorkitems.length === 0 || result.error) { @@ -256,24 +272,33 @@ export class ProcessorService { // Create a new branch for changes const branchName = `update-workitems-${new Date().toISOString().split('T')[0]}`; - await this.repositoryService.createBranch(projectRepoPath, branchName); + await this.sharedRepositoryService.createBranch(projectRepoPath, branchName); // Commit changes - await this.repositoryService.commitChanges( + await this.sharedRepositoryService.commitChanges( projectRepoPath, `Update workitems: ${new Date().toISOString().split('T')[0]}` ); // Push changes - await this.repositoryService.pushChanges(projectRepoPath, branchName, credentials); + await this.sharedRepositoryService.pushChanges(projectRepoPath, branchName, credentials); + + // Generate PR description using Gemini + const description = await this.geminiService.generatePullRequestDescription( + result.processedWorkitems, + result.gitPatch + ); + + // Generate PR title + const title = `Update workitems: ${new Date().toISOString().split('T')[0]}`; // Create pull request - const pullRequestUrl = await this.pullRequestService.createPullRequest( + const pullRequestUrl = await this.sharedPullRequestService.createPullRequest( project, branchName, - result.processedWorkitems, credentials, - result.gitPatch + title, + description ); console.log(`Created pull request: ${pullRequestUrl}`); diff --git a/src/functions/prompts-to-test-spec/src/services/project-service.ts b/src/functions/prompts-to-test-spec/src/services/project-service.ts index 5bddeb2..e1126b7 100644 --- a/src/functions/prompts-to-test-spec/src/services/project-service.ts +++ b/src/functions/prompts-to-test-spec/src/services/project-service.ts @@ -4,6 +4,7 @@ import * as fs from 'fs'; import * as path from 'path'; import {ProjectService as SharedProjectService, Project, Workitem} from 'shared-functions'; +import { WorkitemImplementationStatus } from '../types'; export class ProjectService { private sharedProjectService: SharedProjectService; @@ -176,22 +177,23 @@ export class ProjectService { /** * Update workitem file with implementation log * @param workitem Workitem to update - * @param status Status of the workitem (created, updated, deleted) - * @param files Array of files that were created, updated, or deleted + * @param status Status of the workitem implementation (create, update, delete) + * @param filesWritten Array of files that were created or updated + * @param filesRemoved Array of files that were removed * @returns Updated workitem */ async updateWorkitemWithImplementationLog( workitem: Workitem, - status: 'create' | 'update' | 'delete', - filesWritten: string[], - filesRemoved: string[], + status: WorkitemImplementationStatus, + filesWritten: string[] = [], + filesRemoved: string[] = [], ): Promise { if (!fs.existsSync(workitem.path)) { throw new Error(`Workitem file not found: ${workitem.path}`); } // Read the current content - let content = fs.readFileSync(workitem.path, 'utf-8'); + const content = fs.readFileSync(workitem.path, 'utf-8'); const lines = content.split('\n'); // Format the log message @@ -211,11 +213,16 @@ export class ProjectService { } // Add the list of files - for (const file of filesWritten) { - logMessage += `- Created ${file}\n`; + if (filesWritten.length > 0) { + for (const file of filesWritten) { + logMessage += `- Created ${file}\n`; + } } - for (const file of filesRemoved) { - logMessage += `- Removed ${file}\n`; + + if (filesRemoved.length > 0) { + for (const file of filesRemoved) { + logMessage += `- Removed ${file}\n`; + } } // Add PR URL if available @@ -236,9 +243,6 @@ export class ProjectService { nextSectionIndex = lines.length; } - // Get the existing log content - const existingLogContent = lines.slice(logSectionIndex + 1, nextSectionIndex).join('\n'); - // Insert the new log message after the "### Log" line and before any existing content const beforeLog = lines.slice(0, logSectionIndex + 1); const afterLog = lines.slice(nextSectionIndex); @@ -252,8 +256,10 @@ export class ProjectService { fs.writeFileSync(workitem.path, updatedContent, 'utf-8'); } else { // If no Log section is found, append it to the end of the file - console.log(`No "### Log" section found in workitem ${workitem.name}, appending to the end`); - lines.push('\n### Log'); + if (lines[lines.length - 1] !== '') { + lines.push(''); // Add a blank line before the log section if needed + } + lines.push('### Log'); lines.push(''); // Add a blank line after the log title lines.push(logMessage); diff --git a/src/functions/prompts-to-test-spec/src/services/gemini-project-processor.ts b/src/functions/prompts-to-test-spec/src/services/project-workitems-service.ts similarity index 87% rename from src/functions/prompts-to-test-spec/src/services/gemini-project-processor.ts rename to src/functions/prompts-to-test-spec/src/services/project-workitems-service.ts index 6a7c010..ff34b98 100644 --- a/src/functions/prompts-to-test-spec/src/services/gemini-project-processor.ts +++ b/src/functions/prompts-to-test-spec/src/services/project-workitems-service.ts @@ -1,36 +1,42 @@ /** - * Service for handling Gemini operations within a project + * Service for handling workitem operations within a project */ import * as fs from 'fs'; import * as path from 'path'; -import {ProcessResult, Project, Workitem} from '../types'; +import {ProcessResult} from '../types'; import {ProjectService} from './project-service'; -import {RepositoryService} from './repository-service'; import {DRY_RUN_SKIP_GEMINI} from '../config'; -import {GeminiFileSystemService} from 'shared-functions'; +import { + GeminiFileSystemService, + Project, + Workitem, + RepositoryService as SharedRepositoryService +} from 'shared-functions'; -export class GeminiProjectProcessor { +export class ProjectWorkitemsService { private projectService: ProjectService; - private repositoryService: RepositoryService; + private sharedRepositoryService: SharedRepositoryService; constructor() { this.projectService = new ProjectService(); - this.repositoryService = new RepositoryService(); + this.sharedRepositoryService = new SharedRepositoryService( + path.join(require('os').tmpdir(), 'prompts-to-test-spec') + ); } /** - * Process the project using Gemini + * Process the project workitems * @param project Project to process * @param projectRepoPath Path to the project repository * @returns Process result */ async processProject(project: Project, projectRepoPath: string): Promise { - console.log(`GeminiProjectProcessor: Processing project ${project.name}`); + console.log(`ProjectWorkitemsService: Processing project ${project.name}`); try { // Find all workitems in the project const workitems = await this.projectService.findWorkitems(project.path); - console.log(`GeminiProjectProcessor: Found ${workitems.length} workitems in project ${project.name}`); + console.log(`ProjectWorkitemsService: Found ${workitems.length} workitems in project ${project.name}`); // Skip if no workitems found if (workitems.length === 0) { @@ -46,7 +52,7 @@ export class GeminiProjectProcessor { // Process each workitem const processedWorkitems = []; for (const workitem of workitems) { - console.log(`GeminiProjectProcessor: Processing workitem: ${workitem.name}`); + console.log(`ProjectWorkitemsService: Processing workitem: ${workitem.name}`); const result = await this.processWorkitem(project, projectRepoPath, workitem, projectGuidelines); processedWorkitems.push({workitem, ...result}); } @@ -58,7 +64,7 @@ export class GeminiProjectProcessor { if (totalFilesWritten > 0) { try { console.log(`Generating git patch for project ${project.name} with ${totalFilesWritten} files written`); - gitPatch = await this.repositoryService.generateGitPatch(projectRepoPath); + gitPatch = await this.sharedRepositoryService.generateGitPatch(projectRepoPath); } catch (error) { console.error(`Error generating git patch for project ${project.name}:`, error); } @@ -101,7 +107,7 @@ export class GeminiProjectProcessor { }> { try { // Set the current workitem - console.log(`GeminiProjectProcessor: Processing workitem: ${workitem.name} (Active: ${workitem.isActive})`); + console.log(`ProjectWorkitemsService: Processing workitem: ${workitem.name} (Active: ${workitem.isActive})`); // Read workitem content const workitemContent = fs.readFileSync(workitem.path, 'utf-8'); @@ -161,13 +167,13 @@ export class GeminiProjectProcessor { result.filesDeleted ); - console.log(`GeminiProjectProcessor: Updated workitem file with implementation log for ${workitem.name}`); + console.log(`ProjectWorkitemsService: Updated workitem file with implementation log for ${workitem.name}`); } catch (error) { console.error(`Error updating workitem file with implementation log: ${error}`); } } - console.log(`GeminiProjectProcessor: Completed processing workitem: ${workitem.name} (Status: ${decision}, Files written: ${result.filesWritten.length})`); + console.log(`ProjectWorkitemsService: Completed processing workitem: ${workitem.name} (Status: ${decision}, Files written: ${result.filesWritten.length})`); return { success: true, decision, @@ -205,7 +211,7 @@ export class GeminiProjectProcessor { } }); - console.log(`GeminiProjectProcessor: Collected ${Object.keys(relevantFiles).length} relevant files for workitem ${workitem.name}`); + console.log(`ProjectWorkitemsService: Collected ${Object.keys(relevantFiles).length} relevant files for workitem ${workitem.name}`); } catch (error) { console.error(`Error collecting relevant files for workitem ${workitem.name}:`, error); } @@ -284,6 +290,4 @@ export class GeminiProjectProcessor { filesDeleted: result.filesDeleted }; } - - } diff --git a/src/functions/prompts-to-test-spec/src/services/pull-request-service.ts b/src/functions/prompts-to-test-spec/src/services/pull-request-service.ts deleted file mode 100644 index 60eafae..0000000 --- a/src/functions/prompts-to-test-spec/src/services/pull-request-service.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Service for handling pull request operations - */ -import { - PullRequestService as SharedPullRequestService, - Project, - RepoCredentials, - Workitem, - GeminiService -} from 'shared-functions'; -import {GOOGLE_CLOUD_PROJECT_ID, GOOGLE_CLOUD_LOCATION, GEMINI_MODEL, DRY_RUN_SKIP_GEMINI} from '../config'; - -export class PullRequestService { - private sharedPullRequestService: SharedPullRequestService; - private geminiService: GeminiService; - - constructor() { - this.sharedPullRequestService = new SharedPullRequestService(); - this.geminiService = new GeminiService( - GOOGLE_CLOUD_PROJECT_ID, - GOOGLE_CLOUD_LOCATION, - GEMINI_MODEL, - DRY_RUN_SKIP_GEMINI - ); - } - - /** - * Create a pull request for changes in a repository - * @param project Project information - * @param branchName Name of the branch with changes - * @param processedWorkitems List of processed workitems - * @param credentials Repository credentials - * @param gitPatch Optional git patch to include in the description - * @returns URL of the created pull request - */ - async createPullRequest( - project: Project, - branchName: string, - processedWorkitems: { - workitem: Workitem; - success: boolean; - error?: string; - status?: 'skipped' | 'updated' | 'created'; - filesWritten?: string[] - }[], - credentials: RepoCredentials, - gitPatch?: string - ): Promise { - // Generate PR title and description - const title = `Update workitems: ${new Date().toISOString().split('T')[0]}`; - const description = await this.generatePullRequestDescription(processedWorkitems, gitPatch); - - // Use the shared implementation to create the pull request - return this.sharedPullRequestService.createPullRequest(project, branchName, credentials, title, description); - } - - /** - * Generate a description for the pull request using Gemini - * @param processedWorkitems List of processed workitems - * @param gitPatch Optional git patch to include in the description - * @returns Pull request description - */ - private async generatePullRequestDescription( - processedWorkitems: { - workitem: Workitem; - success: boolean; - error?: string; - status?: 'skipped' | 'updated' | 'created'; - filesWritten?: string[] - }[], - gitPatch?: string - ): Promise { - // 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); - } -} diff --git a/src/functions/prompts-to-test-spec/src/services/repository-service.ts b/src/functions/prompts-to-test-spec/src/services/repository-service.ts deleted file mode 100644 index 9e5405b..0000000 --- a/src/functions/prompts-to-test-spec/src/services/repository-service.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Service for handling repository operations - */ -import * as path from 'path'; -import * as os from 'os'; -import {RepositoryService as SharedRepositoryService, Project, RepoCredentials} from 'shared-functions'; - -export class RepositoryService { - private sharedRepositoryService: SharedRepositoryService; - - constructor(baseDir?: string) { - // Use a different base directory for prompts-to-test-spec - const repoBaseDir = baseDir || path.join(os.tmpdir(), 'prompts-to-test-spec'); - this.sharedRepositoryService = new SharedRepositoryService(repoBaseDir); - } - - /** - * Clone the main repository containing prompts - * @param repoUrl URL of the repository - * @param credentials Optional credentials for private repositories - * @returns Path to the cloned repository - */ - async cloneMainRepository(repoUrl: string, credentials?: RepoCredentials): Promise { - return this.sharedRepositoryService.cloneMainRepository(repoUrl, credentials); - } - - /** - * Clone a project repository - * @param project Project information - * @param credentials Optional credentials for private repositories - * @returns Path to the cloned repository - */ - async cloneProjectRepository(project: Project, credentials?: RepoCredentials): Promise { - return this.sharedRepositoryService.cloneProjectRepository(project, credentials); - } - - /** - * Create a new branch in a repository - * @param repoDir Path to the repository - * @param branchName Name of the branch to create - */ - async createBranch(repoDir: string, branchName: string): Promise { - return this.sharedRepositoryService.createBranch(repoDir, branchName); - } - - /** - * Commit changes to a repository - * @param repoDir Path to the repository - * @param message Commit message - */ - async commitChanges(repoDir: string, message: string): Promise { - return this.sharedRepositoryService.commitChanges(repoDir, message); - } - - /** - * Push changes to a repository - * @param repoDir Path to the repository - * @param branchName Name of the branch to push - * @param credentials Optional credentials for private repositories - */ - async pushChanges(repoDir: string, branchName: string, credentials?: RepoCredentials): Promise { - return this.sharedRepositoryService.pushChanges(repoDir, branchName, credentials); - } - - /** - * Generate a git patch of the changes in a repository - * @param repoDir Path to the repository - * @returns Git patch as a string - */ - async generateGitPatch(repoDir: string): Promise { - return this.sharedRepositoryService.generateGitPatch(repoDir); - } - - /** - * Checkout an existing branch in a repository - * @param repoDir Path to the repository - * @param branchName Name of the branch to checkout - */ - async checkoutBranch(repoDir: string, branchName: string): Promise { - return this.sharedRepositoryService.checkoutBranch(repoDir, branchName); - } -} diff --git a/src/functions/prompts-to-test-spec/src/types.ts b/src/functions/prompts-to-test-spec/src/types.ts index 7444e95..13958a9 100644 --- a/src/functions/prompts-to-test-spec/src/types.ts +++ b/src/functions/prompts-to-test-spec/src/types.ts @@ -2,6 +2,11 @@ * Type definitions for the prompts-to-test-spec function */ +/** + * Status of a workitem implementation + */ +export type WorkitemImplementationStatus = 'create' | 'update' | 'delete'; + export interface Project { name: string; path: string; diff --git a/src/functions/shared/src/types.ts b/src/functions/shared/src/types.ts index 00cec2d..35e0e74 100644 --- a/src/functions/shared/src/types.ts +++ b/src/functions/shared/src/types.ts @@ -29,17 +29,3 @@ export interface RepoCredentials { password?: string; token?: string; } - -export interface ProcessResult { - project: Project; - processedWorkitems: { - workitem: Workitem; - success: boolean; - error?: string; - status?: 'skipped' | 'updated' | 'created'; - filesWritten?: string[]; - }[]; - pullRequestUrl?: string; - error?: string; - gitPatch?: string; -}