Skip to content

Commit f14c055

Browse files
authored
Add create PR tool and skill (#8631)
* Add create PR tool and skill * CCR comments
1 parent 34d7256 commit f14c055

File tree

5 files changed

+271
-1
lines changed

5 files changed

+271
-1
lines changed

package.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,9 @@
38003800
},
38013801
{
38023802
"path": "./src/lm/skills/address-pr-comments/SKILL.md"
3803+
},
3804+
{
3805+
"path": "./src/lm/skills/create-pull-request/SKILL.md"
38033806
}
38043807
],
38053808
"languageModelTools": [
@@ -4040,6 +4043,67 @@
40404043
"userDescription": "%languageModelTools.github-pull-request_openPullRequest.description%",
40414044
"when": "config.githubPullRequests.experimental.chat"
40424045
},
4046+
{
4047+
"name": "github-pull-request_create_pull_request",
4048+
"tags": [
4049+
"github",
4050+
"pull request"
4051+
],
4052+
"toolReferenceName": "create_pull_request",
4053+
"displayName": "%languageModelTools.github-pull-request_create_pull_request.displayName%",
4054+
"userDescription": "%languageModelTools.github-pull-request_create_pull_request.description%",
4055+
"modelDescription": "Create a new GitHub pull request. Requires a title and head branch. The base branch and repo default to the repository defaults. Returns the created pull request number, URL, and details.",
4056+
"icon": "$(git-pull-request-new)",
4057+
"canBeReferencedInPrompt": true,
4058+
"inputSchema": {
4059+
"type": "object",
4060+
"properties": {
4061+
"repo": {
4062+
"type": "object",
4063+
"description": "The repository to create the pull request in.",
4064+
"properties": {
4065+
"owner": {
4066+
"type": "string",
4067+
"description": "The owner of the repository."
4068+
},
4069+
"name": {
4070+
"type": "string",
4071+
"description": "The name of the repository."
4072+
}
4073+
}
4074+
},
4075+
"title": {
4076+
"type": "string",
4077+
"description": "The title of the pull request."
4078+
},
4079+
"body": {
4080+
"type": "string",
4081+
"description": "The body/description of the pull request."
4082+
},
4083+
"head": {
4084+
"type": "string",
4085+
"description": "The name of the branch where your changes are implemented (branch name only, without owner prefix)."
4086+
},
4087+
"headOwner": {
4088+
"type": "string",
4089+
"description": "The owner of the head branch repository. Defaults to the origin/push remote repository owner."
4090+
},
4091+
"base": {
4092+
"type": "string",
4093+
"description": "The name of the branch you want the changes pulled into. Defaults to the repository's default branch."
4094+
},
4095+
"draft": {
4096+
"type": "boolean",
4097+
"description": "Indicates whether the pull request is a draft."
4098+
}
4099+
},
4100+
"required": [
4101+
"title",
4102+
"head"
4103+
]
4104+
},
4105+
"when": "config.githubPullRequests.experimental.chat"
4106+
},
40434107
{
40444108
"name": "github-pull-request_resolveReviewThread",
40454109
"tags": [

package.nls.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,5 +447,7 @@
447447
"languageModelTools.github-pull-request_openPullRequest.displayName": "Open Pull Request",
448448
"languageModelTools.github-pull-request_openPullRequest.description": "Get information about the open GitHub pull request. This information includes: comments, files changed, pull request title + description, and pull request state.",
449449
"languageModelTools.github-pull-request_resolveReviewThread.displayName": "Resolve Review Thread",
450-
"languageModelTools.github-pull-request_resolveReviewThread.description": "Resolve a review thread on the active GitHub pull request."
450+
"languageModelTools.github-pull-request_resolveReviewThread.description": "Resolve a review thread on the active GitHub pull request.",
451+
"languageModelTools.github-pull-request_create_pull_request.displayName": "Create a GitHub pull request",
452+
"languageModelTools.github-pull-request_create_pull_request.description": "Create a new GitHub pull request with a title, head branch, and optional body, base branch, and draft flag."
451453
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
name: create-pull-request
3+
description: "Create a GitHub Pull Request from the current or specified branch. Use when: opening a PR, submitting code for review, creating a draft PR, publishing a branch as a pull request, proposing changes to a repository."
4+
argument-hint: "Optionally specify a title, base branch, or whether to create as a draft"
5+
---
6+
7+
# Create a GitHub Pull Request
8+
9+
Gather the necessary information, prepare a clear title and description, then call the tool to open the pull request.
10+
11+
## When to Use
12+
13+
- The user wants to open a PR for their current or a specified branch
14+
- The user has finished a feature or fix and wants to submit it for review
15+
- The user wants to create a draft PR to share work in progress
16+
- The user asks to "open a PR", "create a pull request", or "submit for review"
17+
18+
## Procedure
19+
20+
### 1. Gather Information
21+
22+
Determine the required parameters before calling the tool:
23+
24+
- **Head branch**: If the user has not specified a branch, use workspace or git context to find the current branch name. Do not use `owner:branch` format - pass just the branch name (e.g. `my-feature`).
25+
- **Base branch**: If the user has not specified a base branch, omit it and let the tool use the repository's default branch.
26+
- **Title**: If the user has not provided a title, derive one from the branch name, recent commits, or the user's description of their work (see Best Practices below).
27+
- **Body**: If the user has not provided a description, prepare a concise summary of what changed and why (see Best Practices below).
28+
- **Draft**: Ask or infer whether the PR should be a draft. Default to non-draft unless the user indicates the work is not ready for review.
29+
30+
### 2. Check for Uncommitted or Unpushed Changes
31+
32+
Before creating the PR, inspect the working tree state. If you need to run git commands, give an explanation for why the command needs to be run.
33+
34+
1. **Check for uncommitted changes**: Use the git tool or VS Code SCM context to determine whether there are staged or unstaged file changes. If yes:
35+
- Ask the user if they want to commit these changes before opening the PR.
36+
- If they do, help them write a commit message and commit the changes (`git add -A && git commit -m "<message>"`).
37+
- If they decline, proceed only if there are already commits on the branch that are ahead of the base - otherwise there is nothing to put in the PR.
38+
39+
2. **Check for unpushed commits**: Determine whether the local branch has commits that have not been pushed to the remote (i.e. the branch is ahead of its upstream). If yes:
40+
- Ask the user if they want to push before opening the PR, or let them know the tool will attempt to push automatically if needed.
41+
- If pushing manually is preferred, run `git push` (or `git push --set-upstream origin <branch>` if no upstream is set yet) before calling the tool.
42+
43+
3. **Confirm the branch is on the remote**: The `create_pull_request` tool requires the head branch to be present on the remote. If it is not, push it first.
44+
45+
If all changes are already committed and pushed, proceed directly to the next step.
46+
47+
### 3. Prepare PR Details
48+
49+
Write a good title and description if the user has not provided them:
50+
51+
**Title**: Use imperative mood, keep it under 72 characters, and describe *what* the PR does (e.g. `Add retry logic for failed API requests`).
52+
53+
**Body**: Include:
54+
- A short summary of what changed and why
55+
- Any relevant issue references (e.g. `Fixes #123`)
56+
- Notable implementation decisions, if useful for the reviewer
57+
58+
### 4. Call the Tool
59+
60+
Use the `github-pull-request_create_pull_request` tool with the gathered parameters:
61+
62+
```
63+
github-pull-request_create_pull_request({
64+
title: '<descriptive title>',
65+
head: '<branch-name>', // branch name only, not owner:branch
66+
body: '<description>', // optional but recommended
67+
base: '<base-branch>', // optional; omit to use repo default
68+
draft: false, // set true for work-in-progress
69+
headOwner: '<owner>', // optional; omit if same as repo owner
70+
repo: { owner: '<owner>', name: '<repo>' } // optional
71+
})
72+
```
73+
74+
### 5. Confirm Result
75+
76+
After the tool returns successfully:
77+
78+
- Report the PR number and URL to the user as a markdown link. The link should be a VS Code URI like `vscode-insiders://github.vscode-pull-request-github/open-pull-request-webview?uri=https://github.com/microsoft/vscode-css-languageservice/pull/460` or `vscode://github.vscode-pull-request-github/open-pull-request-webview?uri=https://github.com/microsoft/vscode-css-languageservice/pull/460`.
79+
- Mention the base branch the PR targets.
80+
- If the PR was created as a draft, remind the user to mark it ready for review when appropriate.
81+
82+
## Best Practices
83+
84+
### Titles
85+
- Use the imperative mood: `Fix`, `Add`, `Update`, `Remove`, `Refactor` - not `Fixed`, `Adding`, etc.
86+
- Be specific: `Fix null pointer in user login flow` beats `Fix bug`.
87+
- Keep it under 72 characters so it displays cleanly in GitHub and email notifications.
88+
89+
### Descriptions
90+
- Start with a one-sentence summary.
91+
- Explain *why* the change is needed, not just *what* it does - reviewers benefit from context.
92+
- Reference related issues with `Fixes #<number>` or `Closes #<number>` to auto-close them on merge.
93+
- If the change is large, add a brief list of the main files or components touched.
94+
95+
### Draft PRs
96+
- Use `draft: true` when the code is not yet ready for formal review (e.g. work in progress, awaiting feedback on approach, CI not yet passing).
97+
- Draft PRs are visible to collaborators but will not show as review-requested until marked ready.
98+
- Suggest using a draft when the user mentions they are still working on it or just want early feedback.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import * as vscode from 'vscode';
8+
import { RepoToolBase } from './toolsUtils';
9+
import { CredentialStore } from '../../github/credentials';
10+
import { FolderRepositoryManager } from '../../github/folderRepositoryManager';
11+
import { RepositoriesManager } from '../../github/repositoriesManager';
12+
13+
interface CreatePullRequestToolParameters {
14+
title: string;
15+
body?: string;
16+
head: string;
17+
headOwner?: string;
18+
base?: string;
19+
draft?: boolean;
20+
repo?: {
21+
owner?: string;
22+
name?: string;
23+
};
24+
}
25+
26+
interface ResolvedPullRequestParams {
27+
owner: string;
28+
name: string;
29+
head: string;
30+
base: string;
31+
folderManager: FolderRepositoryManager;
32+
}
33+
34+
export class CreatePullRequestTool extends RepoToolBase<CreatePullRequestToolParameters> {
35+
public static readonly toolId = 'github-pull-request_create_pull_request';
36+
37+
constructor(credentialStore: CredentialStore, repositoriesManager: RepositoriesManager) {
38+
super(credentialStore, repositoriesManager);
39+
}
40+
41+
private async resolveParams(input: CreatePullRequestToolParameters): Promise<ResolvedPullRequestParams> {
42+
const { owner, name, folderManager } = await this.getRepoInfo({ owner: input.repo?.owner, name: input.repo?.name });
43+
const defaults = await folderManager.getPullRequestDefaults();
44+
const headOwner = input.headOwner ?? defaults.owner;
45+
const head = `${headOwner}:${input.head}`;
46+
const base = input.base ?? defaults.base;
47+
return { owner, name, head, base, folderManager };
48+
}
49+
50+
async invoke(options: vscode.LanguageModelToolInvocationOptions<CreatePullRequestToolParameters>, _token: vscode.CancellationToken): Promise<vscode.LanguageModelToolResult> {
51+
const { owner, name, head, base, folderManager } = await this.resolveParams(options.input);
52+
53+
const result = await folderManager.createPullRequest({
54+
owner,
55+
repo: name,
56+
title: options.input.title,
57+
body: options.input.body,
58+
head,
59+
base,
60+
draft: options.input.draft,
61+
});
62+
63+
if (!result) {
64+
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart('Failed to create the pull request.')]);
65+
}
66+
67+
const prInfo = {
68+
number: result.number,
69+
title: result.title,
70+
body: result.body,
71+
url: result.html_url,
72+
isDraft: result.isDraft,
73+
state: result.state,
74+
base: result.base?.ref,
75+
head: result.head?.ref,
76+
};
77+
78+
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(JSON.stringify(prInfo))]);
79+
}
80+
81+
async prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<CreatePullRequestToolParameters>): Promise<vscode.PreparedToolInvocation> {
82+
const resolved = await this.resolveParams(options.input);
83+
const { owner, name, base } = resolved;
84+
// resolved.head is "owner:branch"; extract just the branch part for display
85+
const headBranch = resolved.head.slice(resolved.head.indexOf(':') + 1);
86+
87+
const repoLabel = `${owner}/${name}`;
88+
const message = new vscode.MarkdownString();
89+
message.appendMarkdown(`**Title:** ${options.input.title}\n\n`);
90+
if (options.input.body) {
91+
message.appendMarkdown(`**Description:** ${options.input.body}\n\n`);
92+
}
93+
message.appendMarkdown(`**Branch:** \`${headBranch}\` → \`${base}\`\n\n`);
94+
message.appendMarkdown(`**Repository:** ${repoLabel}\n\n`);
95+
96+
return {
97+
invocationMessage: vscode.l10n.t('Creating pull request'),
98+
confirmationMessages: {
99+
title: vscode.l10n.t('Create Pull Request'),
100+
message,
101+
},
102+
};
103+
}
104+
}

