@g-abhishek/gitx 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +386 -3
- package/dist/ai/claudeAi.d.ts +35 -0
- package/dist/ai/claudeAi.d.ts.map +1 -0
- package/dist/ai/claudeAi.js +396 -0
- package/dist/ai/claudeAi.js.map +1 -0
- package/dist/ai/claudeCliAi.d.ts +27 -0
- package/dist/ai/claudeCliAi.d.ts.map +1 -0
- package/dist/ai/claudeCliAi.js +312 -0
- package/dist/ai/claudeCliAi.js.map +1 -0
- package/dist/ai/localClaudeAi.d.ts +2 -0
- package/dist/ai/localClaudeAi.d.ts.map +1 -0
- package/dist/ai/localClaudeAi.js +4 -0
- package/dist/ai/localClaudeAi.js.map +1 -0
- package/dist/ai/mockAi.d.ts +8 -1
- package/dist/ai/mockAi.d.ts.map +1 -1
- package/dist/ai/mockAi.js +57 -0
- package/dist/ai/mockAi.js.map +1 -1
- package/dist/ai/openAiAi.d.ts +33 -0
- package/dist/ai/openAiAi.d.ts.map +1 -0
- package/dist/ai/openAiAi.js +388 -0
- package/dist/ai/openAiAi.js.map +1 -0
- package/dist/ai/reviewHelpers.d.ts +66 -0
- package/dist/ai/reviewHelpers.d.ts.map +1 -0
- package/dist/ai/reviewHelpers.js +574 -0
- package/dist/ai/reviewHelpers.js.map +1 -0
- package/dist/ai/types.d.ts +247 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/ai/types.js.map +1 -1
- package/dist/cli/commands/ask.d.ts +27 -0
- package/dist/cli/commands/ask.d.ts.map +1 -0
- package/dist/cli/commands/ask.js +230 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/commit.d.ts +16 -0
- package/dist/cli/commands/commit.d.ts.map +1 -0
- package/dist/cli/commands/commit.js +163 -0
- package/dist/cli/commands/commit.js.map +1 -0
- package/dist/cli/commands/config.d.ts +4 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +666 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/implement.d.ts.map +1 -1
- package/dist/cli/commands/implement.js +149 -31
- package/dist/cli/commands/implement.js.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -69
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/port.d.ts +32 -0
- package/dist/cli/commands/port.d.ts.map +1 -0
- package/dist/cli/commands/port.js +554 -0
- package/dist/cli/commands/port.js.map +1 -0
- package/dist/cli/commands/pr/close.d.ts +15 -0
- package/dist/cli/commands/pr/close.d.ts.map +1 -0
- package/dist/cli/commands/pr/close.js +71 -0
- package/dist/cli/commands/pr/close.js.map +1 -0
- package/dist/cli/commands/pr/create.d.ts +17 -0
- package/dist/cli/commands/pr/create.d.ts.map +1 -1
- package/dist/cli/commands/pr/create.js +208 -7
- package/dist/cli/commands/pr/create.js.map +1 -1
- package/dist/cli/commands/pr/fixComments.d.ts +5 -2
- package/dist/cli/commands/pr/fixComments.d.ts.map +1 -1
- package/dist/cli/commands/pr/fixComments.js +5 -13
- package/dist/cli/commands/pr/fixComments.js.map +1 -1
- package/dist/cli/commands/pr/index.d.ts.map +1 -1
- package/dist/cli/commands/pr/index.js +6 -2
- package/dist/cli/commands/pr/index.js.map +1 -1
- package/dist/cli/commands/pr/list.d.ts.map +1 -1
- package/dist/cli/commands/pr/list.js +24 -4
- package/dist/cli/commands/pr/list.js.map +1 -1
- package/dist/cli/commands/pr/merge.d.ts +23 -0
- package/dist/cli/commands/pr/merge.d.ts.map +1 -0
- package/dist/cli/commands/pr/merge.js +191 -0
- package/dist/cli/commands/pr/merge.js.map +1 -0
- package/dist/cli/commands/pr/resolve.d.ts +3 -0
- package/dist/cli/commands/pr/resolve.d.ts.map +1 -0
- package/dist/cli/commands/pr/resolve.js +92 -0
- package/dist/cli/commands/pr/resolve.js.map +1 -0
- package/dist/cli/commands/pr/review.d.ts.map +1 -1
- package/dist/cli/commands/pr/review.js +121 -6
- package/dist/cli/commands/pr/review.js.map +1 -1
- package/dist/cli/commands/push.d.ts +16 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +166 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +24 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +414 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +34 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config.d.ts +20 -3
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +98 -45
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +61 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/core/context.d.ts +6 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/gitx.d.ts +43 -0
- package/dist/core/gitx.d.ts.map +1 -1
- package/dist/core/gitx.js +187 -20
- package/dist/core/gitx.js.map +1 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/azure.d.ts +26 -0
- package/dist/providers/azure.d.ts.map +1 -0
- package/dist/providers/azure.js +256 -0
- package/dist/providers/azure.js.map +1 -0
- package/dist/providers/base.d.ts +104 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +5 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/factory.d.ts +8 -0
- package/dist/providers/factory.d.ts.map +1 -0
- package/dist/providers/factory.js +25 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/github.d.ts +19 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +291 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/gitlab.d.ts +19 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +186 -0
- package/dist/providers/gitlab.js.map +1 -0
- package/dist/types/config.d.ts +50 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/utils/azureAuth.d.ts +51 -0
- package/dist/utils/azureAuth.d.ts.map +1 -0
- package/dist/utils/azureAuth.js +172 -0
- package/dist/utils/azureAuth.js.map +1 -0
- package/dist/utils/git.d.ts +19 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +45 -8
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/gitOps.d.ts +125 -0
- package/dist/utils/gitOps.d.ts.map +1 -0
- package/dist/utils/gitOps.js +396 -0
- package/dist/utils/gitOps.js.map +1 -0
- package/dist/utils/lockFile.d.ts +13 -0
- package/dist/utils/lockFile.d.ts.map +1 -0
- package/dist/utils/lockFile.js +54 -0
- package/dist/utils/lockFile.js.map +1 -0
- package/dist/utils/retry.d.ts +10 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +31 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/workflows/implement.d.ts +41 -0
- package/dist/workflows/implement.d.ts.map +1 -0
- package/dist/workflows/implement.js +219 -0
- package/dist/workflows/implement.js.map +1 -0
- package/dist/workflows/pr.d.ts +41 -0
- package/dist/workflows/pr.d.ts.map +1 -0
- package/dist/workflows/pr.js +291 -0
- package/dist/workflows/pr.js.map +1 -0
- package/dist/workflows/prAddress.d.ts +55 -0
- package/dist/workflows/prAddress.d.ts.map +1 -0
- package/dist/workflows/prAddress.js +349 -0
- package/dist/workflows/prAddress.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude AI integration via the Anthropic Messages API.
|
|
3
|
+
*
|
|
4
|
+
* Authentication: reads ANTHROPIC_API_KEY from the environment.
|
|
5
|
+
* Model: defaults to claude-3-5-haiku-20241022 (fast, affordable).
|
|
6
|
+
* Override via GITX_AI_MODEL env var.
|
|
7
|
+
*
|
|
8
|
+
* All methods send a structured system prompt and parse the JSON response.
|
|
9
|
+
* If parsing fails we fall back gracefully rather than crashing.
|
|
10
|
+
*/
|
|
11
|
+
import axios, { isAxiosError } from "axios";
|
|
12
|
+
import { GitxError } from "../utils/errors.js";
|
|
13
|
+
const ANTHROPIC_API = "https://api.anthropic.com/v1/messages";
|
|
14
|
+
const DEFAULT_MODEL = "claude-3-5-haiku-20241022";
|
|
15
|
+
const MAX_TOKENS = 4096;
|
|
16
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
17
|
+
function getModel(override) {
|
|
18
|
+
return process.env["GITX_AI_MODEL"] ?? override ?? DEFAULT_MODEL;
|
|
19
|
+
}
|
|
20
|
+
async function callClaude(system, userPrompt, apiKey, model) {
|
|
21
|
+
const body = {
|
|
22
|
+
model,
|
|
23
|
+
max_tokens: MAX_TOKENS,
|
|
24
|
+
system,
|
|
25
|
+
messages: [{ role: "user", content: userPrompt }],
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
const { data } = await axios.post(ANTHROPIC_API, body, {
|
|
29
|
+
headers: {
|
|
30
|
+
"x-api-key": apiKey,
|
|
31
|
+
"anthropic-version": "2023-06-01",
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
},
|
|
34
|
+
timeout: 60_000,
|
|
35
|
+
});
|
|
36
|
+
const text = data.content.find((c) => c.type === "text")?.text ?? "";
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (isAxiosError(err)) {
|
|
41
|
+
const status = err.response?.status;
|
|
42
|
+
const msg = err.response?.data?.error ?? err.message;
|
|
43
|
+
if (status === 401) {
|
|
44
|
+
throw new GitxError("Anthropic API authentication failed. Check ANTHROPIC_API_KEY.", { exitCode: 1, cause: err });
|
|
45
|
+
}
|
|
46
|
+
throw new GitxError(`Anthropic API error (${status ?? "network"}): ${String(msg)}`, { exitCode: 1, cause: err });
|
|
47
|
+
}
|
|
48
|
+
throw new GitxError(`Unexpected AI error: ${String(err)}`, { exitCode: 1, cause: err });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract JSON from a Claude response that may include markdown code fences.
|
|
53
|
+
* Claude sometimes wraps JSON in ```json ... ``` blocks.
|
|
54
|
+
*/
|
|
55
|
+
function extractJson(text) {
|
|
56
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
57
|
+
if (fenced?.[1])
|
|
58
|
+
return fenced[1].trim();
|
|
59
|
+
// Find first { or [ and last } or ]
|
|
60
|
+
const start = text.search(/[{[]/);
|
|
61
|
+
const endBrace = text.lastIndexOf("}");
|
|
62
|
+
const endBracket = text.lastIndexOf("]");
|
|
63
|
+
const end = Math.max(endBrace, endBracket);
|
|
64
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
65
|
+
return text.slice(start, end + 1);
|
|
66
|
+
}
|
|
67
|
+
return text.trim();
|
|
68
|
+
}
|
|
69
|
+
function parseJson(text, fallback) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(extractJson(text));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ─── ClaudeAi ─────────────────────────────────────────────────────────────────
|
|
78
|
+
export class ClaudeAi {
|
|
79
|
+
apiKey;
|
|
80
|
+
model;
|
|
81
|
+
/**
|
|
82
|
+
* @param apiKey Anthropic API key. Falls back to ANTHROPIC_API_KEY env var.
|
|
83
|
+
* @param model Model override. Falls back to GITX_AI_MODEL env var then default.
|
|
84
|
+
*/
|
|
85
|
+
constructor(apiKey, model) {
|
|
86
|
+
const key = apiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
87
|
+
if (!key) {
|
|
88
|
+
throw new GitxError("No Anthropic API key available. Run `gitx config setup` or set ANTHROPIC_API_KEY.", { exitCode: 2 });
|
|
89
|
+
}
|
|
90
|
+
this.apiKey = key;
|
|
91
|
+
this.model = getModel(model);
|
|
92
|
+
}
|
|
93
|
+
/** Check whether an API key is available without instantiating the class. */
|
|
94
|
+
static isAvailable(key) {
|
|
95
|
+
return Boolean(key ?? process.env["ANTHROPIC_API_KEY"]);
|
|
96
|
+
}
|
|
97
|
+
async analyzeTask(input) {
|
|
98
|
+
const system = `You are an expert software engineer. Analyze the given development task and respond with ONLY valid JSON matching this exact structure (no prose, no markdown, just raw JSON):
|
|
99
|
+
{
|
|
100
|
+
"task": "<the original task string>",
|
|
101
|
+
"intent": "<one of: refactor | bugfix | feature | chore | unknown>",
|
|
102
|
+
"summary": "<one sentence explaining what needs to be done>",
|
|
103
|
+
"assumptions": ["<assumption 1>", "<assumption 2>"],
|
|
104
|
+
"risks": ["<risk 1>", "<risk 2>"]
|
|
105
|
+
}`;
|
|
106
|
+
const text = await callClaude(system, `Task: ${input}`, this.apiKey, this.model);
|
|
107
|
+
const parsed = parseJson(text, {});
|
|
108
|
+
return {
|
|
109
|
+
task: input,
|
|
110
|
+
intent: parsed.intent ?? "unknown",
|
|
111
|
+
summary: parsed.summary ?? "Unable to analyze task.",
|
|
112
|
+
assumptions: Array.isArray(parsed.assumptions) ? parsed.assumptions : [],
|
|
113
|
+
risks: Array.isArray(parsed.risks) ? parsed.risks : [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async generatePlan(context) {
|
|
117
|
+
const ctx = context;
|
|
118
|
+
const taskDesc = ctx.task ?? "Unknown task";
|
|
119
|
+
const analysisSummary = ctx.analysis?.summary ?? "";
|
|
120
|
+
const fileList = ctx.repoFiles?.slice(0, 50).join("\n") ?? "(not provided)";
|
|
121
|
+
const fileContentsSection = ctx.fileContents && Object.keys(ctx.fileContents).length > 0
|
|
122
|
+
? `\n\nRelevant file contents:\n${Object.entries(ctx.fileContents)
|
|
123
|
+
.map(([p, c]) => `--- ${p} ---\n${c}`)
|
|
124
|
+
.join("\n\n")}`
|
|
125
|
+
: "";
|
|
126
|
+
const system = `You are an expert software engineer creating a step-by-step implementation plan. Respond with ONLY valid JSON:
|
|
127
|
+
{
|
|
128
|
+
"steps": [
|
|
129
|
+
{ "id": "step-1", "title": "<short title>", "description": "<detailed description of what to change and why>" },
|
|
130
|
+
{ "id": "step-2", "title": "<short title>", "description": "<detailed description>" }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
Keep steps atomic and ordered. Each step should touch one logical concern.`;
|
|
134
|
+
const userPrompt = `Task: ${taskDesc}
|
|
135
|
+
Analysis: ${analysisSummary}
|
|
136
|
+
Repo files (top 50):
|
|
137
|
+
${fileList}${fileContentsSection}`;
|
|
138
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
139
|
+
const parsed = parseJson(text, { steps: [] });
|
|
140
|
+
const steps = Array.isArray(parsed.steps) ? parsed.steps : [];
|
|
141
|
+
if (steps.length === 0) {
|
|
142
|
+
return {
|
|
143
|
+
steps: [
|
|
144
|
+
{ id: "step-1", title: "Analyze & implement", description: taskDesc },
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { steps };
|
|
149
|
+
}
|
|
150
|
+
async generateDiffs(step) {
|
|
151
|
+
const s = step;
|
|
152
|
+
const stepId = s.id ?? "step-1";
|
|
153
|
+
const fileContentsSection = s.fileContents && Object.keys(s.fileContents).length > 0
|
|
154
|
+
? `\n\nCurrent file contents (apply changes to these):\n${Object.entries(s.fileContents)
|
|
155
|
+
.map(([p, c]) => `--- ${p} ---\n${c}`)
|
|
156
|
+
.join("\n\n")}`
|
|
157
|
+
: "\n\n(No existing file contents provided — create new files as needed.)";
|
|
158
|
+
const system = `You are an expert software engineer. Generate unified diffs for the given implementation step.
|
|
159
|
+
|
|
160
|
+
Respond with ONLY valid JSON:
|
|
161
|
+
{
|
|
162
|
+
"stepId": "<step id>",
|
|
163
|
+
"diffs": [
|
|
164
|
+
{
|
|
165
|
+
"path": "<relative file path>",
|
|
166
|
+
"unifiedDiff": "<valid unified diff content starting with --- a/path and +++ b/path>"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Rules for unified diffs:
|
|
172
|
+
- Start with: --- a/<path>\\n+++ b/<path>
|
|
173
|
+
- Use @@ -<start>,<count> +<start>,<count> @@ context headers
|
|
174
|
+
- Lines starting with ' ' are context (unchanged)
|
|
175
|
+
- Lines starting with '-' are removed
|
|
176
|
+
- Lines starting with '+' are added
|
|
177
|
+
- For new files use: --- /dev/null\\n+++ b/<path>
|
|
178
|
+
- For deleted files use: --- a/<path>\\n+++ /dev/null
|
|
179
|
+
- Always include 3 lines of context around changes
|
|
180
|
+
- Make minimal, precise changes`;
|
|
181
|
+
const userPrompt = `Task: ${s.task ?? ""}
|
|
182
|
+
Step ID: ${stepId}
|
|
183
|
+
Step Title: ${s.title ?? ""}
|
|
184
|
+
Step Description: ${s.description ?? ""}${fileContentsSection}`;
|
|
185
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
186
|
+
const parsed = parseJson(text, { stepId, diffs: [] });
|
|
187
|
+
return {
|
|
188
|
+
stepId,
|
|
189
|
+
diffs: Array.isArray(parsed.diffs) ? parsed.diffs : [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async summarizeChanges(diff) {
|
|
193
|
+
const d = diff;
|
|
194
|
+
const diffContent = d.rawDiff ??
|
|
195
|
+
(d.diffs ?? [])
|
|
196
|
+
.flatMap((dr) => dr.diffs.map((f) => `File: ${f.path}\n${f.unifiedDiff}`))
|
|
197
|
+
.join("\n\n") ??
|
|
198
|
+
"(no diffs)";
|
|
199
|
+
const system = `You are a technical writer. Summarize code changes for a pull request. Respond with ONLY valid JSON:
|
|
200
|
+
{
|
|
201
|
+
"summary": "<2-3 sentence plain-English summary of what changed and why>",
|
|
202
|
+
"filesChanged": [
|
|
203
|
+
{ "path": "<file path>", "changeType": "<add | modify | delete>" }
|
|
204
|
+
]
|
|
205
|
+
}`;
|
|
206
|
+
const text = await callClaude(system, `Changes:\n${diffContent}`, this.apiKey, this.model);
|
|
207
|
+
const parsed = parseJson(text, {
|
|
208
|
+
summary: "",
|
|
209
|
+
filesChanged: [],
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
summary: parsed.summary ?? "Code changes applied.",
|
|
213
|
+
filesChanged: Array.isArray(parsed.filesChanged) ? parsed.filesChanged : [],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async suggestFixes(comment) {
|
|
217
|
+
const c = comment;
|
|
218
|
+
const commentsText = (c.comments ?? [])
|
|
219
|
+
.map((co) => `[${co.author ?? "reviewer"}${co.path ? ` on ${co.path}:${co.line ?? ""}` : ""}]: ${co.body}`)
|
|
220
|
+
.join("\n\n");
|
|
221
|
+
const fileContentsSection = c.fileContents && Object.keys(c.fileContents).length > 0
|
|
222
|
+
? `\n\nCurrent file contents:\n${Object.entries(c.fileContents)
|
|
223
|
+
.map(([p, content]) => `--- ${p} ---\n${content}`)
|
|
224
|
+
.join("\n\n")}`
|
|
225
|
+
: "";
|
|
226
|
+
const system = `You are an expert code reviewer suggesting fixes for pull request review comments. Respond with ONLY valid JSON:
|
|
227
|
+
{
|
|
228
|
+
"suggestedEdits": [
|
|
229
|
+
{
|
|
230
|
+
"path": "<file path>",
|
|
231
|
+
"rationale": "<why this change addresses the comment>",
|
|
232
|
+
"unifiedDiff": "<valid unified diff>"
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
If a comment doesn't require a code change, omit it from suggestedEdits.`;
|
|
237
|
+
const userPrompt = `PR Title: ${c.prTitle ?? ""}
|
|
238
|
+
PR Body: ${c.prBody ?? ""}
|
|
239
|
+
|
|
240
|
+
Review Comments:
|
|
241
|
+
${commentsText}${fileContentsSection}`;
|
|
242
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
243
|
+
const parsed = parseJson(text, { suggestedEdits: [] });
|
|
244
|
+
return {
|
|
245
|
+
suggestedEdits: Array.isArray(parsed.suggestedEdits) ? parsed.suggestedEdits : [],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async reviewPR(context) {
|
|
249
|
+
const ctx = context;
|
|
250
|
+
const diffSection = ctx.diff
|
|
251
|
+
? `\n\nDiff (unified):\n${ctx.diff.slice(0, 8000)}`
|
|
252
|
+
: "";
|
|
253
|
+
const commentsSection = ctx.comments && ctx.comments.length > 0
|
|
254
|
+
? `\n\nExisting review comments:\n${ctx.comments.map((c) => `[${c.author}${c.path ? ` @ ${c.path}` : ""}]: ${c.body}`).join("\n")}`
|
|
255
|
+
: "";
|
|
256
|
+
const system = `You are an expert code reviewer. Review the given pull request thoroughly and respond with ONLY valid JSON:
|
|
257
|
+
{
|
|
258
|
+
"summary": "<2-4 sentence overall assessment>",
|
|
259
|
+
"issues": [
|
|
260
|
+
{ "severity": "<critical|warning|suggestion>", "description": "<specific issue>", "file": "<optional file>", "line": null }
|
|
261
|
+
],
|
|
262
|
+
"positives": ["<good thing 1>", "<good thing 2>"],
|
|
263
|
+
"verdict": "<approve|request_changes|comment>"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Severity guide:
|
|
267
|
+
- critical: bugs, security issues, data loss risk
|
|
268
|
+
- warning: code quality, performance, missing error handling
|
|
269
|
+
- suggestion: style, naming, minor improvements`;
|
|
270
|
+
const userPrompt = `PR Title: ${ctx.prTitle ?? ""}
|
|
271
|
+
PR Description: ${ctx.prBody ?? ""}${diffSection}${commentsSection}`;
|
|
272
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
273
|
+
const parsed = parseJson(text, {
|
|
274
|
+
summary: "",
|
|
275
|
+
issues: [],
|
|
276
|
+
positives: [],
|
|
277
|
+
verdict: "comment",
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
summary: parsed.summary ?? "Review could not be generated.",
|
|
281
|
+
issues: Array.isArray(parsed.issues) ? parsed.issues : [],
|
|
282
|
+
positives: Array.isArray(parsed.positives) ? parsed.positives : [],
|
|
283
|
+
verdict: (["approve", "request_changes", "comment"].includes(parsed.verdict ?? ""))
|
|
284
|
+
? parsed.verdict
|
|
285
|
+
: "comment",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async generatePrContent(commits, diff, stat) {
|
|
289
|
+
const system = `You are an expert software engineer writing pull request descriptions.
|
|
290
|
+
You are given a list of commits on the branch and the unified diff of all changes.
|
|
291
|
+
|
|
292
|
+
The input may contain:
|
|
293
|
+
- "=== Changed files (complete list) ===" — the full --stat summary of every file touched
|
|
294
|
+
- "=== Detailed diff ===" — the actual patch (may be truncated for large changesets)
|
|
295
|
+
|
|
296
|
+
When a file list is present, use it as the authoritative source of ALL changed files.
|
|
297
|
+
Do not ignore files that appear in the list but are absent from the truncated diff.
|
|
298
|
+
|
|
299
|
+
Produce a clear, informative PR title and description:
|
|
300
|
+
|
|
301
|
+
Rules:
|
|
302
|
+
- title: short, human-readable, present-tense (e.g. "Add user authentication flow")
|
|
303
|
+
No conventional-commit prefix needed. Max 72 chars.
|
|
304
|
+
- body: 2-4 sentences describing WHAT changed and WHY. Cover ALL files from the list.
|
|
305
|
+
Plain English. Do not repeat the title. Do not use bullet points.
|
|
306
|
+
|
|
307
|
+
Respond with ONLY valid JSON (no markdown fences):
|
|
308
|
+
{"title":"<PR title>","body":"<PR description>"}`;
|
|
309
|
+
const commitList = commits.slice(0, 20).join("\n");
|
|
310
|
+
const diffSection = stat
|
|
311
|
+
? `=== Changed files (complete list) ===\n${stat}\n\n=== Detailed diff ===\n${diff.slice(0, 16000)}`
|
|
312
|
+
: `Diff:\n${diff.slice(0, 16000)}`;
|
|
313
|
+
const userPrompt = `Commits on this branch:\n${commitList}\n\n${diffSection}`;
|
|
314
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
315
|
+
const parsed = parseJson(text, {});
|
|
316
|
+
return {
|
|
317
|
+
title: parsed.title?.trim() ?? "Update branch",
|
|
318
|
+
body: parsed.body?.trim() ?? "",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async resolveConflict(filePath, conflictContent) {
|
|
322
|
+
const system = `You are an expert software engineer resolving git merge conflicts.
|
|
323
|
+
|
|
324
|
+
The file contains standard git conflict markers:
|
|
325
|
+
<<<<<<< HEAD (or <<<<<<< ours) — changes on the current branch
|
|
326
|
+
======= — separator
|
|
327
|
+
>>>>>>> branch (or >>>>>>> theirs) — incoming changes being merged/rebased
|
|
328
|
+
|
|
329
|
+
Your task:
|
|
330
|
+
1. Understand BOTH sides of every conflict in the file.
|
|
331
|
+
2. Produce a single correct version that preserves the intent of BOTH changes where possible.
|
|
332
|
+
3. If the two sides are genuinely contradictory and cannot be safely merged, set confidence to "low".
|
|
333
|
+
|
|
334
|
+
Rules:
|
|
335
|
+
- Remove ALL conflict markers (<<<<<<, =======, >>>>>>>) from the output.
|
|
336
|
+
- Do NOT add comments explaining what you did.
|
|
337
|
+
- Keep all non-conflicting code exactly as-is.
|
|
338
|
+
- The output must be syntactically valid for the file type.
|
|
339
|
+
|
|
340
|
+
Respond with ONLY valid JSON (no markdown fences):
|
|
341
|
+
{"resolved":"<full resolved file content>","confidence":"high|low","explanation":"<one sentence>"}`;
|
|
342
|
+
const userPrompt = `File: ${filePath}\n\n${conflictContent.slice(0, 20000)}`;
|
|
343
|
+
const text = await callClaude(system, userPrompt, this.apiKey, this.model);
|
|
344
|
+
const parsed = parseJson(text, {});
|
|
345
|
+
return {
|
|
346
|
+
resolved: parsed.resolved ?? conflictContent,
|
|
347
|
+
confidence: parsed.confidence === "low" ? "low" : "high",
|
|
348
|
+
explanation: parsed.explanation?.trim() ?? "Conflict resolved.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async generateCommitMessage(diff) {
|
|
352
|
+
const system = `You are an expert software engineer writing git commit messages.
|
|
353
|
+
You receive either a plain unified diff OR a structured input with:
|
|
354
|
+
- "=== Changed files (complete list) ===" — the full --stat summary of every file touched
|
|
355
|
+
- "=== Detailed diff ===" — the actual patch (may be truncated for large changesets)
|
|
356
|
+
|
|
357
|
+
When a file list is present, use it as the authoritative source of ALL changes.
|
|
358
|
+
Do not ignore files that appear in the list but are missing from the truncated diff.
|
|
359
|
+
|
|
360
|
+
Produce ONE CONVENTIONAL COMMIT covering all the changes:
|
|
361
|
+
|
|
362
|
+
Rules:
|
|
363
|
+
- subject: "<type>(<scope>): <imperative description>"
|
|
364
|
+
- type: feat | fix | refactor | chore | docs | test | perf | ci | style | build
|
|
365
|
+
- scope: the primary area affected; omit if changes span many unrelated areas
|
|
366
|
+
- description: imperative mood, no period, 72 chars max for the whole subject line
|
|
367
|
+
- if several distinct features or fixes are present, pick the most impactful for the subject
|
|
368
|
+
- body: cover EVERY significant change visible in the file list. For each distinct change,
|
|
369
|
+
one sentence on what was done and why. Plain English, no bullet lists, no repetition of the subject.
|
|
370
|
+
|
|
371
|
+
Respond with ONLY valid JSON (no markdown fences):
|
|
372
|
+
{"subject":"<subject line>","body":"<body covering all changes, or empty string>"}`;
|
|
373
|
+
const text = await callClaude(system, `Diff:\n${diff.slice(0, 20000)}`, this.apiKey, this.model);
|
|
374
|
+
const parsed = parseJson(text, {});
|
|
375
|
+
return {
|
|
376
|
+
subject: parsed.subject?.trim() ?? "chore: update files",
|
|
377
|
+
body: parsed.body?.trim() || undefined,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
async reviewPRDetailed(context) {
|
|
381
|
+
const { buildSeniorReviewSystem, buildSeniorReviewPrompt, parseSeniorReview } = await import("./reviewHelpers.js");
|
|
382
|
+
const text = await callClaude(buildSeniorReviewSystem(), buildSeniorReviewPrompt(context), this.apiKey, this.model);
|
|
383
|
+
return parseSeniorReview(text);
|
|
384
|
+
}
|
|
385
|
+
async generateFix(context) {
|
|
386
|
+
const { buildFixSystem, buildFixPrompt, parseFixResponse } = await import("./reviewHelpers.js");
|
|
387
|
+
const text = await callClaude(buildFixSystem(), buildFixPrompt(context), this.apiKey, this.model);
|
|
388
|
+
return parseFixResponse(text, context.filePath, context.line);
|
|
389
|
+
}
|
|
390
|
+
async ask(question, context) {
|
|
391
|
+
const { buildAskSystem, buildAskPrompt, parseAskResponse } = await import("./reviewHelpers.js");
|
|
392
|
+
const text = await callClaude(buildAskSystem(), buildAskPrompt(question, context), this.apiKey, this.model);
|
|
393
|
+
return parseAskResponse(text);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=claudeAi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeAi.js","sourceRoot":"","sources":["../../src/ai/claudeAi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Claude AI integration via the Anthropic Messages API.\n *\n * Authentication: reads ANTHROPIC_API_KEY from the environment.\n * Model: defaults to claude-3-5-haiku-20241022 (fast, affordable).\n * Override via GITX_AI_MODEL env var.\n *\n * All methods send a structured system prompt and parse the JSON response.\n * If parsing fails we fall back gracefully rather than crashing.\n */\n\nimport axios, { isAxiosError } from \"axios\";\nimport { GitxError } from \"../utils/errors.js\";\nimport type {\n AiAnalyzeTaskResponse,\n AiClient,\n AiGenerateDiffsResponse,\n AiGeneratePlanResponse,\n AiReviewPRResponse,\n AiSuggestFixesResponse,\n AiSummarizeChangesResponse,\n} from \"./types.js\";\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\";\nconst DEFAULT_MODEL = \"claude-3-5-haiku-20241022\";\nconst MAX_TOKENS = 4096;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction getModel(override?: string): string {\n return process.env[\"GITX_AI_MODEL\"] ?? override ?? DEFAULT_MODEL;\n}\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\ninterface ClaudeRequestBody {\n model: string;\n max_tokens: number;\n system: string;\n messages: ClaudeMessage[];\n}\n\ninterface ClaudeResponseBody {\n content: Array<{ type: string; text: string }>;\n}\n\nasync function callClaude(system: string, userPrompt: string, apiKey: string, model: string): Promise<string> {\n const body: ClaudeRequestBody = {\n model,\n max_tokens: MAX_TOKENS,\n system,\n messages: [{ role: \"user\", content: userPrompt }],\n };\n\n try {\n const { data } = await axios.post<ClaudeResponseBody>(ANTHROPIC_API, body, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n timeout: 60_000,\n });\n\n const text = data.content.find((c) => c.type === \"text\")?.text ?? \"\";\n return text;\n } catch (err) {\n if (isAxiosError(err)) {\n const status = err.response?.status;\n const msg = (err.response?.data as Record<string, unknown> | undefined)?.error ?? err.message;\n if (status === 401) {\n throw new GitxError(\n \"Anthropic API authentication failed. Check ANTHROPIC_API_KEY.\",\n { exitCode: 1, cause: err }\n );\n }\n throw new GitxError(\n `Anthropic API error (${status ?? \"network\"}): ${String(msg)}`,\n { exitCode: 1, cause: err }\n );\n }\n throw new GitxError(`Unexpected AI error: ${String(err)}`, { exitCode: 1, cause: err });\n }\n}\n\n/**\n * Extract JSON from a Claude response that may include markdown code fences.\n * Claude sometimes wraps JSON in ```json ... ``` blocks.\n */\nfunction extractJson(text: string): string {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (fenced?.[1]) return fenced[1].trim();\n // Find first { or [ and last } or ]\n const start = text.search(/[{[]/);\n const endBrace = text.lastIndexOf(\"}\");\n const endBracket = text.lastIndexOf(\"]\");\n const end = Math.max(endBrace, endBracket);\n if (start !== -1 && end !== -1 && end > start) {\n return text.slice(start, end + 1);\n }\n return text.trim();\n}\n\nfunction parseJson<T>(text: string, fallback: T): T {\n try {\n return JSON.parse(extractJson(text)) as T;\n } catch {\n return fallback;\n }\n}\n\n// ─── ClaudeAi ─────────────────────────────────────────────────────────────────\n\nexport class ClaudeAi implements AiClient {\n private readonly apiKey: string;\n private readonly model: string;\n\n /**\n * @param apiKey Anthropic API key. Falls back to ANTHROPIC_API_KEY env var.\n * @param model Model override. Falls back to GITX_AI_MODEL env var then default.\n */\n constructor(apiKey?: string, model?: string) {\n const key = apiKey ?? process.env[\"ANTHROPIC_API_KEY\"];\n if (!key) {\n throw new GitxError(\n \"No Anthropic API key available. Run `gitx config setup` or set ANTHROPIC_API_KEY.\",\n { exitCode: 2 }\n );\n }\n this.apiKey = key;\n this.model = getModel(model);\n }\n\n /** Check whether an API key is available without instantiating the class. */\n static isAvailable(key?: string): boolean {\n return Boolean(key ?? process.env[\"ANTHROPIC_API_KEY\"]);\n }\n\n async analyzeTask(input: string): Promise<AiAnalyzeTaskResponse> {\n const system = `You are an expert software engineer. Analyze the given development task and respond with ONLY valid JSON matching this exact structure (no prose, no markdown, just raw JSON):\n{\n \"task\": \"<the original task string>\",\n \"intent\": \"<one of: refactor | bugfix | feature | chore | unknown>\",\n \"summary\": \"<one sentence explaining what needs to be done>\",\n \"assumptions\": [\"<assumption 1>\", \"<assumption 2>\"],\n \"risks\": [\"<risk 1>\", \"<risk 2>\"]\n}`;\n\n const text = await callClaude(system, `Task: ${input}`, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiAnalyzeTaskResponse>>(text, {});\n return {\n task: input,\n intent: (parsed.intent as AiAnalyzeTaskResponse[\"intent\"]) ?? \"unknown\",\n summary: parsed.summary ?? \"Unable to analyze task.\",\n assumptions: Array.isArray(parsed.assumptions) ? parsed.assumptions : [],\n risks: Array.isArray(parsed.risks) ? parsed.risks : [],\n };\n }\n\n async generatePlan(context: unknown): Promise<AiGeneratePlanResponse> {\n const ctx = context as {\n task?: string;\n analysis?: AiAnalyzeTaskResponse;\n repoFiles?: string[];\n fileContents?: Record<string, string>;\n };\n\n const taskDesc = ctx.task ?? \"Unknown task\";\n const analysisSummary = ctx.analysis?.summary ?? \"\";\n const fileList = ctx.repoFiles?.slice(0, 50).join(\"\\n\") ?? \"(not provided)\";\n const fileContentsSection =\n ctx.fileContents && Object.keys(ctx.fileContents).length > 0\n ? `\\n\\nRelevant file contents:\\n${Object.entries(ctx.fileContents)\n .map(([p, c]) => `--- ${p} ---\\n${c}`)\n .join(\"\\n\\n\")}`\n : \"\";\n\n const system = `You are an expert software engineer creating a step-by-step implementation plan. Respond with ONLY valid JSON:\n{\n \"steps\": [\n { \"id\": \"step-1\", \"title\": \"<short title>\", \"description\": \"<detailed description of what to change and why>\" },\n { \"id\": \"step-2\", \"title\": \"<short title>\", \"description\": \"<detailed description>\" }\n ]\n}\nKeep steps atomic and ordered. Each step should touch one logical concern.`;\n\n const userPrompt = `Task: ${taskDesc}\nAnalysis: ${analysisSummary}\nRepo files (top 50):\n${fileList}${fileContentsSection}`;\n\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiGeneratePlanResponse>>(text, { steps: [] });\n const steps = Array.isArray(parsed.steps) ? parsed.steps : [];\n\n if (steps.length === 0) {\n return {\n steps: [\n { id: \"step-1\", title: \"Analyze & implement\", description: taskDesc },\n ],\n };\n }\n return { steps };\n }\n\n async generateDiffs(step: unknown): Promise<AiGenerateDiffsResponse> {\n const s = step as {\n id?: string;\n title?: string;\n description?: string;\n task?: string;\n analysis?: AiAnalyzeTaskResponse;\n fileContents?: Record<string, string>;\n };\n\n const stepId = s.id ?? \"step-1\";\n const fileContentsSection =\n s.fileContents && Object.keys(s.fileContents).length > 0\n ? `\\n\\nCurrent file contents (apply changes to these):\\n${Object.entries(s.fileContents)\n .map(([p, c]) => `--- ${p} ---\\n${c}`)\n .join(\"\\n\\n\")}`\n : \"\\n\\n(No existing file contents provided — create new files as needed.)\";\n\n const system = `You are an expert software engineer. Generate unified diffs for the given implementation step.\n\nRespond with ONLY valid JSON:\n{\n \"stepId\": \"<step id>\",\n \"diffs\": [\n {\n \"path\": \"<relative file path>\",\n \"unifiedDiff\": \"<valid unified diff content starting with --- a/path and +++ b/path>\"\n }\n ]\n}\n\nRules for unified diffs:\n- Start with: --- a/<path>\\\\n+++ b/<path>\n- Use @@ -<start>,<count> +<start>,<count> @@ context headers\n- Lines starting with ' ' are context (unchanged)\n- Lines starting with '-' are removed\n- Lines starting with '+' are added\n- For new files use: --- /dev/null\\\\n+++ b/<path>\n- For deleted files use: --- a/<path>\\\\n+++ /dev/null\n- Always include 3 lines of context around changes\n- Make minimal, precise changes`;\n\n const userPrompt = `Task: ${s.task ?? \"\"}\nStep ID: ${stepId}\nStep Title: ${s.title ?? \"\"}\nStep Description: ${s.description ?? \"\"}${fileContentsSection}`;\n\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiGenerateDiffsResponse>>(text, { stepId, diffs: [] });\n return {\n stepId,\n diffs: Array.isArray(parsed.diffs) ? parsed.diffs : [],\n };\n }\n\n async summarizeChanges(diff: unknown): Promise<AiSummarizeChangesResponse> {\n const d = diff as {\n diffs?: AiGenerateDiffsResponse[];\n rawDiff?: string;\n };\n\n const diffContent =\n d.rawDiff ??\n (d.diffs ?? [])\n .flatMap((dr) => dr.diffs.map((f) => `File: ${f.path}\\n${f.unifiedDiff}`))\n .join(\"\\n\\n\") ??\n \"(no diffs)\";\n\n const system = `You are a technical writer. Summarize code changes for a pull request. Respond with ONLY valid JSON:\n{\n \"summary\": \"<2-3 sentence plain-English summary of what changed and why>\",\n \"filesChanged\": [\n { \"path\": \"<file path>\", \"changeType\": \"<add | modify | delete>\" }\n ]\n}`;\n\n const text = await callClaude(system, `Changes:\\n${diffContent}`, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiSummarizeChangesResponse>>(text, {\n summary: \"\",\n filesChanged: [],\n });\n return {\n summary: parsed.summary ?? \"Code changes applied.\",\n filesChanged: Array.isArray(parsed.filesChanged) ? parsed.filesChanged : [],\n };\n }\n\n async suggestFixes(comment: unknown): Promise<AiSuggestFixesResponse> {\n const c = comment as {\n comments?: Array<{ body: string; path?: string; line?: number; author?: string }>;\n prTitle?: string;\n prBody?: string;\n fileContents?: Record<string, string>;\n };\n\n const commentsText = (c.comments ?? [])\n .map(\n (co) =>\n `[${co.author ?? \"reviewer\"}${co.path ? ` on ${co.path}:${co.line ?? \"\"}` : \"\"}]: ${co.body}`\n )\n .join(\"\\n\\n\");\n\n const fileContentsSection =\n c.fileContents && Object.keys(c.fileContents).length > 0\n ? `\\n\\nCurrent file contents:\\n${Object.entries(c.fileContents)\n .map(([p, content]) => `--- ${p} ---\\n${content}`)\n .join(\"\\n\\n\")}`\n : \"\";\n\n const system = `You are an expert code reviewer suggesting fixes for pull request review comments. Respond with ONLY valid JSON:\n{\n \"suggestedEdits\": [\n {\n \"path\": \"<file path>\",\n \"rationale\": \"<why this change addresses the comment>\",\n \"unifiedDiff\": \"<valid unified diff>\"\n }\n ]\n}\nIf a comment doesn't require a code change, omit it from suggestedEdits.`;\n\n const userPrompt = `PR Title: ${c.prTitle ?? \"\"}\nPR Body: ${c.prBody ?? \"\"}\n\nReview Comments:\n${commentsText}${fileContentsSection}`;\n\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiSuggestFixesResponse>>(text, { suggestedEdits: [] });\n return {\n suggestedEdits: Array.isArray(parsed.suggestedEdits) ? parsed.suggestedEdits : [],\n };\n }\n\n async reviewPR(context: unknown): Promise<AiReviewPRResponse> {\n const ctx = context as {\n prTitle?: string;\n prBody?: string;\n diff?: string;\n comments?: Array<{ body: string; author: string; path?: string }>;\n };\n\n const diffSection = ctx.diff\n ? `\\n\\nDiff (unified):\\n${ctx.diff.slice(0, 8000)}`\n : \"\";\n const commentsSection =\n ctx.comments && ctx.comments.length > 0\n ? `\\n\\nExisting review comments:\\n${ctx.comments.map((c) => `[${c.author}${c.path ? ` @ ${c.path}` : \"\"}]: ${c.body}`).join(\"\\n\")}`\n : \"\";\n\n const system = `You are an expert code reviewer. Review the given pull request thoroughly and respond with ONLY valid JSON:\n{\n \"summary\": \"<2-4 sentence overall assessment>\",\n \"issues\": [\n { \"severity\": \"<critical|warning|suggestion>\", \"description\": \"<specific issue>\", \"file\": \"<optional file>\", \"line\": null }\n ],\n \"positives\": [\"<good thing 1>\", \"<good thing 2>\"],\n \"verdict\": \"<approve|request_changes|comment>\"\n}\n\nSeverity guide:\n- critical: bugs, security issues, data loss risk\n- warning: code quality, performance, missing error handling\n- suggestion: style, naming, minor improvements`;\n\n const userPrompt = `PR Title: ${ctx.prTitle ?? \"\"}\nPR Description: ${ctx.prBody ?? \"\"}${diffSection}${commentsSection}`;\n\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<AiReviewPRResponse>>(text, {\n summary: \"\",\n issues: [],\n positives: [],\n verdict: \"comment\",\n });\n\n return {\n summary: parsed.summary ?? \"Review could not be generated.\",\n issues: Array.isArray(parsed.issues) ? parsed.issues : [],\n positives: Array.isArray(parsed.positives) ? parsed.positives : [],\n verdict: ([\"approve\", \"request_changes\", \"comment\"].includes(parsed.verdict ?? \"\"))\n ? (parsed.verdict as AiReviewPRResponse[\"verdict\"])\n : \"comment\",\n };\n }\n\n async generatePrContent(commits: string[], diff: string, stat?: string): Promise<import(\"./types.js\").AiPrContentResponse> {\n const system = `You are an expert software engineer writing pull request descriptions.\nYou are given a list of commits on the branch and the unified diff of all changes.\n\nThe input may contain:\n - \"=== Changed files (complete list) ===\" — the full --stat summary of every file touched\n - \"=== Detailed diff ===\" — the actual patch (may be truncated for large changesets)\n\nWhen a file list is present, use it as the authoritative source of ALL changed files.\nDo not ignore files that appear in the list but are absent from the truncated diff.\n\nProduce a clear, informative PR title and description:\n\nRules:\n- title: short, human-readable, present-tense (e.g. \"Add user authentication flow\")\n No conventional-commit prefix needed. Max 72 chars.\n- body: 2-4 sentences describing WHAT changed and WHY. Cover ALL files from the list.\n Plain English. Do not repeat the title. Do not use bullet points.\n\nRespond with ONLY valid JSON (no markdown fences):\n{\"title\":\"<PR title>\",\"body\":\"<PR description>\"}`;\n\n const commitList = commits.slice(0, 20).join(\"\\n\");\n const diffSection = stat\n ? `=== Changed files (complete list) ===\\n${stat}\\n\\n=== Detailed diff ===\\n${diff.slice(0, 16000)}`\n : `Diff:\\n${diff.slice(0, 16000)}`;\n const userPrompt = `Commits on this branch:\\n${commitList}\\n\\n${diffSection}`;\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<import(\"./types.js\").AiPrContentResponse>>(text, {});\n return {\n title: parsed.title?.trim() ?? \"Update branch\",\n body: parsed.body?.trim() ?? \"\",\n };\n }\n\n async resolveConflict(filePath: string, conflictContent: string): Promise<import(\"./types.js\").AiConflictResolutionResponse> {\n const system = `You are an expert software engineer resolving git merge conflicts.\n\nThe file contains standard git conflict markers:\n <<<<<<< HEAD (or <<<<<<< ours) — changes on the current branch\n ======= — separator\n >>>>>>> branch (or >>>>>>> theirs) — incoming changes being merged/rebased\n\nYour task:\n1. Understand BOTH sides of every conflict in the file.\n2. Produce a single correct version that preserves the intent of BOTH changes where possible.\n3. If the two sides are genuinely contradictory and cannot be safely merged, set confidence to \"low\".\n\nRules:\n- Remove ALL conflict markers (<<<<<<, =======, >>>>>>>) from the output.\n- Do NOT add comments explaining what you did.\n- Keep all non-conflicting code exactly as-is.\n- The output must be syntactically valid for the file type.\n\nRespond with ONLY valid JSON (no markdown fences):\n{\"resolved\":\"<full resolved file content>\",\"confidence\":\"high|low\",\"explanation\":\"<one sentence>\"}`;\n\n const userPrompt = `File: ${filePath}\\n\\n${conflictContent.slice(0, 20000)}`;\n const text = await callClaude(system, userPrompt, this.apiKey, this.model);\n const parsed = parseJson<Partial<import(\"./types.js\").AiConflictResolutionResponse>>(text, {});\n return {\n resolved: parsed.resolved ?? conflictContent,\n confidence: parsed.confidence === \"low\" ? \"low\" : \"high\",\n explanation: parsed.explanation?.trim() ?? \"Conflict resolved.\",\n };\n }\n async generateCommitMessage(diff: string): Promise<import(\"./types.js\").AiCommitMessageResponse> {\n const system = `You are an expert software engineer writing git commit messages.\nYou receive either a plain unified diff OR a structured input with:\n - \"=== Changed files (complete list) ===\" — the full --stat summary of every file touched\n - \"=== Detailed diff ===\" — the actual patch (may be truncated for large changesets)\n\nWhen a file list is present, use it as the authoritative source of ALL changes.\nDo not ignore files that appear in the list but are missing from the truncated diff.\n\nProduce ONE CONVENTIONAL COMMIT covering all the changes:\n\nRules:\n- subject: \"<type>(<scope>): <imperative description>\"\n - type: feat | fix | refactor | chore | docs | test | perf | ci | style | build\n - scope: the primary area affected; omit if changes span many unrelated areas\n - description: imperative mood, no period, 72 chars max for the whole subject line\n - if several distinct features or fixes are present, pick the most impactful for the subject\n- body: cover EVERY significant change visible in the file list. For each distinct change,\n one sentence on what was done and why. Plain English, no bullet lists, no repetition of the subject.\n\nRespond with ONLY valid JSON (no markdown fences):\n{\"subject\":\"<subject line>\",\"body\":\"<body covering all changes, or empty string>\"}`;\n\n const text = await callClaude(system, `Diff:\\n${diff.slice(0, 20000)}`, this.apiKey, this.model);\n const parsed = parseJson<Partial<import(\"./types.js\").AiCommitMessageResponse>>(text, {});\n return {\n subject: parsed.subject?.trim() ?? \"chore: update files\",\n body: parsed.body?.trim() || undefined,\n };\n }\n\n async reviewPRDetailed(\n context: Parameters<import(\"./types.js\").AiClient[\"reviewPRDetailed\"]>[0]\n ): Promise<import(\"./types.js\").AiDetailedReviewResponse> {\n const { buildSeniorReviewSystem, buildSeniorReviewPrompt, parseSeniorReview } = await import(\"./reviewHelpers.js\");\n const text = await callClaude(\n buildSeniorReviewSystem(),\n buildSeniorReviewPrompt(context),\n this.apiKey,\n this.model\n );\n return parseSeniorReview(text);\n }\n\n async generateFix(\n context: Parameters<import(\"./types.js\").AiClient[\"generateFix\"]>[0]\n ): Promise<import(\"./types.js\").AiFixResponse> {\n const { buildFixSystem, buildFixPrompt, parseFixResponse } = await import(\"./reviewHelpers.js\");\n const text = await callClaude(\n buildFixSystem(),\n buildFixPrompt(context),\n this.apiKey,\n this.model\n );\n return parseFixResponse(text, context.filePath, context.line);\n }\n\n async ask(\n question: string,\n context: import(\"./types.js\").AiAskContext\n ): Promise<import(\"./types.js\").AiAskResponse> {\n const { buildAskSystem, buildAskPrompt, parseAskResponse } = await import(\"./reviewHelpers.js\");\n const text = await callClaude(buildAskSystem(), buildAskPrompt(question, context), this.apiKey, this.model);\n return parseAskResponse(text);\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeCliAi — uses the locally installed `claude` CLI (Claude Code)
|
|
3
|
+
* as an AI backend. No API key required; uses the user's existing Claude login.
|
|
4
|
+
*
|
|
5
|
+
* Detection: `claude --version`
|
|
6
|
+
* Invocation: `claude -p "<combined system+user prompt>"`
|
|
7
|
+
*/
|
|
8
|
+
import type { AiAnalyzeTaskResponse, AiClient, AiGenerateDiffsResponse, AiGeneratePlanResponse, AiReviewPRResponse, AiSuggestFixesResponse, AiSummarizeChangesResponse } from "./types.js";
|
|
9
|
+
export declare class ClaudeCliAi implements AiClient {
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if the `claude` CLI binary is installed and accessible.
|
|
12
|
+
*/
|
|
13
|
+
static isAvailable(): Promise<boolean>;
|
|
14
|
+
analyzeTask(input: string): Promise<AiAnalyzeTaskResponse>;
|
|
15
|
+
generatePlan(context: unknown): Promise<AiGeneratePlanResponse>;
|
|
16
|
+
generateDiffs(step: unknown): Promise<AiGenerateDiffsResponse>;
|
|
17
|
+
summarizeChanges(diff: unknown): Promise<AiSummarizeChangesResponse>;
|
|
18
|
+
suggestFixes(comment: unknown): Promise<AiSuggestFixesResponse>;
|
|
19
|
+
reviewPR(context: unknown): Promise<AiReviewPRResponse>;
|
|
20
|
+
generatePrContent(commits: string[], diff: string, stat?: string): Promise<import("./types.js").AiPrContentResponse>;
|
|
21
|
+
resolveConflict(filePath: string, conflictContent: string): Promise<import("./types.js").AiConflictResolutionResponse>;
|
|
22
|
+
generateCommitMessage(diff: string): Promise<import("./types.js").AiCommitMessageResponse>;
|
|
23
|
+
reviewPRDetailed(context: Parameters<import("./types.js").AiClient["reviewPRDetailed"]>[0]): Promise<import("./types.js").AiDetailedReviewResponse>;
|
|
24
|
+
generateFix(context: Parameters<import("./types.js").AiClient["generateFix"]>[0]): Promise<import("./types.js").AiFixResponse>;
|
|
25
|
+
ask(question: string, context: import("./types.js").AiAskContext): Promise<import("./types.js").AiAskResponse>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=claudeCliAi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeCliAi.d.ts","sourceRoot":"","sources":["../../src/ai/claudeCliAi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EACV,qBAAqB,EACrB,QAAQ,EACR,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AA6DpB,qBAAa,WAAY,YAAW,QAAQ;IAC1C;;OAEG;WACU,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAUtC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAe1D,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAiC/D,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAuC9D,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAoBpE,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAmC/D,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0CvD,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,YAAY,EAAE,mBAAmB,CAAC;IAkCpH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,YAAY,EAAE,4BAA4B,CAAC;IA+BtH,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,YAAY,EAAE,uBAAuB,CAAC;IA+B1F,gBAAgB,CACpB,OAAO,EAAE,UAAU,CAAC,OAAO,YAAY,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,GACxE,OAAO,CAAC,OAAO,YAAY,EAAE,wBAAwB,CAAC;IAUnD,WAAW,CACf,OAAO,EAAE,UAAU,CAAC,OAAO,YAAY,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GACnE,OAAO,CAAC,OAAO,YAAY,EAAE,aAAa,CAAC;IAUxC,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,GACzC,OAAO,CAAC,OAAO,YAAY,EAAE,aAAa,CAAC;CAK/C"}
|