@g-abhishek/gitx 0.1.1 → 0.1.4

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.
Files changed (161) hide show
  1. package/README.md +374 -3
  2. package/dist/ai/claudeAi.d.ts +35 -0
  3. package/dist/ai/claudeAi.d.ts.map +1 -0
  4. package/dist/ai/claudeAi.js +396 -0
  5. package/dist/ai/claudeAi.js.map +1 -0
  6. package/dist/ai/claudeCliAi.d.ts +27 -0
  7. package/dist/ai/claudeCliAi.d.ts.map +1 -0
  8. package/dist/ai/claudeCliAi.js +312 -0
  9. package/dist/ai/claudeCliAi.js.map +1 -0
  10. package/dist/ai/localClaudeAi.d.ts +2 -0
  11. package/dist/ai/localClaudeAi.d.ts.map +1 -0
  12. package/dist/ai/localClaudeAi.js +4 -0
  13. package/dist/ai/localClaudeAi.js.map +1 -0
  14. package/dist/ai/mockAi.d.ts +8 -1
  15. package/dist/ai/mockAi.d.ts.map +1 -1
  16. package/dist/ai/mockAi.js +57 -0
  17. package/dist/ai/mockAi.js.map +1 -1
  18. package/dist/ai/openAiAi.d.ts +33 -0
  19. package/dist/ai/openAiAi.d.ts.map +1 -0
  20. package/dist/ai/openAiAi.js +388 -0
  21. package/dist/ai/openAiAi.js.map +1 -0
  22. package/dist/ai/reviewHelpers.d.ts +66 -0
  23. package/dist/ai/reviewHelpers.d.ts.map +1 -0
  24. package/dist/ai/reviewHelpers.js +559 -0
  25. package/dist/ai/reviewHelpers.js.map +1 -0
  26. package/dist/ai/types.d.ts +247 -0
  27. package/dist/ai/types.d.ts.map +1 -1
  28. package/dist/ai/types.js.map +1 -1
  29. package/dist/cli/commands/ask.d.ts +27 -0
  30. package/dist/cli/commands/ask.d.ts.map +1 -0
  31. package/dist/cli/commands/ask.js +230 -0
  32. package/dist/cli/commands/ask.js.map +1 -0
  33. package/dist/cli/commands/commit.d.ts +16 -0
  34. package/dist/cli/commands/commit.d.ts.map +1 -0
  35. package/dist/cli/commands/commit.js +163 -0
  36. package/dist/cli/commands/commit.js.map +1 -0
  37. package/dist/cli/commands/config.d.ts +4 -0
  38. package/dist/cli/commands/config.d.ts.map +1 -0
  39. package/dist/cli/commands/config.js +666 -0
  40. package/dist/cli/commands/config.js.map +1 -0
  41. package/dist/cli/commands/implement.d.ts.map +1 -1
  42. package/dist/cli/commands/implement.js +149 -28
  43. package/dist/cli/commands/implement.js.map +1 -1
  44. package/dist/cli/commands/init.d.ts +4 -0
  45. package/dist/cli/commands/init.d.ts.map +1 -1
  46. package/dist/cli/commands/init.js +7 -54
  47. package/dist/cli/commands/init.js.map +1 -1
  48. package/dist/cli/commands/port.d.ts +32 -0
  49. package/dist/cli/commands/port.d.ts.map +1 -0
  50. package/dist/cli/commands/port.js +554 -0
  51. package/dist/cli/commands/port.js.map +1 -0
  52. package/dist/cli/commands/pr/close.d.ts +15 -0
  53. package/dist/cli/commands/pr/close.d.ts.map +1 -0
  54. package/dist/cli/commands/pr/close.js +71 -0
  55. package/dist/cli/commands/pr/close.js.map +1 -0
  56. package/dist/cli/commands/pr/create.d.ts +17 -0
  57. package/dist/cli/commands/pr/create.d.ts.map +1 -1
  58. package/dist/cli/commands/pr/create.js +209 -5
  59. package/dist/cli/commands/pr/create.js.map +1 -1
  60. package/dist/cli/commands/pr/fixComments.d.ts.map +1 -1
  61. package/dist/cli/commands/pr/fixComments.js +77 -5
  62. package/dist/cli/commands/pr/fixComments.js.map +1 -1
  63. package/dist/cli/commands/pr/index.d.ts.map +1 -1
  64. package/dist/cli/commands/pr/index.js +4 -0
  65. package/dist/cli/commands/pr/index.js.map +1 -1
  66. package/dist/cli/commands/pr/list.d.ts.map +1 -1
  67. package/dist/cli/commands/pr/list.js +26 -3
  68. package/dist/cli/commands/pr/list.js.map +1 -1
  69. package/dist/cli/commands/pr/merge.d.ts +23 -0
  70. package/dist/cli/commands/pr/merge.d.ts.map +1 -0
  71. package/dist/cli/commands/pr/merge.js +191 -0
  72. package/dist/cli/commands/pr/merge.js.map +1 -0
  73. package/dist/cli/commands/pr/review.d.ts.map +1 -1
  74. package/dist/cli/commands/pr/review.js +123 -5
  75. package/dist/cli/commands/pr/review.js.map +1 -1
  76. package/dist/cli/commands/push.d.ts +16 -0
  77. package/dist/cli/commands/push.d.ts.map +1 -0
  78. package/dist/cli/commands/push.js +166 -0
  79. package/dist/cli/commands/push.js.map +1 -0
  80. package/dist/cli/commands/sync.d.ts +24 -0
  81. package/dist/cli/commands/sync.d.ts.map +1 -0
  82. package/dist/cli/commands/sync.js +414 -0
  83. package/dist/cli/commands/sync.js.map +1 -0
  84. package/dist/cli/index.d.ts.map +1 -1
  85. package/dist/cli/index.js +34 -6
  86. package/dist/cli/index.js.map +1 -1
  87. package/dist/config/config.d.ts +20 -3
  88. package/dist/config/config.d.ts.map +1 -1
  89. package/dist/config/config.js +103 -24
  90. package/dist/config/config.js.map +1 -1
  91. package/dist/config/schema.d.ts.map +1 -1
  92. package/dist/config/schema.js +70 -9
  93. package/dist/config/schema.js.map +1 -1
  94. package/dist/core/context.d.ts +13 -0
  95. package/dist/core/context.d.ts.map +1 -0
  96. package/dist/core/context.js +2 -0
  97. package/dist/core/context.js.map +1 -0
  98. package/dist/core/gitx.d.ts +47 -0
  99. package/dist/core/gitx.d.ts.map +1 -1
  100. package/dist/core/gitx.js +204 -9
  101. package/dist/core/gitx.js.map +1 -1
  102. package/dist/index.d.ts +1 -5
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +4 -1
  105. package/dist/index.js.map +1 -1
  106. package/dist/providers/azure.d.ts +26 -0
  107. package/dist/providers/azure.d.ts.map +1 -0
  108. package/dist/providers/azure.js +256 -0
  109. package/dist/providers/azure.js.map +1 -0
  110. package/dist/providers/base.d.ts +104 -0
  111. package/dist/providers/base.d.ts.map +1 -0
  112. package/dist/providers/base.js +5 -0
  113. package/dist/providers/base.js.map +1 -0
  114. package/dist/providers/factory.d.ts +8 -0
  115. package/dist/providers/factory.d.ts.map +1 -0
  116. package/dist/providers/factory.js +25 -0
  117. package/dist/providers/factory.js.map +1 -0
  118. package/dist/providers/github.d.ts +19 -0
  119. package/dist/providers/github.d.ts.map +1 -0
  120. package/dist/providers/github.js +291 -0
  121. package/dist/providers/github.js.map +1 -0
  122. package/dist/providers/gitlab.d.ts +19 -0
  123. package/dist/providers/gitlab.d.ts.map +1 -0
  124. package/dist/providers/gitlab.js +186 -0
  125. package/dist/providers/gitlab.js.map +1 -0
  126. package/dist/types/config.d.ts +53 -9
  127. package/dist/types/config.d.ts.map +1 -1
  128. package/dist/types/config.js.map +1 -1
  129. package/dist/utils/azureAuth.d.ts +51 -0
  130. package/dist/utils/azureAuth.d.ts.map +1 -0
  131. package/dist/utils/azureAuth.js +172 -0
  132. package/dist/utils/azureAuth.js.map +1 -0
  133. package/dist/utils/git.d.ts +22 -0
  134. package/dist/utils/git.d.ts.map +1 -1
  135. package/dist/utils/git.js +63 -7
  136. package/dist/utils/git.js.map +1 -1
  137. package/dist/utils/gitOps.d.ts +118 -0
  138. package/dist/utils/gitOps.d.ts.map +1 -0
  139. package/dist/utils/gitOps.js +380 -0
  140. package/dist/utils/gitOps.js.map +1 -0
  141. package/dist/utils/lockFile.d.ts +13 -0
  142. package/dist/utils/lockFile.d.ts.map +1 -0
  143. package/dist/utils/lockFile.js +54 -0
  144. package/dist/utils/lockFile.js.map +1 -0
  145. package/dist/utils/retry.d.ts +10 -0
  146. package/dist/utils/retry.d.ts.map +1 -0
  147. package/dist/utils/retry.js +31 -0
  148. package/dist/utils/retry.js.map +1 -0
  149. package/dist/workflows/implement.d.ts +41 -0
  150. package/dist/workflows/implement.d.ts.map +1 -0
  151. package/dist/workflows/implement.js +219 -0
  152. package/dist/workflows/implement.js.map +1 -0
  153. package/dist/workflows/pr.d.ts +41 -0
  154. package/dist/workflows/pr.d.ts.map +1 -0
  155. package/dist/workflows/pr.js +285 -0
  156. package/dist/workflows/pr.js.map +1 -0
  157. package/dist/workflows/prAddress.d.ts +55 -0
  158. package/dist/workflows/prAddress.d.ts.map +1 -0
  159. package/dist/workflows/prAddress.js +349 -0
  160. package/dist/workflows/prAddress.js.map +1 -0
  161. package/package.json +1 -1
