activity-simulator / src /core /issueTracker.js
abedelbahnasy55's picture
fix: handle repeated prefixes
fff2b6a
import logger from '../utils/logger.js';
class IssueTracker {
constructor(githubService, aiProvider, memory, developer, conversationSimulator) {
this.github = githubService;
this.ai = aiProvider;
this.memory = memory;
this.developer = developer;
this.conversation = conversationSimulator;
}
async processIssues() {
try {
const issues = await this.github.listIssues('open');
for (const issue of issues) {
await this._processSingleIssue(issue);
}
} catch (error) {
logger.error(`Failed to process issues: ${error.message}`);
}
}
async _processSingleIssue(issue) {
const statusData = this.memory.recallWithMetadata(`issue-status-${issue.number}`);
const status = statusData?.value || null;
try {
if (!status) {
await this._handleNewIssue(issue);
} else if (status === 'acknowledged') {
await this._handleAcknowledgedIssue(issue);
} else if (status === 'in-progress') {
await this._handleInProgressIssue(issue);
} else if (status === 'needs-info') {
await this._handleNeedsInfoIssue(issue);
} else if (status === 'done') {
await this._handleDoneIssue(issue);
}
} catch (error) {
logger.error(`Failed to process issue #${issue.number}: ${error.message}`);
}
}
async _handleNewIssue(issue) {
logger.info(`Handling new issue #${issue.number}: ${issue.title}`);
const comment = await this.conversation.generateIssueComment(issue, []);
await this.github.addIssueComment(issue.number, comment);
this.memory.remember(`issue-status-${issue.number}`, 'acknowledged', {
tags: ['issue'],
title: issue.title,
});
}
async _handleAcknowledgedIssue(issue) {
if (Math.random() < 0.6) {
await this._startWorkingOnIssue(issue);
}
}
async _startWorkingOnIssue(issue) {
logger.info(`Starting work on issue #${issue.number}`);
await this.github.addLabels(issue.number, ['in-progress']);
const statusUpdate = await this.conversation.generateStatusUpdate(issue, 'in-progress');
await this.github.addIssueComment(issue.number, statusUpdate);
this.memory.remember(`issue-status-${issue.number}`, 'in-progress', {
tags: ['active-issue'],
title: issue.title,
});
}
async _handleInProgressIssue(issue) {
const cleanTitle = this._cleanTitle(issue.title);
const action = this.developer.getRoleBasedAction();
const branchName = this._generateBranchName(cleanTitle);
try {
await this.github.createBranch(branchName);
} catch (error) {
logger.warn(`Branch ${branchName} exists`);
}
const codeContent = await this._generateCodeForIssue(action, issue);
const fileName = this._generateFileName(action);
await this.github.createOrUpdateFile(fileName, codeContent, `feat: ${issue.title}`, branchName);
const prTitle = `${this._getCommitPrefix(cleanTitle)}: ${cleanTitle}`;
const prBody = await this._generatePRBody(issue, branchName);
const pr = await this.github.createPullRequest(prTitle, prBody, branchName);
this.memory.remember(`issue-status-${issue.number}`, 'done', {
tags: ['done', 'open-pr'],
prNumber: pr.number,
});
this.memory.remember(`pr-for-issue-${issue.number}`, pr.number);
logger.info(`Created PR #${pr.number} for issue #${issue.number}`);
}
async _handleNeedsInfoIssue(issue) {
const clarification = await this.conversation.generateIssueClarification(issue);
await this.github.addIssueComment(issue.number, clarification);
}
async _handleDoneIssue(issue) {
const prNumber = this.memory.recall(`pr-for-issue-${issue.number}`);
if (prNumber) {
try {
const pr = await this.github.getPullRequest(prNumber);
if (pr && pr.merged) {
await this.github.closeIssue(issue.number);
this.memory.remember(`issue-status-${issue.number}`, 'closed');
logger.info(`Closed issue #${issue.number} after PR merged`);
}
} catch (error) {
logger.error(`Failed to check PR status for issue #${issue.number}: ${error.message}`);
}
}
}
async _generateCodeForIssue(action, issue) {
const roleContext = this.developer.getRoleContext();
const prompt = `أنت مطور (${roleContext.role}) تكتب كود لـ Issue.
Issue: ${issue.title}
Action: ${action}
اكتب كود JavaScript/TypeScript حقيقي ومنطقي.
يجب أن يكون كود إنتاجي مع error handling و comments.
اكتب الكود فقط.`;
const code = await this.ai.generate(prompt, {
maxTokens: 1500,
temperature: 0.7,
});
return code.trim();
}
async _generatePRBody(issue, branchName) {
const roleContext = this.developer.getRoleContext();
return `## ${issue.title}
**Closes:** #${issue.number}
### What
Implemented ${issue.title.toLowerCase()}.
### Why
${roleContext.phrases?.[0] || 'Improves the project.'}
### Testing
- [ ] Tests pass
### Notes
Ready for review.`;
}
_generateBranchName(issueTitle) {
const sanitized = issueTitle
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.slice(0, 50);
const prefix = this._getCommitPrefix(issueTitle);
return `${prefix}/${sanitized}`;
}
_getCommitPrefix(title) {
const cleanTitle = title.replace(/^(feat|fix|docs|refactor|test|chore):\s*/i, '').toLowerCase();
const lower = cleanTitle.toLowerCase();
if (lower.includes('add') || lower.includes('implement') || lower.includes('new')) return 'feat';
if (lower.includes('fix') || lower.includes('bug') || lower.includes('error')) return 'fix';
if (lower.includes('refactor') || lower.includes('clean') || lower.includes('improve')) return 'refactor';
return 'chore';
}
_cleanTitle(title) {
let cleaned = title.trim();
while (/^(feat|fix|docs|refactor|test|chore):\s*/i.test(cleaned)) {
cleaned = cleaned.replace(/^(feat|fix|docs|refactor|test|chore):\s*/i, '').trim();
}
return cleaned;
}
_generateFileName(action) {
const ext = '.js';
const nameMap = {
'create-component': `src/components/${action.replace('create-', '')}${ext}`,
'add-api-endpoint': `src/api/endpoints${ext}`,
'fix-bug': `src/fixes/bugFix${ext}`,
'add-feature': `src/features/${action.replace('add-', '')}${ext}`,
'add-tests': `tests/test${ext}`,
};
return nameMap[action] || `src/${action}${ext}`;
}
}
export default IssueTracker;