Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
ai-generators.ts4.7 KB · 170 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/**
 * AI generators — commit messages, PR descriptions, changelogs, issue triage.
 * All exposed via POST endpoints for CLI hooks + web UI convenience.
 */

import {
  getAnthropic,
  MODEL_HAIKU,
  MODEL_SONNET,
  extractText,
  parseJsonResponse,
  isAiAvailable,
} from "./ai-client";

export async function generateCommitMessage(diff: string): Promise<string> {
  if (!isAiAvailable() || !diff.trim()) {
    return "chore: update files";
  }
  const client = getAnthropic();
  const message = await client.messages.create({
    model: MODEL_HAIKU,
    max_tokens: 512,
    messages: [
      {
        role: "user",
        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.

Diff:
\`\`\`
${diff.slice(0, 40000)}
\`\`\``,
      },
    ],
  });
  return extractText(message).trim();
}

export async function generatePrSummary(
  title: string,
  diff: string
): Promise<string> {
  if (!isAiAvailable() || !diff.trim()) return "";
  const client = getAnthropic();
  const message = await client.messages.create({
    model: MODEL_SONNET,
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        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).

Title: ${title}

Diff:
\`\`\`
${diff.slice(0, 60000)}
\`\`\``,
      },
    ],
  });
  return extractText(message).trim();
}

export async function generateChangelog(
  repoFullName: string,
  fromRef: string | null,
  toRef: string,
  commits: Array<{ sha: string; message: string; author: string }>
): Promise<string> {
  if (!isAiAvailable() || commits.length === 0) {
    const header = `## ${toRef}${fromRef ? ` (since ${fromRef})` : ""}\n\n`;
    return (
      header +
      commits
        .map((c) => `- ${c.message.split("\n")[0]} (${c.sha.slice(0, 7)}) — ${c.author}`)
        .join("\n")
    );
  }
  const client = getAnthropic();
  const commitBlob = commits
    .slice(0, 200)
    .map((c) => `- ${c.sha.slice(0, 7)} ${c.message.split("\n")[0]}${c.author}`)
    .join("\n");
  const message = await client.messages.create({
    model: MODEL_SONNET,
    max_tokens: 2048,
    messages: [
      {
        role: "user",
        content: `Generate a polished release-notes changelog in Markdown for ${repoFullName}.

Release: ${toRef}
Previous: ${fromRef || "(initial)"}

Group 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.

Commits:
${commitBlob}`,
      },
    ],
  });
  return extractText(message).trim();
}

interface IssueTriage {
  suggestedLabels: string[];
  duplicateOfIssueNumber: number | null;
  priority: "critical" | "high" | "medium" | "low";
  summary: string;
}

export async function triageIssue(
  title: string,
  body: string,
  existingLabels: string[],
  recentIssues: Array<{ number: number; title: string }>
): Promise<IssueTriage> {
  const fallback: IssueTriage = {
    suggestedLabels: [],
    duplicateOfIssueNumber: null,
    priority: "medium",
    summary: "",
  };
  if (!isAiAvailable()) return fallback;
  const client = getAnthropic();
  const message = await client.messages.create({
    model: MODEL_HAIKU,
    max_tokens: 512,
    messages: [
      {
        role: "user",
        content: `Triage this new GitHub-style issue.

Title: ${title}
Body:
${body.slice(0, 4000)}

Available labels: ${existingLabels.join(", ") || "(none)"}
Recent issues (to check for duplicates):
${recentIssues.map((i) => `#${i.number}: ${i.title}`).join("\n")}

Respond ONLY with JSON:
{
  "suggestedLabels": ["label1", "label2"],
  "duplicateOfIssueNumber": null,
  "priority": "medium",
  "summary": "one sentence"
}
Only suggest labels from the available list. Set duplicateOfIssueNumber only when confident.`,
      },
    ],
  });
  const parsed = parseJsonResponse<IssueTriage>(extractText(message));
  if (!parsed) return fallback;
  return {
    suggestedLabels: Array.isArray(parsed.suggestedLabels)
      ? parsed.suggestedLabels.filter((l) => existingLabels.includes(l))
      : [],
    duplicateOfIssueNumber:
      typeof parsed.duplicateOfIssueNumber === "number"
        ? parsed.duplicateOfIssueNumber
        : null,
    priority: (["critical", "high", "medium", "low"] as const).includes(
      parsed.priority as never
    )
      ? parsed.priority
      : "medium",
    summary: typeof parsed.summary === "string" ? parsed.summary : "",
  };
}