@@ -0,0 +1,388 @@
1
+ /**
2
+ * OpenAiAi — OpenAI Chat Completions API integration.
3
+ *
4
+ * Authentication: OPENAI_API_KEY env var or stored config key.
5
+ * Model: defaults to gpt-4o. Override via GITX_AI_MODEL env var.
6
+ *
7
+ * All methods use the same structured JSON prompt pattern as ClaudeAi.
8
+ */
9
+ import axios, { isAxiosError } from "axios";
10
+ import { GitxError } from "../utils/errors.js";
11
+ const OPENAI_API = "https://api.openai.com/v1/chat/completions";
12
+ const DEFAULT_MODEL = "gpt-4o";
13
+ const MAX_TOKENS = 4096;
14
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
15
+ function getModel(override) {
16
+ return process.env["GITX_AI_MODEL"] ?? override ?? DEFAULT_MODEL;
17
+ }
18
+ async function callOpenAi(system, userPrompt, apiKey, model) {
19
+ const body = {
20
+ model,
21
+ max_tokens: MAX_TOKENS,
22
+ messages: [
23
+ { role: "system", content: system },
24
+ { role: "user", content: userPrompt },
25
+ ],
26
+ };
27
+ try {
28
+ const { data } = await axios.post(OPENAI_API, body, {
29
+ headers: {
30
+ Authorization: `Bearer ${apiKey}`,
31
+ "Content-Type": "application/json",
32
+ },
33
+ timeout: 60_000,
34
+ });
35
+ return data.choices[0]?.message?.content?.trim() ?? "";
36
+ }
37
+ catch (err) {
38
+ if (isAxiosError(err)) {
39
+ const status = err.response?.status;
40
+ const msg = err.response?.data?.error ??
41
+ err.message;
42
+ if (status === 401) {
43
+ throw new GitxError("OpenAI API authentication failed. Check OPENAI_API_KEY.", { exitCode: 1, cause: err });
44
+ }
45
+ if (status === 429) {
46
+ throw new GitxError("OpenAI rate limit exceeded. Wait a moment and retry.", { exitCode: 1, cause: err });
47
+ }
48
+ throw new GitxError(`OpenAI API error (${status ?? "network"}): ${String(msg)}`, { exitCode: 1, cause: err });
49
+ }
50
+ throw new GitxError(`Unexpected OpenAI error: ${String(err)}`, {
51
+ exitCode: 1,
52
+ cause: err,
53
+ });
54
+ }
55
+ }
56
+ function extractJson(text) {
57
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
58
+ if (fenced?.[1])
59
+ return fenced[1].trim();
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
+ return text.trim();
67
+ }
68
+ function parseJson(text, fallback) {
69
+ try {
70
+ return JSON.parse(extractJson(text));
71
+ }
72
+ catch {
73
+ return fallback;
74
+ }
75
+ }
76
+ // ─── OpenAiAi ─────────────────────────────────────────────────────────────────
77
+ export class OpenAiAi {
78
+ apiKey;
79
+ model;
80
+ /**
81
+ * @param apiKey OpenAI API key. Falls back to OPENAI_API_KEY env var.
82
+ * @param model Model override. Falls back to GITX_AI_MODEL then gpt-4o.
83
+ */
84
+ constructor(apiKey, model) {
85
+ const key = apiKey ?? process.env["OPENAI_API_KEY"];
86
+ if (!key) {
87
+ throw new GitxError("No OpenAI API key available. Run `gitx config set openai` or set OPENAI_API_KEY.", { exitCode: 2 });
88
+ }
89
+ this.apiKey = key;
90
+ this.model = getModel(model);
91
+ }
92
+ /** Check whether an OpenAI API key is available without instantiating. */
93
+ static isAvailable(key) {
94
+ return Boolean(key ?? process.env["OPENAI_API_KEY"]);
95
+ }
96
+ async analyzeTask(input) {
97
+ 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):
98
+ {
99
+ "task": "<the original task string>",
100
+ "intent": "<one of: refactor | bugfix | feature | chore | unknown>",
101
+ "summary": "<one sentence explaining what needs to be done>",
102
+ "assumptions": ["<assumption 1>", "<assumption 2>"],
103
+ "risks": ["<risk 1>", "<risk 2>"]
104
+ }`;
105
+ const text = await callOpenAi(system, `Task: ${input}`, this.apiKey, this.model);
106
+ const parsed = parseJson(text, {});
107
+ return {
108
+ task: input,
109
+ intent: parsed.intent ?? "unknown",
110
+ summary: parsed.summary ?? "Unable to analyze task.",
111
+ assumptions: Array.isArray(parsed.assumptions) ? parsed.assumptions : [],
112
+ risks: Array.isArray(parsed.risks) ? parsed.risks : [],
113
+ };
114
+ }
115
+ async generatePlan(context) {
116
+ const ctx = context;
117
+ const taskDesc = ctx.task ?? "Unknown task";
118
+ const analysisSummary = ctx.analysis?.summary ?? "";
119
+ const fileList = ctx.repoFiles?.slice(0, 50).join("\n") ?? "(not provided)";
120
+ const fileContentsSection = ctx.fileContents && Object.keys(ctx.fileContents).length > 0
121
+ ? `\n\nRelevant file contents:\n${Object.entries(ctx.fileContents)
122
+ .map(([p, c]) => `--- ${p} ---\n${c}`)
123
+ .join("\n\n")}`
124
+ : "";
125
+ const system = `You are an expert software engineer creating a step-by-step implementation plan. Respond with ONLY valid JSON:
126
+ {
127
+ "steps": [
128
+ { "id": "step-1", "title": "<short title>", "description": "<detailed description>" }
129
+ ]
130
+ }
131
+ Keep steps atomic and ordered. Each step should touch one logical concern.`;
132
+ const userPrompt = `Task: ${taskDesc}
133
+ Analysis: ${analysisSummary}
134
+ Repo files (top 50):
135
+ ${fileList}${fileContentsSection}`;
136
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
137
+ const parsed = parseJson(text, { steps: [] });
138
+ const steps = Array.isArray(parsed.steps) ? parsed.steps : [];
139
+ if (steps.length === 0) {
140
+ return { steps: [{ id: "step-1", title: "Analyze & implement", description: taskDesc }] };
141
+ }
142
+ return { steps };
143
+ }
144
+ async generateDiffs(step) {
145
+ const s = step;
146
+ const stepId = s.id ?? "step-1";
147
+ const fileContentsSection = s.fileContents && Object.keys(s.fileContents).length > 0
148
+ ? `\n\nCurrent file contents:\n${Object.entries(s.fileContents)
149
+ .map(([p, c]) => `--- ${p} ---\n${c}`)
150
+ .join("\n\n")}`
151
+ : "\n\n(No existing file contents — create new files as needed.)";
152
+ const system = `You are an expert software engineer. Generate unified diffs for the given implementation step.
153
+ Respond with ONLY valid JSON:
154
+ {
155
+ "stepId": "<step id>",
156
+ "diffs": [
157
+ {
158
+ "path": "<relative file path>",
159
+ "unifiedDiff": "<valid unified diff starting with --- a/path and +++ b/path>"
160
+ }
161
+ ]
162
+ }
163
+
164
+ Rules for unified diffs:
165
+ - Start with: --- a/<path>\\n+++ b/<path>
166
+ - Use @@ -<start>,<count> +<start>,<count> @@ context headers
167
+ - Lines starting with ' ' are context (unchanged)
168
+ - Lines starting with '-' are removed
169
+ - Lines starting with '+' are added
170
+ - For new files use: --- /dev/null\\n+++ b/<path>
171
+ - Always include 3 lines of context around changes`;
172
+ const userPrompt = `Task: ${s.task ?? ""}
173
+ Step ID: ${stepId}
174
+ Step Title: ${s.title ?? ""}
175
+ Step Description: ${s.description ?? ""}${fileContentsSection}`;
176
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
177
+ const parsed = parseJson(text, { stepId, diffs: [] });
178
+ return {
179
+ stepId,
180
+ diffs: Array.isArray(parsed.diffs) ? parsed.diffs : [],
181
+ };
182
+ }
183
+ async summarizeChanges(diff) {
184
+ const d = diff;
185
+ const diffContent = d.rawDiff ??
186
+ (d.diffs ?? [])
187
+ .flatMap((dr) => dr.diffs.map((f) => `File: ${f.path}\n${f.unifiedDiff}`))
188
+ .join("\n\n") ??
189
+ "(no diffs)";
190
+ const system = `You are a technical writer. Summarize code changes for a pull request. Respond with ONLY valid JSON:
191
+ {
192
+ "summary": "<2-3 sentence plain-English summary of what changed and why>",
193
+ "filesChanged": [
194
+ { "path": "<file path>", "changeType": "<add | modify | delete>" }
195
+ ]
196
+ }`;
197
+ const text = await callOpenAi(system, `Changes:\n${diffContent}`, this.apiKey, this.model);
198
+ const parsed = parseJson(text, {
199
+ summary: "",
200
+ filesChanged: [],
201
+ });
202
+ return {
203
+ summary: parsed.summary ?? "Code changes applied.",
204
+ filesChanged: Array.isArray(parsed.filesChanged) ? parsed.filesChanged : [],
205
+ };
206
+ }
207
+ async suggestFixes(comment) {
208
+ const c = comment;
209
+ const commentsText = (c.comments ?? [])
210
+ .map((co) => `[${co.author ?? "reviewer"}${co.path ? ` on ${co.path}:${co.line ?? ""}` : ""}]: ${co.body}`)
211
+ .join("\n\n");
212
+ const fileContentsSection = c.fileContents && Object.keys(c.fileContents).length > 0
213
+ ? `\n\nCurrent file contents:\n${Object.entries(c.fileContents)
214
+ .map(([p, content]) => `--- ${p} ---\n${content}`)
215
+ .join("\n\n")}`
216
+ : "";
217
+ const system = `You are an expert code reviewer suggesting fixes for pull request review comments. Respond with ONLY valid JSON:
218
+ {
219
+ "suggestedEdits": [
220
+ {
221
+ "path": "<file path>",
222
+ "rationale": "<why this change addresses the comment>",
223
+ "unifiedDiff": "<valid unified diff>"
224
+ }
225
+ ]
226
+ }
227
+ If a comment doesn't require a code change, omit it from suggestedEdits.`;
228
+ const userPrompt = `PR Title: ${c.prTitle ?? ""}
229
+ PR Body: ${c.prBody ?? ""}
230
+
231
+ Review Comments:
232
+ ${commentsText}${fileContentsSection}`;
233
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
234
+ const parsed = parseJson(text, { suggestedEdits: [] });
235
+ return {
236
+ suggestedEdits: Array.isArray(parsed.suggestedEdits) ? parsed.suggestedEdits : [],
237
+ };
238
+ }
239
+ async reviewPR(context) {
240
+ const ctx = context;
241
+ const diffSection = ctx.diff
242
+ ? `\n\nDiff (unified):\n${ctx.diff.slice(0, 8000)}`
243
+ : "";
244
+ const commentsSection = ctx.comments && ctx.comments.length > 0
245
+ ? `\n\nExisting review comments:\n${ctx.comments
246
+ .map((c) => `[${c.author}${c.path ? ` @ ${c.path}` : ""}]: ${c.body}`)
247
+ .join("\n")}`
248
+ : "";
249
+ const system = `You are an expert code reviewer. Review the given pull request thoroughly and respond with ONLY valid JSON:
250
+ {
251
+ "summary": "<2-4 sentence overall assessment>",
252
+ "issues": [
253
+ { "severity": "<critical|warning|suggestion>", "description": "<specific issue>", "file": "<optional file>", "line": null }
254
+ ],
255
+ "positives": ["<good thing 1>", "<good thing 2>"],
256
+ "verdict": "<approve|request_changes|comment>"
257
+ }
258
+
259
+ Severity guide:
260
+ - critical: bugs, security issues, data loss risk
261
+ - warning: code quality, performance, missing error handling
262
+ - suggestion: style, naming, minor improvements`;
263
+ const userPrompt = `PR Title: ${ctx.prTitle ?? ""}
264
+ PR Description: ${ctx.prBody ?? ""}${diffSection}${commentsSection}`;
265
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
266
+ const parsed = parseJson(text, {
267
+ summary: "",
268
+ issues: [],
269
+ positives: [],
270
+ verdict: "comment",
271
+ });
272
+ return {
273
+ summary: parsed.summary ?? "Review could not be generated.",
274
+ issues: Array.isArray(parsed.issues) ? parsed.issues : [],
275
+ positives: Array.isArray(parsed.positives) ? parsed.positives : [],
276
+ verdict: (["approve", "request_changes", "comment"].includes(parsed.verdict ?? ""))
277
+ ? parsed.verdict
278
+ : "comment",
279
+ };
280
+ }
281
+ async generatePrContent(commits, diff, stat) {
282
+ const system = `You are an expert software engineer writing pull request descriptions.
283
+ You are given a list of commits on the branch and the unified diff of all changes.
284
+
285
+ The input may contain:
286
+ - "=== Changed files (complete list) ===" — the full --stat summary of every file touched
287
+ - "=== Detailed diff ===" — the actual patch (may be truncated for large changesets)
288
+
289
+ When a file list is present, use it as the authoritative source of ALL changed files.
290
+ Do not ignore files that appear in the list but are absent from the truncated diff.
291
+
292
+ Produce a clear, informative PR title and description:
293
+
294
+ Rules:
295
+ - title: short, human-readable, present-tense. No conventional-commit prefix. Max 72 chars.
296
+ - body: 2-4 sentences describing WHAT changed and WHY. Cover ALL files from the list.
297
+ Plain English, no bullet points.
298
+
299
+ Respond with ONLY valid JSON (no markdown fences):
300
+ {"title":"<PR title>","body":"<PR description>"}`;
301
+ const commitList = commits.slice(0, 20).join("\n");
302
+ const diffSection = stat
303
+ ? `=== Changed files (complete list) ===\n${stat}\n\n=== Detailed diff ===\n${diff.slice(0, 16000)}`
304
+ : `Diff:\n${diff.slice(0, 16000)}`;
305
+ const userPrompt = `Commits on this branch:\n${commitList}\n\n${diffSection}`;
306
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
307
+ const parsed = parseJson(text, {});
308
+ return {
309
+ title: parsed.title?.trim() ?? "Update branch",
310
+ body: parsed.body?.trim() ?? "",
311
+ };
312
+ }
313
+ async resolveConflict(filePath, conflictContent) {
314
+ const system = `You are an expert software engineer resolving git merge conflicts.
315
+
316
+ The file contains standard git conflict markers:
317
+ <<<<<<< HEAD — changes on the current branch
318
+ ======= — separator
319
+ >>>>>>> theirs — incoming changes
320
+
321
+ Your task:
322
+ 1. Understand BOTH sides of every conflict.
323
+ 2. Produce a single correct version preserving the intent of BOTH changes where possible.
324
+ 3. If the sides are genuinely contradictory, set confidence to "low".
325
+
326
+ Rules:
327
+ - Remove ALL conflict markers from the output.
328
+ - Do NOT add explanatory comments.
329
+ - Keep all non-conflicting code exactly as-is.
330
+ - Output must be syntactically valid.
331
+
332
+ Respond with ONLY valid JSON (no markdown fences):
333
+ {"resolved":"<full resolved file content>","confidence":"high|low","explanation":"<one sentence>"}`;
334
+ const userPrompt = `File: ${filePath}\n\n${conflictContent.slice(0, 20000)}`;
335
+ const text = await callOpenAi(system, userPrompt, this.apiKey, this.model);
336
+ const parsed = parseJson(text, {});
337
+ return {
338
+ resolved: parsed.resolved ?? conflictContent,
339
+ confidence: parsed.confidence === "low" ? "low" : "high",
340
+ explanation: parsed.explanation?.trim() ?? "Conflict resolved.",
341
+ };
342
+ }
343
+ async generateCommitMessage(diff) {
344
+ const system = `You are an expert software engineer writing git commit messages.
345
+ You receive either a plain unified diff OR a structured input with:
346
+ - "=== Changed files (complete list) ===" — the full --stat summary of every file touched
347
+ - "=== Detailed diff ===" — the actual patch (may be truncated for large changesets)
348
+
349
+ When a file list is present, use it as the authoritative source of ALL changes.
350
+ Do not ignore files that appear in the list but are missing from the truncated diff.
351
+
352
+ Produce ONE CONVENTIONAL COMMIT covering all the changes:
353
+
354
+ Rules:
355
+ - subject: "<type>(<scope>): <imperative description>"
356
+ - type: feat | fix | refactor | chore | docs | test | perf | ci | style | build
357
+ - scope: the primary area affected; omit if changes span many unrelated areas
358
+ - description: imperative mood, no period, 72 chars max for the whole subject line
359
+ - if several distinct features or fixes are present, pick the most impactful for the subject
360
+ - body: cover EVERY significant change visible in the file list. For each distinct change,
361
+ one sentence on what was done and why. Plain English, no bullet lists, no repetition of the subject.
362
+
363
+ Respond with ONLY valid JSON (no markdown fences):
364
+ {"subject":"<subject line>","body":"<body covering all changes, or empty string>"}`;
365
+ const text = await callOpenAi(system, `Diff:\n${diff.slice(0, 20000)}`, this.apiKey, this.model);
366
+ const parsed = parseJson(text, {});
367
+ return {
368
+ subject: parsed.subject?.trim() ?? "chore: update files",
369
+ body: parsed.body?.trim() || undefined,
370
+ };
371
+ }
372
+ async reviewPRDetailed(context) {
373
+ const { buildSeniorReviewSystem, buildSeniorReviewPrompt, parseSeniorReview } = await import("./reviewHelpers.js");
374
+ const text = await callOpenAi(buildSeniorReviewSystem(), buildSeniorReviewPrompt(context), this.apiKey, this.model);
375
+ return parseSeniorReview(text);
376
+ }
377
+ async generateFix(context) {
378
+ const { buildFixSystem, buildFixPrompt, parseFixResponse } = await import("./reviewHelpers.js");
379
+ const text = await callOpenAi(buildFixSystem(), buildFixPrompt(context), this.apiKey, this.model);
380
+ return parseFixResponse(text, context.filePath, context.line);
381
+ }
382
+ async ask(question, context) {
383
+ const { buildAskSystem, buildAskPrompt, parseAskResponse } = await import("./reviewHelpers.js");
384
+ const text = await callOpenAi(buildAskSystem(), buildAskPrompt(question, context), this.apiKey, this.model);
385
+ return parseAskResponse(text);
386
+ }
387
+ }
388
+ //# sourceMappingURL=openAiAi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openAiAi.js","sourceRoot":"","sources":["../../src/ai/openAiAi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAiAi — OpenAI Chat Completions API integration.\n *\n * Authentication: OPENAI_API_KEY env var or stored config key.\n * Model: defaults to gpt-4o. Override via GITX_AI_MODEL env var.\n *\n * All methods use the same structured JSON prompt pattern as ClaudeAi.\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 OPENAI_API = \"https://api.openai.com/v1/chat/completions\";\nconst DEFAULT_MODEL = \"gpt-4o\";\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 ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\ninterface OpenAiRequestBody {\n model: string;\n max_tokens: number;\n messages: ChatMessage[];\n}\n\ninterface OpenAiResponseBody {\n choices: Array<{\n message: { role: string; content: string };\n finish_reason: string;\n }>;\n}\n\nasync function callOpenAi(\n system: string,\n userPrompt: string,\n apiKey: string,\n model: string\n): Promise<string> {\n const body: OpenAiRequestBody = {\n model,\n max_tokens: MAX_TOKENS,\n messages: [\n { role: \"system\", content: system },\n { role: \"user\", content: userPrompt },\n ],\n };\n\n try {\n const { data } = await axios.post<OpenAiResponseBody>(OPENAI_API, body, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n timeout: 60_000,\n });\n\n return data.choices[0]?.message?.content?.trim() ?? \"\";\n } catch (err) {\n if (isAxiosError(err)) {\n const status = err.response?.status;\n const msg =\n (err.response?.data as Record<string, unknown> | undefined)?.error ??\n err.message;\n if (status === 401) {\n throw new GitxError(\n \"OpenAI API authentication failed. Check OPENAI_API_KEY.\",\n { exitCode: 1, cause: err }\n );\n }\n if (status === 429) {\n throw new GitxError(\n \"OpenAI rate limit exceeded. Wait a moment and retry.\",\n { exitCode: 1, cause: err }\n );\n }\n throw new GitxError(\n `OpenAI API error (${status ?? \"network\"}): ${String(msg)}`,\n { exitCode: 1, cause: err }\n );\n }\n throw new GitxError(`Unexpected OpenAI error: ${String(err)}`, {\n exitCode: 1,\n cause: err,\n });\n }\n}\n\nfunction extractJson(text: string): string {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (fenced?.[1]) return fenced[1].trim();\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) return text.slice(start, end + 1);\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// ─── OpenAiAi ─────────────────────────────────────────────────────────────────\n\nexport class OpenAiAi implements AiClient {\n private readonly apiKey: string;\n private readonly model: string;\n\n /**\n * @param apiKey OpenAI API key. Falls back to OPENAI_API_KEY env var.\n * @param model Model override. Falls back to GITX_AI_MODEL then gpt-4o.\n */\n constructor(apiKey?: string, model?: string) {\n const key = apiKey ?? process.env[\"OPENAI_API_KEY\"];\n if (!key) {\n throw new GitxError(\n \"No OpenAI API key available. Run `gitx config set openai` or set OPENAI_API_KEY.\",\n { exitCode: 2 }\n );\n }\n this.apiKey = key;\n this.model = getModel(model);\n }\n\n /** Check whether an OpenAI API key is available without instantiating. */\n static isAvailable(key?: string): boolean {\n return Boolean(key ?? process.env[\"OPENAI_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):\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 callOpenAi(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>\" }\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 callOpenAi(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 { steps: [{ id: \"step-1\", title: \"Analyze & implement\", description: taskDesc }] };\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 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:\\n${Object.entries(s.fileContents)\n .map(([p, c]) => `--- ${p} ---\\n${c}`)\n .join(\"\\n\\n\")}`\n : \"\\n\\n(No existing file contents — create new files as needed.)\";\n\n const system = `You are an expert software engineer. Generate unified diffs for the given implementation step.\nRespond with ONLY valid JSON:\n{\n \"stepId\": \"<step id>\",\n \"diffs\": [\n {\n \"path\": \"<relative file path>\",\n \"unifiedDiff\": \"<valid unified diff 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- Always include 3 lines of context around 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 callOpenAi(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 { diffs?: AiGenerateDiffsResponse[]; rawDiff?: string };\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 callOpenAi(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 callOpenAi(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\n .map((c) => `[${c.author}${c.path ? ` @ ${c.path}` : \"\"}]: ${c.body}`)\n .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 callOpenAi(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. No conventional-commit prefix. Max 72 chars.\n- body: 2-4 sentences describing WHAT changed and WHY. Cover ALL files from the list.\n Plain English, no 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 callOpenAi(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 — changes on the current branch\n ======= — separator\n >>>>>>> theirs — incoming changes\n\nYour task:\n1. Understand BOTH sides of every conflict.\n2. Produce a single correct version preserving the intent of BOTH changes where possible.\n3. If the sides are genuinely contradictory, set confidence to \"low\".\n\nRules:\n- Remove ALL conflict markers from the output.\n- Do NOT add explanatory comments.\n- Keep all non-conflicting code exactly as-is.\n- Output must be syntactically valid.\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 callOpenAi(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 callOpenAi(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 callOpenAi(\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 callOpenAi(\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 callOpenAi(buildAskSystem(), buildAskPrompt(question, context), this.apiKey, this.model);\n return parseAskResponse(text);\n }\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared helpers for the senior-developer PR review.
3
+ *
4
+ * buildSeniorReviewSystem() — the AI system prompt
5
+ * buildSeniorReviewPrompt() — formats the user-facing context block
6
+ * parseSeniorReview() — safely parses the AI JSON response
7
+ */
8
+ import type { AiAskContext, AiAskResponse, AiDetailedReviewResponse, AiFixResponse } from "./types.js";
9
+ export declare function buildSeniorReviewSystem(): string;
10
+ export declare function buildSeniorReviewPrompt(context: {
11
+ prTitle: string;
12
+ prBody: string;
13
+ author: string;
14
+ headBranch: string;
15
+ baseBranch: string;
16
+ diff: string;
17
+ changedFiles: Record<string, string>;
18
+ contextFiles: Record<string, string>;
19
+ repoFileList: string[];
20
+ existingComments: Array<{
21
+ author: string;
22
+ body: string;
23
+ path?: string;
24
+ line?: number;
25
+ }>;
26
+ }): string;
27
+ export declare function parseSeniorReview(text: string): AiDetailedReviewResponse;
28
+ /**
29
+ * System prompt for the AI fix generator.
30
+ * Instructs the model to produce a minimal, targeted line-range replacement.
31
+ */
32
+ export declare function buildFixSystem(): string;
33
+ /**
34
+ * Build the user prompt for a single fix request.
35
+ * Includes the comment, file content with line numbers, and the relevant diff.
36
+ */
37
+ export declare function buildFixPrompt(ctx: {
38
+ comment: string;
39
+ commentAuthor: string;
40
+ filePath: string;
41
+ line: number;
42
+ fileContent: string;
43
+ fileDiff: string;
44
+ }): string;
45
+ /**
46
+ * Safely parse the AI response for a fix request.
47
+ * Returns a safe fallback (isDiscussion + low confidence) on parse failure.
48
+ */
49
+ export declare function parseFixResponse(text: string, filePath: string, line: number): AiFixResponse;
50
+ /**
51
+ * Builds the system prompt for `gitx ask`.
52
+ * Includes the full command reference and setup guidance so the AI can answer
53
+ * both "how do I…" questions and "is X configured?" diagnostics.
54
+ */
55
+ export declare function buildAskSystem(): string;
56
+ /**
57
+ * Builds the user-turn prompt for `gitx ask`, injecting live repo context
58
+ * and the full gitx setup status so the AI can answer diagnostic questions accurately.
59
+ */
60
+ export declare function buildAskPrompt(question: string, ctx: AiAskContext): string;
61
+ /**
62
+ * Safely parses the AI JSON response for `gitx ask`.
63
+ * Falls back to using the raw text as the answer if JSON parsing fails.
64
+ */
65
+ export declare function parseAskResponse(raw: string): AiAskResponse;
66
+ //# sourceMappingURL=reviewHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewHelpers.d.ts","sourceRoot":"","sources":["../../src/ai/reviewHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIvG,wBAAgB,uBAAuB,IAAI,MAAM,CAwChD;AAwJD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE;IACP,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzF,GACA,MAAM,CA6FR;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,CAkCxE;AAID;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAsBvC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAgCT;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAgD5F;AAoCD;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAgCvC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CAoE1E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAa3D"}