activity-simulator / src /core /prWorkflow.js
abedelbahnasy55's picture
feat: cloud simulator - Docker, dashboard, auto-start
ccb6b75
import logger from '../utils/logger.js';
class PRWorkflow {
constructor(githubService, aiProvider, memory, developer, conversationSimulator, reviewEngine) {
this.github = githubService;
this.ai = aiProvider;
this.memory = memory;
this.developer = developer;
this.conversation = conversationSimulator;
this.reviewEngine = reviewEngine;
}
async processPRs() {
try {
const prs = await this.github.listPullRequests('open');
for (const pr of prs) {
await this._processSinglePR(pr);
}
} catch (error) {
logger.error(`Failed to process PRs: ${error.message}`);
}
}
async _processSinglePR(pr) {
const statusData = this.memory.recallWithMetadata(`pr-status-${pr.number}`);
const status = statusData?.value || null;
try {
if (!status) {
await this._handleNewPR(pr);
} else if (status === 'reviewed-approved') {
await this._handleApprovedPR(pr);
} else if (status === 'reviewed-changes') {
await this._handleChangesRequestedPR(pr);
} else if (status === 'updated') {
await this._handleUpdatedPR(pr);
}
} catch (error) {
logger.error(`Failed to process PR #${pr.number}: ${error.message}`);
}
}
async _handleNewPR(pr) {
logger.info(`Handling new PR #${pr.number}: ${pr.title}`);
await this.developer.adaptRoleFromContext(pr.title, []);
const files = await this.github.getPullRequestFiles(pr.number);
const reviews = await this.reviewEngine.reviewPullRequest(pr, files);
if (reviews.length === 0) {
const comment = await this.conversation.generatePRComment(pr, [], files);
await this.github.addPullRequestComment(pr.number, comment);
this.memory.remember(`pr-status-${pr.number}`, 'reviewed-comment', {
tags: ['review', 'pr'],
});
return;
}
const hasBugs = reviews.some(r => r.type === 'bug' || r.type === 'security');
const hasSuggestions = reviews.some(r => r.type === 'suggestion');
if (hasBugs) {
const summary = reviews.filter(r => r.type === 'bug' || r.type === 'security')
.map(r => `**${r.file}**: ${r.review}`).join('\n\n');
await this.github.addPullRequestReview(
pr.number,
'CHANGES_REQUESTED',
`Found some issues that need fixing:\n\n${summary}`,
pr.head?.sha
);
this.memory.remember(`pr-status-${pr.number}`, 'reviewed-changes', {
tags: ['review', 'pr'],
reviews,
});
} else if (hasSuggestions) {
const comment = await this.conversation.generatePRComment(pr, [], files);
await this.github.addPullRequestComment(pr.number, comment);
this.memory.remember(`pr-status-${pr.number}`, 'reviewed-comment', {
tags: ['review', 'pr'],
});
} else {
const comment = await this.conversation.generatePRComment(pr, [], files);
await this.github.addPullRequestReview(pr.number, 'APPROVED', comment, pr.head?.sha);
this.memory.remember(`pr-status-${pr.number}`, 'reviewed-approved', {
tags: ['review', 'pr'],
});
}
}
async _handleApprovedPR(pr) {
if (Math.random() < 0.3) {
await this._mergePR(pr);
}
}
async _handleChangesRequestedPR(pr) {
if (Math.random() < 0.5) {
await this._updatePRWithFixes(pr);
}
}
async _handleUpdatedPR(pr) {
const files = await this.github.getPullRequestFiles(pr.number);
const comment = await this.conversation.generatePRComment(pr, [], files);
const isApproved = Math.random() < 0.7;
if (isApproved) {
await this.github.addPullRequestReview(pr.number, 'APPROVED', comment, pr.head?.sha);
this.memory.remember(`pr-status-${pr.number}`, 'reviewed-approved', {
tags: ['review', 'pr'],
});
} else {
await this.github.addPullRequestComment(pr.number, comment);
}
}
async _mergePR(pr) {
logger.info(`Merging PR #${pr.number}`);
try {
await this.github.mergePullRequest(pr.number);
if (pr.head?.ref) {
await this.github.deleteBranch(pr.head.ref);
}
this.memory.remember(`pr-status-${pr.number}`, 'merged', {
tags: ['merged', 'pr'],
});
logger.info(`Merged PR #${pr.number}`);
} catch (error) {
logger.error(`Failed to merge PR #${pr.number}: ${error.message}`);
}
}
async _updatePRWithFixes(pr) {
logger.info(`Updating PR #${pr.number} with fixes`);
const files = await this.github.getPullRequestFiles(pr.number);
if (files.length === 0) return;
const file = files[Math.floor(Math.random() * files.length)];
const codeContent = await this._generateFixCode(file);
await this.github.createOrUpdateFile(
file.filename,
codeContent,
'fix: address review comments',
pr.head?.ref || 'main'
);
const followUp = await this.conversation.generateFollowUpComment(pr, 'review feedback', ['Fixed the issue']);
await this.github.addPullRequestComment(pr.number, followUp);
this.memory.remember(`pr-status-${pr.number}`, 'updated', {
tags: ['updated', 'pr'],
});
}
async _generateFixCode(file) {
const prompt = `اكتب كود محسّن للملف ${file.filename}.
يجب أن يكون كود أفضل من النسخة السابقة.
اكتب الكود فقط.`;
const code = await this.ai.generate(prompt, {
maxTokens: 1000,
temperature: 0.6,
});
return code.trim();
}
}
export default PRWorkflow;