src/lm/tools/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import * as vscode from 'vscode';
88
import { ActivePullRequestTool } from './activePullRequestTool';
9+
import { CreatePullRequestTool } from './createPullRequestTool';
910
import { FetchIssueTool } from './fetchIssueTool';
1011
import { FetchLabelsTool } from './fetchLabelsTool';
1112
import { FetchNotificationTool } from './fetchNotificationTool';
@@ -22,6 +23,7 @@ export function registerTools(context: vscode.ExtensionContext, credentialStore:
2223
context.subscriptions.push(vscode.lm.registerTool(ActivePullRequestTool.toolId, new ActivePullRequestTool(repositoriesManager)));
2324
context.subscriptions.push(vscode.lm.registerTool(OpenPullRequestTool.toolId, new OpenPullRequestTool(repositoriesManager)));
2425
context.subscriptions.push(vscode.lm.registerTool(PullRequestStatusChecksTool.toolId, new PullRequestStatusChecksTool(credentialStore, repositoriesManager)));
26+
context.subscriptions.push(vscode.lm.registerTool(CreatePullRequestTool.toolId, new CreatePullRequestTool(credentialStore, repositoriesManager)));
2527
context.subscriptions.push(vscode.lm.registerTool(ResolveReviewThreadTool.toolId, new ResolveReviewThreadTool(repositoriesManager)));
2628
}
2729

0 commit comments

Comments
 (0)