Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
"command": "issue-agent.openIssueComposer",
"title": "Open Issue Composer",
"icon": "$(edit)"
},
{
"command": "issue-agent.commitAndPush",
"title": "Issue Agent: Commit and Push Changes",
"icon": "$(git-commit)"
}
],
"menus": {
Expand Down
209 changes: 206 additions & 3 deletions src/agentRunner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Octokit } from '@octokit/rest';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
Expand Down Expand Up @@ -125,19 +126,58 @@ export class AgentRunner {
name: config.get<string>('repo.name') || '',
};

// Get iteration history
const history = this.contextManager.getIterationHistory(issue.number);
const issueSummary = this.contextManager.getIssueSummary(issue.number);

// Show iteration history if exists
if (history.hasHistory) {
this.sendToWebview({
type: 'status',
message: `📚 Found ${history.iterations} previous iteration(s)...`,
});
vscode.window.showInformationMessage(
`📚 This issue has ${history.iterations} previous iteration(s). Including context in prompt.`,
);
}

// Fetch PR comments if there's an associated PR
let prComments: any[] = [];
try {
this.sendToWebview({
type: 'status',
message: '🔍 Checking for PR review comments...',
});
prComments = await this.fetchPRComments(issue.number, repoMeta);
if (prComments.length > 0) {
await this.contextManager.savePRComments(issue.number, prComments);
this.sendToWebview({
type: 'status',
message: `💬 Found ${prComments.length} PR review comment(s)`,
});
vscode.window.showInformationMessage(
`💬 Found ${prComments.length} PR review comment(s). Including in prompt.`,
);
}
} catch (error) {
console.log('No PR comments found or error fetching:', error);
}

// Get conversation history
const conversationKey = `conversation-${issue.number}`;
const conversation = this.context.workspaceState.get<any[]>(
conversationKey,
[],
);

// Build prompt (simplified - AI analyzes repo automatically)
// Build prompt with iteration history and PR comments
const prompt = this.promptBuilder.buildPrompt(
issue,
data.userContext || '',
repoMeta,
conversation,
history,
prComments,
);

// Get iteration count
Expand All @@ -164,6 +204,33 @@ export class AgentRunner {
contextPath: this.contextManager.getContextPath(issue.number),
});

// Step 1: Create and switch to a new branch BEFORE opening Copilot
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders) {
const rootPath = workspaceFolders[0].uri.fsPath;
const branchName = `issue-${issue.number}-fix`;

this.sendToWebview({
type: 'status',
message: `Creating branch ${branchName}...`,
});

try {
console.log(`[Agent] Creating branch: ${branchName}`);
await this.gitUtils.createBranch(branchName, rootPath);
console.log(`[Agent] ✅ Branch created: ${branchName}`);

vscode.window.showInformationMessage(
`✅ Created and switched to branch: ${branchName}`,
);
} catch (error) {
console.error(`[Agent] Failed to create branch:`, error);
vscode.window.showWarningMessage(
`Could not create branch ${branchName}. Copilot will make changes on the current branch.`,
);
}
}

this.sendToWebview({
type: 'status',
message: '✨ Opening Copilot Chat with your prompt...',
Expand All @@ -172,12 +239,25 @@ export class AgentRunner {
// Open Copilot Chat and paste the prompt for user review
await this.openInCopilotChat(prompt);

vscode.window.showInformationMessage(
const branchName = `issue-${issue.number}-fix`;
const choice = await vscode.window.showInformationMessage(
'🚀 Copilot Chat is ready!\n\n' +
`✅ You're now on branch: ${branchName}\n\n` +
'📋 Paste the prompt (Cmd+V), review it, and press Enter.\n\n' +
'✨ Copilot will analyze your code and make the necessary changes to fix the issue!',
'✨ Copilot will make the changes on this branch!',
'Got it!',
'Commit & Push Later',
);

// If user wants to commit later, show them how
if (choice === 'Commit & Push Later') {
vscode.window.showInformationMessage(
`After Copilot makes changes:\n\n` +
`1. Review the changes in Source Control\n` +
`2. Run "Issue Agent: Commit and Push" command (Cmd+Shift+P)\n` +
`3. Or manually: git add -A && git commit && git push`,
);
}
} catch (error) {
const errorMsg = `Failed to run agent: ${error}`;
this.sendToWebview({
Expand Down Expand Up @@ -740,4 +820,127 @@ export class AgentRunner {
'\n\n... [Content truncated, file too large] ...'
);
}

/**
* Fetch PR review comments and issue comments
*/
private async fetchPRComments(
issueNumber: number,
repoMeta: { owner: string; name: string },
): Promise<
Array<{
user: string;
body: string;
created_at: string;
path?: string;
type?: string;
}>
> {
try {
const token = await this.context.secrets.get('github-token');
if (!token) {
return [];
}

const octokit = new Octokit({ auth: token });

const allComments: Array<{
user: string;
body: string;
created_at: string;
path?: string;
type?: string;
}> = [];

// First, fetch comments directly on the issue itself
try {
const issueCommentsResponse = await octokit.issues.listComments({
owner: repoMeta.owner,
repo: repoMeta.name,
issue_number: issueNumber,
});

allComments.push(
...issueCommentsResponse.data.map((c) => ({
user: c.user?.login || 'unknown',
body: c.body || '',
created_at: c.created_at,
type: 'issue-comment',
})),
);

console.log(
`Found ${issueCommentsResponse.data.length} comments on issue #${issueNumber}`,
);
} catch (error) {
console.error('Error fetching issue comments:', error);
}

// Now, find if there's a PR associated with this issue
try {
const prs = await octokit.pulls.list({
owner: repoMeta.owner,
repo: repoMeta.name,
state: 'all',
per_page: 100,
});

// Find PR that mentions this issue in title or body
const associatedPR = prs.data.find(
(pr) =>
pr.title.includes(`#${issueNumber}`) ||
pr.body?.includes(`#${issueNumber}`) ||
pr.title.includes(`issue-${issueNumber}`),
);

if (associatedPR) {
console.log(
`Found PR #${associatedPR.number} for issue #${issueNumber}`,
);

// Fetch review comments on the PR
const reviewComments = await octokit.pulls.listReviewComments({
owner: repoMeta.owner,
repo: repoMeta.name,
pull_number: associatedPR.number,
});

allComments.push(
...reviewComments.data.map((c) => ({
user: c.user?.login || 'unknown',
body: c.body || '',
created_at: c.created_at,
path: c.path,
type: 'pr-review-comment',
})),
);

// Fetch regular comments on the PR
const prComments = await octokit.issues.listComments({
owner: repoMeta.owner,
repo: repoMeta.name,
issue_number: associatedPR.number,
});

allComments.push(
...prComments.data.map((c) => ({
user: c.user?.login || 'unknown',
body: c.body || '',
created_at: c.created_at,
type: 'pr-comment',
})),
);
} else {
console.log(`No PR found for issue #${issueNumber}`);
}
} catch (error) {
console.error('Error fetching PR comments:', error);
}

return allComments;
} catch (error) {
console.error('Error fetching comments:', error);
return [];
}
}
}
Loading
Loading