Skip to content

Commit 089b956

Browse files
macclaude
authored andcommitted
feat: add pr skill for generating pull request descriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ac38a4c commit 089b956

4 files changed

Lines changed: 99 additions & 0 deletions

File tree

scripts/build-skills.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ function stripTypes(code) {
1212
.replace(/^import\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]+['"];?\s*$/gm, "")
1313
.replace(/:\s*SkillHandler/g, "")
1414
.replace(/:\s*SkillContext/g, "")
15+
.replace(/:\s*(?:string|number|boolean)(?:\[\])?/g, "")
16+
.replace(/\):\s*Promise<[^>]*>\s*\{/g, ") {")
1517
.replace(/\s+as\s+(string|boolean|number)/g, "");
1618
}
1719

src/skills/built-in/pr/handler.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { SkillHandler } from "../../types.js";
2+
import { execFile } from "node:child_process";
3+
import { promisify } from "node:util";
4+
import { streamQuery } from "../../handler-utils.js";
5+
6+
const execFileAsync = promisify(execFile);
7+
8+
const SYSTEM_PROMPT = `You are an expert at writing pull request descriptions. Generate a clear, well-structured PR description in Markdown.
9+
10+
Use this format:
11+
12+
## Summary
13+
- 2-3 bullet points explaining the purpose and motivation
14+
15+
## Changes
16+
- List the key changes grouped logically
17+
- Reference specific files when helpful
18+
19+
## Test plan
20+
- [ ] Checklist items describing how to verify the changes
21+
22+
Output ONLY the PR description in Markdown, nothing else.`;
23+
24+
async function gitCommand(
25+
args: string[],
26+
cwd: string,
27+
): Promise<string | null> {
28+
try {
29+
const result = await execFileAsync("git", args, {
30+
cwd,
31+
maxBuffer: 10 * 1024 * 1024,
32+
});
33+
return result.stdout.trim() || null;
34+
} catch {
35+
return null;
36+
}
37+
}
38+
39+
const handler: SkillHandler = async (ctx) => {
40+
const base = (ctx.flags.base as string) || "main";
41+
const cwd = ctx.context.cwd;
42+
43+
const [commitLog, diffStat, fullDiff] = await Promise.all([
44+
gitCommand(["log", "--oneline", `${base}..HEAD`], cwd),
45+
gitCommand(["diff", `${base}...HEAD`, "--stat"], cwd),
46+
gitCommand(["diff", `${base}...HEAD`], cwd),
47+
]);
48+
49+
const diff = fullDiff || ctx.context.gitDiff;
50+
51+
if (!diff && !commitLog) {
52+
ctx.logger.info(
53+
`No changes found between '${base}' and HEAD. Check that you are on a feature branch.`,
54+
);
55+
return;
56+
}
57+
58+
const parts = [];
59+
60+
if (commitLog) {
61+
parts.push(`Commits on this branch:\n${commitLog}`);
62+
}
63+
64+
if (diffStat) {
65+
parts.push(`Changed files summary:\n${diffStat}`);
66+
}
67+
68+
if (diff) {
69+
parts.push(`Full diff:\n\`\`\`diff\n${diff}\n\`\`\``);
70+
}
71+
72+
if (ctx.context.fileTree) {
73+
parts.push(`Repository file tree:\n${ctx.context.fileTree}`);
74+
}
75+
76+
const prompt = `Generate a pull request description for the following changes:\n\n${parts.join("\n\n")}`;
77+
78+
await streamQuery(ctx.agent, prompt, { systemPrompt: SYSTEM_PROMPT });
79+
};
80+
81+
export default handler;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "pr",
3+
"version": "1.0.0",
4+
"description": "Generate a pull request description",
5+
"inputs": {
6+
"flags": [
7+
{ "name": "base", "short": "b", "type": "string", "description": "Base branch (default: main)", "default": "main" }
8+
],
9+
"context": ["git-log", "git-diff", "file-tree"]
10+
},
11+
"aliases": ["pull-request"]
12+
}

src/utils/typescript.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export function stripTypes(code: string): string {
1212
// Remove type annotations on variables/parameters (`: Type`)
1313
.replace(/:\s*SkillHandler/g, "")
1414
.replace(/:\s*SkillContext/g, "")
15+
// Remove function parameter type annotations (`: string`, `: string[]`, etc.)
16+
.replace(/:\s*(?:string|number|boolean)(?:\[\])?/g, "")
17+
// Remove return type annotations like `): Promise<...> {`
18+
.replace(/\):\s*Promise<[^>]*>\s*\{/g, ") {")
1519
// Remove `as type` casts
1620
.replace(/\s+as\s+(string|boolean|number)/g, "");
1721
}

0 commit comments

Comments
 (0)