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-chat.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-chat.tsBlame193 lines · 1 contributor
3ef4c9dClaude1/**
2 * AI chat assistant — conversational interface grounded in repo context.
3 *
4 * The assistant can:
5 * - Answer questions about the codebase ("where is auth handled?")
6 * - Explain files / functions ("explain src/lib/gate.ts")
7 * - Draft code (answered verbally, user applies manually)
8 * - Summarise recent activity ("what changed this week?")
9 *
10 * Grounds its answers in a curated context window built from:
11 * - Repo README
12 * - Recent commits
13 * - Tree listing
14 * - Files the user explicitly @-mentions in the message
15 */
16
17import {
18 getAnthropic,
19 MODEL_SONNET,
20 extractText,
21 isAiAvailable,
22} from "./ai-client";
23import {
24 getReadme,
25 getTree,
26 getBlob,
27 listCommits,
28 getDefaultBranch,
29} from "../git/repository";
30
31export interface ChatMessage {
32 role: "user" | "assistant";
33 content: string;
34}
35
36export interface ChatResponse {
37 reply: string;
38 citedFiles: string[];
39}
40
41/**
42 * Build a concise repo context block.
43 * Keeps under ~60k chars to leave room for conversation.
44 */
45async function buildRepoContext(
46 owner: string,
47 repo: string,
48 mentionedFiles: string[]
49): Promise<{ context: string; files: string[] }> {
50 const branch = (await getDefaultBranch(owner, repo)) || "main";
51 const citedFiles: string[] = [];
52 const parts: string[] = [];
53
54 parts.push(`# Repository: ${owner}/${repo}\nDefault branch: ${branch}\n`);
55
56 // README
57 const readme = await getReadme(owner, repo, branch);
58 if (readme) {
59 parts.push(`## README\n${readme.slice(0, 8000)}\n`);
60 }
61
62 // Top-level tree
63 const tree = await getTree(owner, repo, branch);
64 if (tree.length > 0) {
65 parts.push(
66 `## Top-level files\n${tree
67 .slice(0, 60)
68 .map((e) => `- ${e.type === "tree" ? e.name + "/" : e.name}`)
69 .join("\n")}\n`
70 );
71 }
72
73 // Recent commits
74 const commits = await listCommits(owner, repo, branch, 15);
75 if (commits.length > 0) {
76 parts.push(
77 `## Recent commits\n${commits
78 .map((c) => `- ${c.sha.slice(0, 7)} ${c.message.split("\n")[0]} — ${c.author}`)
79 .join("\n")}\n`
80 );
81 }
82
83 // Mentioned files
84 for (const file of mentionedFiles.slice(0, 8)) {
85 try {
86 const blob = await getBlob(owner, repo, branch, file);
87 if (blob && !blob.isBinary) {
88 citedFiles.push(file);
89 parts.push(`## File: ${file}\n\`\`\`\n${blob.content.slice(0, 12000)}\n\`\`\`\n`);
90 }
91 } catch {
92 // ignore
93 }
94 }
95
96 return { context: parts.join("\n"), files: citedFiles };
97}
98
99/**
100 * Extract @-mentions of files from a user's message.
101 * Supports @filename.ext and @path/to/file.ext.
102 */
103function extractFileMentions(text: string): string[] {
104 const matches = text.match(/@([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)/g);
105 if (!matches) return [];
106 return Array.from(new Set(matches.map((m) => m.slice(1))));
107}
108
109export async function chat(
110 owner: string,
111 repo: string | null,
112 history: ChatMessage[],
113 userMessage: string
114): Promise<ChatResponse> {
115 if (!isAiAvailable()) {
116 return {
117 reply:
118 "AI chat is not available — the server needs an ANTHROPIC_API_KEY to be configured.",
119 citedFiles: [],
120 };
121 }
122 const client = getAnthropic();
123
124 const mentioned = extractFileMentions(userMessage);
125 const { context: repoContext, files } = repo
126 ? await buildRepoContext(owner, repo, mentioned)
127 : { context: "", files: [] };
128
129 const system = repo
130 ? `You are GlueCron's AI assistant. You help developers understand and work with the repository ${owner}/${repo}. Be concise, accurate, and reference specific files and line numbers when relevant. If the user asks about something not in your context, say so.`
131 : `You are GlueCron's AI assistant. You help developers navigate the GlueCron platform — a git host with green-gate enforcement, AI code review, and auto-repair. Keep answers concise.`;
132
133 const messages: ChatMessage[] = [
134 ...history,
135 {
136 role: "user",
137 content: repoContext
138 ? `${repoContext}\n\n---\n\nUser question: ${userMessage}`
139 : userMessage,
140 },
141 ];
142
143 const response = await client.messages.create({
144 model: MODEL_SONNET,
145 max_tokens: 2048,
146 system,
147 messages: messages.map((m) => ({ role: m.role, content: m.content })),
148 });
149
150 return {
151 reply: extractText(response).trim(),
152 citedFiles: files,
153 };
154}
155
156/**
157 * Explain a single file — used for the "Explain this file" button on blob views.
158 */
159export async function explainFile(
160 owner: string,
161 repo: string,
162 filePath: string,
163 content: string
164): Promise<string> {
165 if (!isAiAvailable()) {
166 return "AI explanations are not available — server needs ANTHROPIC_API_KEY.";
167 }
168 const client = getAnthropic();
169 const message = await client.messages.create({
170 model: MODEL_SONNET,
171 max_tokens: 1024,
172 messages: [
173 {
174 role: "user",
175 content: `Explain this file from ${owner}/${repo} in plain English.
176
177Structure:
1781. **Purpose** — one sentence
1792. **Key exports / APIs** — bulleted list
1803. **How it works** — 2-4 sentences
1814. **Gotchas / caveats** — only if any
182
183Be concise.
184
185File: ${filePath}
186\`\`\`
187${content.slice(0, 40000)}
188\`\`\``,
189 },
190 ],
191 });
192 return extractText(message).trim();
193}