Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

ai-generators.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

ai-generators.tsBlame170 lines · 1 contributor
3ef4c9dClaude1/**
2 * AI generators — commit messages, PR descriptions, changelogs, issue triage.
3 * All exposed via POST endpoints for CLI hooks + web UI convenience.
4 */
5
6import {
7 getAnthropic,
8 MODEL_HAIKU,
9 MODEL_SONNET,
10 extractText,
11 parseJsonResponse,
12 isAiAvailable,
13} from "./ai-client";
14
15export async function generateCommitMessage(diff: string): Promise<string> {
16 if (!isAiAvailable() || !diff.trim()) {
17 return "chore: update files";
18 }
19 const client = getAnthropic();
20 const message = await client.messages.create({
21 model: MODEL_HAIKU,
22 max_tokens: 512,
23 messages: [
24 {
25 role: "user",
26 content: `Write a single conventional-commit message for this diff. One line subject under 72 chars, lowercase type (feat/fix/refactor/chore/docs/test/perf/style), then optional body wrapped at 72 chars. No backticks or markdown.
27
28Diff:
29\`\`\`
30${diff.slice(0, 40000)}
31\`\`\``,
32 },
33 ],
34 });
35 return extractText(message).trim();
36}
37
38export async function generatePrSummary(
39 title: string,
40 diff: string
41): Promise<string> {
42 if (!isAiAvailable() || !diff.trim()) return "";
43 const client = getAnthropic();
44 const message = await client.messages.create({
45 model: MODEL_SONNET,
46 max_tokens: 1024,
47 messages: [
48 {
49 role: "user",
50 content: `Generate a Markdown PR description for the following changes. Include: Summary (1-3 sentences focused on why), Key changes (bullets), Test plan (bullets), Risks (bullets — omit if minor).
51
52Title: ${title}
53
54Diff:
55\`\`\`
56${diff.slice(0, 60000)}
57\`\`\``,
58 },
59 ],
60 });
61 return extractText(message).trim();
62}
63
64export async function generateChangelog(
65 repoFullName: string,
66 fromRef: string | null,
67 toRef: string,
68 commits: Array<{ sha: string; message: string; author: string }>
69): Promise<string> {
70 if (!isAiAvailable() || commits.length === 0) {
71 const header = `## ${toRef}${fromRef ? ` (since ${fromRef})` : ""}\n\n`;
72 return (
73 header +
74 commits
75 .map((c) => `- ${c.message.split("\n")[0]} (${c.sha.slice(0, 7)}) — ${c.author}`)
76 .join("\n")
77 );
78 }
79 const client = getAnthropic();
80 const commitBlob = commits
81 .slice(0, 200)
82 .map((c) => `- ${c.sha.slice(0, 7)} ${c.message.split("\n")[0]} — ${c.author}`)
83 .join("\n");
84 const message = await client.messages.create({
85 model: MODEL_SONNET,
86 max_tokens: 2048,
87 messages: [
88 {
89 role: "user",
90 content: `Generate a polished release-notes changelog in Markdown for ${repoFullName}.
91
92Release: ${toRef}
93Previous: ${fromRef || "(initial)"}
94
95Group commits by category (Features, Fixes, Performance, Refactoring, Docs, Other). Omit empty categories. Use bullet points. Keep it concise — no marketing fluff, just the facts a user of the project needs. Reference SHAs in parentheses.
96
97Commits:
98${commitBlob}`,
99 },
100 ],
101 });
102 return extractText(message).trim();
103}
104
105interface IssueTriage {
106 suggestedLabels: string[];
107 duplicateOfIssueNumber: number | null;
108 priority: "critical" | "high" | "medium" | "low";
109 summary: string;
110}
111
112export async function triageIssue(
113 title: string,
114 body: string,
115 existingLabels: string[],
116 recentIssues: Array<{ number: number; title: string }>
117): Promise<IssueTriage> {
118 const fallback: IssueTriage = {
119 suggestedLabels: [],
120 duplicateOfIssueNumber: null,
121 priority: "medium",
122 summary: "",
123 };
124 if (!isAiAvailable()) return fallback;
125 const client = getAnthropic();
126 const message = await client.messages.create({
127 model: MODEL_HAIKU,
128 max_tokens: 512,
129 messages: [
130 {
131 role: "user",
132 content: `Triage this new GitHub-style issue.
133
134Title: ${title}
135Body:
136${body.slice(0, 4000)}
137
138Available labels: ${existingLabels.join(", ") || "(none)"}
139Recent issues (to check for duplicates):
140${recentIssues.map((i) => `#${i.number}: ${i.title}`).join("\n")}
141
142Respond ONLY with JSON:
143{
144 "suggestedLabels": ["label1", "label2"],
145 "duplicateOfIssueNumber": null,
146 "priority": "medium",
147 "summary": "one sentence"
148}
149Only suggest labels from the available list. Set duplicateOfIssueNumber only when confident.`,
150 },
151 ],
152 });
153 const parsed = parseJsonResponse<IssueTriage>(extractText(message));
154 if (!parsed) return fallback;
155 return {
156 suggestedLabels: Array.isArray(parsed.suggestedLabels)
157 ? parsed.suggestedLabels.filter((l) => existingLabels.includes(l))
158 : [],
159 duplicateOfIssueNumber:
160 typeof parsed.duplicateOfIssueNumber === "number"
161 ? parsed.duplicateOfIssueNumber
162 : null,
163 priority: (["critical", "high", "medium", "low"] as const).includes(
164 parsed.priority as never
165 )
166 ? parsed.priority
167 : "medium",
168 summary: typeof parsed.summary === "string" ? parsed.summary : "",
169 };
170}