automata-cli 0.1.0-bugfix-nextwork.57 → 0.1.0-feature-021-execute-command.125
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 +14 -0
- package/dist/index.js +1152 -114
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command6 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -19,16 +19,46 @@ import React2 from "react";
|
|
|
19
19
|
// src/config/ConfigWizard.tsx
|
|
20
20
|
import { useState } from "react";
|
|
21
21
|
import { Box, Text, useInput, useApp } from "ink";
|
|
22
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
23
|
+
import { join as join2 } from "path";
|
|
22
24
|
|
|
23
25
|
// src/config/configStore.ts
|
|
24
26
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
25
|
-
import { join } from "path";
|
|
27
|
+
import { join, resolve as resolve2, sep } from "path";
|
|
28
|
+
var DEFAULT_CLAUDE_SYSTEM_PROMPT = "You are an expert software engineer. Implement the following issue according to the project's existing conventions and style. Make minimal, targeted changes that satisfy the requirements. Run tests and linting before finishing.";
|
|
29
|
+
var DEFAULT_FIX_COMMENTS_PROMPT = "You are an expert software engineer reviewing a pull request. Below are the open review comments left by reviewers on this PR. Please address each comment by making the appropriate code changes. Focus on the reviewer's concerns and make minimal, targeted changes that resolve each comment without altering unrelated code.";
|
|
30
|
+
var DEFAULT_SONAR_PROMPT = "You are an expert software engineer. You have been given the URL of a SonarCloud analysis for this pull request. If the `sonar-quality-gate` skill is available in this repository, use it. The project is public, so use the SonarCloud REST API directly (no authentication required) rather than scraping the URL. Inspect both the quality gate and the list of issues for this pull request. If the quality gate fails because of duplication or another metric-based condition, use the relevant Sonar APIs to identify the affected files and details instead of relying only on the issues endpoint. Fix all new issues and quality-gate failures reported. Focus on code smells, bugs, vulnerabilities, and blocking quality-gate conditions flagged in this PR. Make targeted, minimal changes that resolve each issue without altering unrelated code.";
|
|
26
31
|
var CONFIG_DIR = ".automata";
|
|
27
32
|
var CONFIG_FILE = "config.json";
|
|
28
33
|
function configPath() {
|
|
29
34
|
return join(process.cwd(), CONFIG_DIR, CONFIG_FILE);
|
|
30
35
|
}
|
|
31
|
-
function
|
|
36
|
+
function automataDir() {
|
|
37
|
+
return join(process.cwd(), CONFIG_DIR);
|
|
38
|
+
}
|
|
39
|
+
function resolvePromptRef(value, dir) {
|
|
40
|
+
if (!value.endsWith(".md")) return value;
|
|
41
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
42
|
+
throw new Error(`Prompt file "${value}" must be a plain filename with no subdirectories`);
|
|
43
|
+
}
|
|
44
|
+
const fullPath = resolve2(dir, value);
|
|
45
|
+
const safeBase = resolve2(dir) + sep;
|
|
46
|
+
if (!fullPath.startsWith(safeBase)) {
|
|
47
|
+
throw new Error(`Prompt file "${value}" resolves outside .automata/`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return readFileSync2(fullPath, "utf8");
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err?.code === "ENOENT") {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Prompt file "${value}" not found in "${dir}". Expected path: ${fullPath}`,
|
|
55
|
+
{ cause: err }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function readRawConfig() {
|
|
32
62
|
try {
|
|
33
63
|
const raw = readFileSync2(configPath(), "utf8");
|
|
34
64
|
return JSON.parse(raw);
|
|
@@ -36,6 +66,26 @@ function readConfig() {
|
|
|
36
66
|
return {};
|
|
37
67
|
}
|
|
38
68
|
}
|
|
69
|
+
function readConfig() {
|
|
70
|
+
let config;
|
|
71
|
+
try {
|
|
72
|
+
const raw = readFileSync2(configPath(), "utf8");
|
|
73
|
+
config = JSON.parse(raw);
|
|
74
|
+
} catch {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
const dir = automataDir();
|
|
78
|
+
if (config.claudeSystemPrompt) {
|
|
79
|
+
config.claudeSystemPrompt = resolvePromptRef(config.claudeSystemPrompt, dir);
|
|
80
|
+
}
|
|
81
|
+
if (config.prompts?.sonar) {
|
|
82
|
+
config.prompts.sonar = resolvePromptRef(config.prompts.sonar, dir);
|
|
83
|
+
}
|
|
84
|
+
if (config.prompts?.fixComments) {
|
|
85
|
+
config.prompts.fixComments = resolvePromptRef(config.prompts.fixComments, dir);
|
|
86
|
+
}
|
|
87
|
+
return config;
|
|
88
|
+
}
|
|
39
89
|
function writeConfig(config) {
|
|
40
90
|
const dir = join(process.cwd(), CONFIG_DIR);
|
|
41
91
|
mkdirSync(dir, { recursive: true });
|
|
@@ -44,6 +94,11 @@ function writeConfig(config) {
|
|
|
44
94
|
|
|
45
95
|
// src/config/ConfigWizard.tsx
|
|
46
96
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
97
|
+
function writePromptFile(filename, content) {
|
|
98
|
+
const dir = join2(process.cwd(), ".automata");
|
|
99
|
+
mkdirSync2(dir, { recursive: true });
|
|
100
|
+
writeFileSync2(join2(dir, filename), content, "utf8");
|
|
101
|
+
}
|
|
47
102
|
var REMOTE_OPTIONS = [
|
|
48
103
|
{ label: "GitHub", value: "gh" },
|
|
49
104
|
{ label: "Azure DevOps", value: "azdo" }
|
|
@@ -53,22 +108,48 @@ var TECHNIQUE_OPTIONS = [
|
|
|
53
108
|
{ label: "By Assignee", value: "assignee" },
|
|
54
109
|
{ label: "By Title Contains", value: "title-contains" }
|
|
55
110
|
];
|
|
111
|
+
var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts"];
|
|
112
|
+
var PROMPTS_MENU_OPTIONS = ["Sonar", "Fix-Comments"];
|
|
56
113
|
function ConfigWizard() {
|
|
57
114
|
const existing = readConfig();
|
|
115
|
+
const rawExisting = readRawConfig();
|
|
58
116
|
const initialRemoteIndex = REMOTE_OPTIONS.findIndex((o) => o.value === existing.remoteType);
|
|
59
117
|
const initialTechIndex = TECHNIQUE_OPTIONS.findIndex((o) => o.value === existing.issueDiscoveryTechnique);
|
|
60
|
-
const [screen, setScreen] = useState("
|
|
118
|
+
const [screen, setScreen] = useState("main");
|
|
119
|
+
const [mainMenuIndex, setMainMenuIndex] = useState(0);
|
|
61
120
|
const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(initialRemoteIndex >= 0 ? initialRemoteIndex : 0);
|
|
62
121
|
const [selectedTechIndex, setSelectedTechIndex] = useState(initialTechIndex >= 0 ? initialTechIndex : 0);
|
|
63
122
|
const [discoveryValue, setDiscoveryValue] = useState(existing.issueDiscoveryValue ?? "");
|
|
64
123
|
const [systemPrompt, setSystemPrompt] = useState(existing.claudeSystemPrompt ?? "");
|
|
124
|
+
const [promptsMenuIndex, setPromptsMenuIndex] = useState(0);
|
|
125
|
+
const [sonarPrompt, setSonarPrompt] = useState(existing.prompts?.sonar ?? DEFAULT_SONAR_PROMPT);
|
|
126
|
+
const [fixCommentsPrompt, setFixCommentsPrompt] = useState(
|
|
127
|
+
existing.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT
|
|
128
|
+
);
|
|
65
129
|
const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
|
|
66
130
|
const [pendingTechnique, setPendingTechnique] = useState(
|
|
67
131
|
existing.issueDiscoveryTechnique ?? "label"
|
|
68
132
|
);
|
|
69
133
|
const { exit } = useApp();
|
|
70
134
|
useInput((input, key) => {
|
|
71
|
-
if (screen === "
|
|
135
|
+
if (screen === "main") {
|
|
136
|
+
if (key.upArrow) {
|
|
137
|
+
setMainMenuIndex((i) => i > 0 ? i - 1 : MAIN_MENU_OPTIONS.length - 1);
|
|
138
|
+
} else if (key.downArrow) {
|
|
139
|
+
setMainMenuIndex((i) => i < MAIN_MENU_OPTIONS.length - 1 ? i + 1 : 0);
|
|
140
|
+
} else if (key.return) {
|
|
141
|
+
const chosen = MAIN_MENU_OPTIONS[mainMenuIndex];
|
|
142
|
+
if (chosen === "Remote / Mode") {
|
|
143
|
+
setScreen("remote");
|
|
144
|
+
} else if (chosen === "Implement-Next") {
|
|
145
|
+
setScreen("technique");
|
|
146
|
+
} else {
|
|
147
|
+
setScreen("prompts-menu");
|
|
148
|
+
}
|
|
149
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
150
|
+
exit();
|
|
151
|
+
}
|
|
152
|
+
} else if (screen === "remote") {
|
|
72
153
|
if (key.upArrow) {
|
|
73
154
|
setSelectedRemoteIndex((i) => i > 0 ? i - 1 : REMOTE_OPTIONS.length - 1);
|
|
74
155
|
} else if (key.downArrow) {
|
|
@@ -79,10 +160,12 @@ function ConfigWizard() {
|
|
|
79
160
|
if (chosen.value === "gh") {
|
|
80
161
|
setScreen("technique");
|
|
81
162
|
} else {
|
|
82
|
-
writeConfig({ ...
|
|
163
|
+
writeConfig({ ...rawExisting, remoteType: chosen.value });
|
|
83
164
|
exit();
|
|
84
165
|
}
|
|
85
|
-
} else if (key.escape
|
|
166
|
+
} else if (key.escape) {
|
|
167
|
+
setScreen("main");
|
|
168
|
+
} else if (key.ctrl && input === "c") {
|
|
86
169
|
exit();
|
|
87
170
|
}
|
|
88
171
|
} else if (screen === "technique") {
|
|
@@ -94,7 +177,9 @@ function ConfigWizard() {
|
|
|
94
177
|
const chosen = TECHNIQUE_OPTIONS[selectedTechIndex];
|
|
95
178
|
setPendingTechnique(chosen.value);
|
|
96
179
|
setScreen("value");
|
|
97
|
-
} else if (key.escape
|
|
180
|
+
} else if (key.escape) {
|
|
181
|
+
setScreen("main");
|
|
182
|
+
} else if (key.ctrl && input === "c") {
|
|
98
183
|
exit();
|
|
99
184
|
}
|
|
100
185
|
} else if (screen === "value") {
|
|
@@ -102,46 +187,128 @@ function ConfigWizard() {
|
|
|
102
187
|
setScreen("system-prompt");
|
|
103
188
|
} else if (key.backspace || key.delete) {
|
|
104
189
|
setDiscoveryValue((v) => v.slice(0, -1));
|
|
105
|
-
} else if (key.escape
|
|
190
|
+
} else if (key.escape) {
|
|
191
|
+
setScreen("main");
|
|
192
|
+
} else if (key.ctrl && input === "c") {
|
|
106
193
|
exit();
|
|
107
194
|
} else if (input && !key.ctrl && !key.meta) {
|
|
108
195
|
setDiscoveryValue((v) => v + input);
|
|
109
196
|
}
|
|
110
197
|
} else if (screen === "system-prompt") {
|
|
111
198
|
if (key.return) {
|
|
199
|
+
let claudeSystemPromptValue;
|
|
200
|
+
if (systemPrompt) {
|
|
201
|
+
writePromptFile("claude-system-prompt.md", systemPrompt);
|
|
202
|
+
claudeSystemPromptValue = "claude-system-prompt.md";
|
|
203
|
+
}
|
|
112
204
|
writeConfig({
|
|
113
|
-
...
|
|
205
|
+
...rawExisting,
|
|
114
206
|
remoteType: pendingRemote,
|
|
115
207
|
issueDiscoveryTechnique: pendingTechnique,
|
|
116
208
|
issueDiscoveryValue: discoveryValue || void 0,
|
|
117
|
-
claudeSystemPrompt:
|
|
209
|
+
claudeSystemPrompt: claudeSystemPromptValue
|
|
118
210
|
});
|
|
119
211
|
exit();
|
|
120
212
|
} else if (key.backspace || key.delete) {
|
|
121
213
|
setSystemPrompt((v) => v.slice(0, -1));
|
|
122
|
-
} else if (key.escape
|
|
214
|
+
} else if (key.escape) {
|
|
215
|
+
setScreen("main");
|
|
216
|
+
} else if (key.ctrl && input === "c") {
|
|
123
217
|
exit();
|
|
124
218
|
} else if (input && !key.ctrl && !key.meta) {
|
|
125
219
|
setSystemPrompt((v) => v + input);
|
|
126
220
|
}
|
|
221
|
+
} else if (screen === "prompts-menu") {
|
|
222
|
+
if (key.upArrow) {
|
|
223
|
+
setPromptsMenuIndex((i) => i > 0 ? i - 1 : PROMPTS_MENU_OPTIONS.length - 1);
|
|
224
|
+
} else if (key.downArrow) {
|
|
225
|
+
setPromptsMenuIndex((i) => i < PROMPTS_MENU_OPTIONS.length - 1 ? i + 1 : 0);
|
|
226
|
+
} else if (key.return) {
|
|
227
|
+
const chosen = PROMPTS_MENU_OPTIONS[promptsMenuIndex];
|
|
228
|
+
if (chosen === "Sonar") {
|
|
229
|
+
setScreen("sonar-prompt");
|
|
230
|
+
} else {
|
|
231
|
+
setScreen("fix-comments-prompt");
|
|
232
|
+
}
|
|
233
|
+
} else if (key.escape) {
|
|
234
|
+
setScreen("main");
|
|
235
|
+
} else if (key.ctrl && input === "c") {
|
|
236
|
+
exit();
|
|
237
|
+
}
|
|
238
|
+
} else if (screen === "sonar-prompt") {
|
|
239
|
+
if (key.return) {
|
|
240
|
+
let sonarValue;
|
|
241
|
+
if (sonarPrompt) {
|
|
242
|
+
writePromptFile("sonar-prompt.md", sonarPrompt);
|
|
243
|
+
sonarValue = "sonar-prompt.md";
|
|
244
|
+
}
|
|
245
|
+
const current = readRawConfig();
|
|
246
|
+
writeConfig({
|
|
247
|
+
...current,
|
|
248
|
+
prompts: { ...current.prompts, sonar: sonarValue }
|
|
249
|
+
});
|
|
250
|
+
setScreen("prompts-menu");
|
|
251
|
+
} else if (key.backspace || key.delete) {
|
|
252
|
+
setSonarPrompt((v) => v.slice(0, -1));
|
|
253
|
+
} else if (key.escape) {
|
|
254
|
+
setScreen("prompts-menu");
|
|
255
|
+
} else if (key.ctrl && input === "c") {
|
|
256
|
+
exit();
|
|
257
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
258
|
+
setSonarPrompt((v) => v + input);
|
|
259
|
+
}
|
|
260
|
+
} else if (screen === "fix-comments-prompt") {
|
|
261
|
+
if (key.return) {
|
|
262
|
+
let fixCommentsValue;
|
|
263
|
+
if (fixCommentsPrompt) {
|
|
264
|
+
writePromptFile("fix-comments-prompt.md", fixCommentsPrompt);
|
|
265
|
+
fixCommentsValue = "fix-comments-prompt.md";
|
|
266
|
+
}
|
|
267
|
+
const current = readRawConfig();
|
|
268
|
+
writeConfig({
|
|
269
|
+
...current,
|
|
270
|
+
prompts: { ...current.prompts, fixComments: fixCommentsValue }
|
|
271
|
+
});
|
|
272
|
+
setScreen("prompts-menu");
|
|
273
|
+
} else if (key.backspace || key.delete) {
|
|
274
|
+
setFixCommentsPrompt((v) => v.slice(0, -1));
|
|
275
|
+
} else if (key.escape) {
|
|
276
|
+
setScreen("prompts-menu");
|
|
277
|
+
} else if (key.ctrl && input === "c") {
|
|
278
|
+
exit();
|
|
279
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
280
|
+
setFixCommentsPrompt((v) => v + input);
|
|
281
|
+
}
|
|
127
282
|
}
|
|
128
283
|
});
|
|
129
|
-
if (screen === "
|
|
284
|
+
if (screen === "main") {
|
|
130
285
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
131
286
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Automata" }),
|
|
132
287
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
288
|
+
MAIN_MENU_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === mainMenuIndex ? "cyan" : void 0, children: [
|
|
289
|
+
index === mainMenuIndex ? "\u276F " : " ",
|
|
290
|
+
option
|
|
291
|
+
] }) }, option)),
|
|
292
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
293
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to cancel" })
|
|
294
|
+
] });
|
|
295
|
+
}
|
|
296
|
+
if (screen === "remote") {
|
|
297
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
298
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Remote / Mode" }),
|
|
299
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
133
300
|
/* @__PURE__ */ jsx(Text, { children: "Remote environment type:" }),
|
|
134
301
|
REMOTE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedRemoteIndex ? "cyan" : void 0, children: [
|
|
135
302
|
index === selectedRemoteIndex ? "\u276F " : " ",
|
|
136
303
|
option.label
|
|
137
304
|
] }) }, option.value)),
|
|
138
305
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
139
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
|
|
306
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
140
307
|
] });
|
|
141
308
|
}
|
|
142
309
|
if (screen === "technique") {
|
|
143
310
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
144
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
311
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Issue Discovery Technique" }),
|
|
145
312
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
146
313
|
/* @__PURE__ */ jsx(Text, { children: "How to find the next issue to work on:" }),
|
|
147
314
|
TECHNIQUE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedTechIndex ? "cyan" : void 0, children: [
|
|
@@ -149,13 +316,13 @@ function ConfigWizard() {
|
|
|
149
316
|
option.label
|
|
150
317
|
] }) }, option.value)),
|
|
151
318
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
152
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
|
|
319
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
153
320
|
] });
|
|
154
321
|
}
|
|
155
322
|
if (screen === "value") {
|
|
156
323
|
const techLabel = TECHNIQUE_OPTIONS.find((t) => t.value === pendingTechnique)?.label ?? pendingTechnique;
|
|
157
324
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
158
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
325
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Issue Discovery Value" }),
|
|
159
326
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
160
327
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
161
328
|
techLabel,
|
|
@@ -167,22 +334,66 @@ function ConfigWizard() {
|
|
|
167
334
|
] })
|
|
168
335
|
] }),
|
|
169
336
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
170
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type value \xB7 Enter to
|
|
337
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type value \xB7 Enter to continue \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
338
|
+
] });
|
|
339
|
+
}
|
|
340
|
+
if (screen === "system-prompt") {
|
|
341
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
342
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Claude System Prompt" }),
|
|
343
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
344
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
345
|
+
"System prompt (optional):",
|
|
346
|
+
" ",
|
|
347
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
348
|
+
systemPrompt,
|
|
349
|
+
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
350
|
+
] })
|
|
351
|
+
] }),
|
|
352
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
353
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save and exit \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
354
|
+
] });
|
|
355
|
+
}
|
|
356
|
+
if (screen === "prompts-menu") {
|
|
357
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
358
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts" }),
|
|
359
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
360
|
+
PROMPTS_MENU_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === promptsMenuIndex ? "cyan" : void 0, children: [
|
|
361
|
+
index === promptsMenuIndex ? "\u276F " : " ",
|
|
362
|
+
option
|
|
363
|
+
] }) }, option)),
|
|
364
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
365
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to edit \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
366
|
+
] });
|
|
367
|
+
}
|
|
368
|
+
if (screen === "sonar-prompt") {
|
|
369
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
370
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts \u2014 Sonar" }),
|
|
371
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
372
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
373
|
+
"Sonar prompt:",
|
|
374
|
+
" ",
|
|
375
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
376
|
+
sonarPrompt,
|
|
377
|
+
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
378
|
+
] })
|
|
379
|
+
] }),
|
|
380
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
381
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
171
382
|
] });
|
|
172
383
|
}
|
|
173
384
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
174
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
385
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts \u2014 Fix-Comments" }),
|
|
175
386
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
176
387
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
177
|
-
"
|
|
388
|
+
"Fix-Comments prompt:",
|
|
178
389
|
" ",
|
|
179
390
|
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
180
|
-
|
|
391
|
+
fixCommentsPrompt,
|
|
181
392
|
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
182
393
|
] })
|
|
183
394
|
] }),
|
|
184
395
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
185
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save
|
|
396
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
186
397
|
] });
|
|
187
398
|
}
|
|
188
399
|
|
|
@@ -195,7 +406,7 @@ var configSetType = new Command("type").description("Set the remote environment
|
|
|
195
406
|
`);
|
|
196
407
|
process.exit(1);
|
|
197
408
|
}
|
|
198
|
-
const current =
|
|
409
|
+
const current = readRawConfig();
|
|
199
410
|
writeConfig({ ...current, remoteType: value });
|
|
200
411
|
process.stdout.write(`Remote type set to: ${value}
|
|
201
412
|
`);
|
|
@@ -206,19 +417,19 @@ var configSetIssueDiscoveryTechnique = new Command("issue-discovery-technique").
|
|
|
206
417
|
`);
|
|
207
418
|
process.exit(1);
|
|
208
419
|
}
|
|
209
|
-
const current =
|
|
420
|
+
const current = readRawConfig();
|
|
210
421
|
writeConfig({ ...current, issueDiscoveryTechnique: value });
|
|
211
422
|
process.stdout.write(`Issue discovery technique set to: ${value}
|
|
212
423
|
`);
|
|
213
424
|
});
|
|
214
425
|
var configSetIssueDiscoveryValue = new Command("issue-discovery-value").description("Set the value for the issue discovery technique (label name, username, or search string)").argument("<value>", "The filter value").action((value) => {
|
|
215
|
-
const current =
|
|
426
|
+
const current = readRawConfig();
|
|
216
427
|
writeConfig({ ...current, issueDiscoveryValue: value });
|
|
217
428
|
process.stdout.write(`Issue discovery value set to: ${value}
|
|
218
429
|
`);
|
|
219
430
|
});
|
|
220
431
|
var configSetClaudeSystemPrompt = new Command("claude-system-prompt").description("Set the system prompt used when invoking Claude Code").argument("<value>", "System prompt text").action((value) => {
|
|
221
|
-
const current =
|
|
432
|
+
const current = readRawConfig();
|
|
222
433
|
writeConfig({ ...current, claudeSystemPrompt: value });
|
|
223
434
|
process.stdout.write(`Claude system prompt set.
|
|
224
435
|
`);
|
|
@@ -299,6 +510,8 @@ function getCurrentBranch() {
|
|
|
299
510
|
}
|
|
300
511
|
return stdout.trim();
|
|
301
512
|
}
|
|
513
|
+
var SONAR_FETCH_TIMEOUT_MS = 5e3;
|
|
514
|
+
var SONAR_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
|
|
302
515
|
function parseOwnerRepo() {
|
|
303
516
|
const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
|
|
304
517
|
if (status !== 0) return null;
|
|
@@ -335,7 +548,295 @@ function fetchCheckRunOutputs(ownerRepo, sha) {
|
|
|
335
548
|
}
|
|
336
549
|
return map;
|
|
337
550
|
}
|
|
338
|
-
function
|
|
551
|
+
function extractSonarProjectKey(url) {
|
|
552
|
+
try {
|
|
553
|
+
const parsed = new URL(url);
|
|
554
|
+
const id = parsed.searchParams.get("id");
|
|
555
|
+
return id ?? null;
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function isSonarUrl(url) {
|
|
561
|
+
try {
|
|
562
|
+
const hostname = new URL(url).hostname;
|
|
563
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
564
|
+
} catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function stripHtml(text) {
|
|
569
|
+
let stripped = "";
|
|
570
|
+
let inTag = false;
|
|
571
|
+
for (const char of text) {
|
|
572
|
+
if (char === "<") {
|
|
573
|
+
inTag = true;
|
|
574
|
+
stripped += " ";
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (char === ">") {
|
|
578
|
+
inTag = false;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!inTag) {
|
|
582
|
+
stripped += char;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return stripped;
|
|
586
|
+
}
|
|
587
|
+
function normalizeText(text) {
|
|
588
|
+
if (!text) return void 0;
|
|
589
|
+
const normalized = stripHtml(text).replaceAll(/\s+/g, " ").trim();
|
|
590
|
+
return normalized || void 0;
|
|
591
|
+
}
|
|
592
|
+
function resolveSonarPath(componentKey, components) {
|
|
593
|
+
if (!componentKey) return void 0;
|
|
594
|
+
const mapped = components.get(componentKey);
|
|
595
|
+
if (mapped) return mapped;
|
|
596
|
+
const separatorIndex = componentKey.indexOf(":");
|
|
597
|
+
if (separatorIndex === -1) return void 0;
|
|
598
|
+
const path = componentKey.slice(separatorIndex + 1).trim();
|
|
599
|
+
return path || void 0;
|
|
600
|
+
}
|
|
601
|
+
function mapSonarIssuesPage(data, targetIssues, components, rules) {
|
|
602
|
+
for (const component of data.components ?? []) {
|
|
603
|
+
if (component.key && component.path) {
|
|
604
|
+
components.set(component.key, component.path);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const rule of data.rules ?? []) {
|
|
608
|
+
if (!rule.key) continue;
|
|
609
|
+
const explanation = normalizeText(rule.htmlDesc) ?? normalizeText(rule.htmlNote) ?? normalizeText(rule.name);
|
|
610
|
+
if (explanation) {
|
|
611
|
+
rules.set(rule.key, explanation);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
for (const issue of data.issues ?? []) {
|
|
615
|
+
if (!issue.key || !issue.message) continue;
|
|
616
|
+
const ruleKey = issue.rule;
|
|
617
|
+
const mappedIssue = {
|
|
618
|
+
key: issue.key,
|
|
619
|
+
severity: issue.severity,
|
|
620
|
+
type: issue.type,
|
|
621
|
+
message: issue.message,
|
|
622
|
+
path: resolveSonarPath(issue.component, components),
|
|
623
|
+
line: issue.line ?? issue.textRange?.startLine ?? null,
|
|
624
|
+
...ruleKey ? { rule: ruleKey } : {},
|
|
625
|
+
...ruleKey && rules.has(ruleKey) ? { explanation: rules.get(ruleKey) } : {}
|
|
626
|
+
};
|
|
627
|
+
targetIssues.push(mappedIssue);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function mapSonarHotspotsPage(data, targetHotspots, components) {
|
|
631
|
+
for (const component of data.components ?? []) {
|
|
632
|
+
if (component.key && component.path) {
|
|
633
|
+
components.set(component.key, component.path);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
targetHotspots.push(...data.hotspots ?? []);
|
|
637
|
+
}
|
|
638
|
+
function mapSonarHotspot(hotspot, components, detail) {
|
|
639
|
+
const rule = detail?.rule;
|
|
640
|
+
return {
|
|
641
|
+
key: detail?.key ?? hotspot.key ?? "",
|
|
642
|
+
rule: hotspot.ruleKey ?? rule?.key ?? "",
|
|
643
|
+
ruleName: normalizeText(rule?.name),
|
|
644
|
+
status: detail?.status ?? hotspot.status ?? "UNKNOWN",
|
|
645
|
+
message: detail?.message ?? hotspot.message ?? "",
|
|
646
|
+
path: detail?.component?.path ?? resolveSonarPath(hotspot.component, components),
|
|
647
|
+
line: detail?.line ?? detail?.textRange?.startLine ?? hotspot.line ?? hotspot.textRange?.startLine ?? null,
|
|
648
|
+
securityCategory: rule?.securityCategory ?? hotspot.securityCategory,
|
|
649
|
+
vulnerabilityProbability: rule?.vulnerabilityProbability ?? hotspot.vulnerabilityProbability,
|
|
650
|
+
riskDescription: normalizeText(rule?.riskDescription),
|
|
651
|
+
vulnerabilityDescription: normalizeText(rule?.vulnerabilityDescription),
|
|
652
|
+
fixRecommendations: normalizeText(rule?.fixRecommendations)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function fetchSonarJson(apiUrl) {
|
|
656
|
+
const controller = new AbortController();
|
|
657
|
+
const timeoutId = setTimeout(() => controller.abort(), SONAR_FETCH_TIMEOUT_MS);
|
|
658
|
+
try {
|
|
659
|
+
const response = await fetch(apiUrl, { signal: controller.signal });
|
|
660
|
+
if (!response.ok) {
|
|
661
|
+
return { ok: false, status: response.status };
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
ok: true,
|
|
665
|
+
status: response.status,
|
|
666
|
+
data: await response.json()
|
|
667
|
+
};
|
|
668
|
+
} catch {
|
|
669
|
+
return { ok: false, status: null };
|
|
670
|
+
} finally {
|
|
671
|
+
clearTimeout(timeoutId);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
async function fetchSonarNewIssues(projectKey, prNumber) {
|
|
675
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
|
|
676
|
+
const response = await fetchSonarJson(apiUrl);
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
return response.data.paging?.total ?? null;
|
|
681
|
+
}
|
|
682
|
+
async function fetchSonarGateViolations(projectKey, prNumber) {
|
|
683
|
+
const apiUrl = `https://sonarcloud.io/api/qualitygates/project_status?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}`;
|
|
684
|
+
const response = await fetchSonarJson(apiUrl);
|
|
685
|
+
if (!response.ok) {
|
|
686
|
+
return response;
|
|
687
|
+
}
|
|
688
|
+
const projectStatus = response.data.projectStatus;
|
|
689
|
+
const gateViolations = (projectStatus?.conditions ?? []).filter((condition) => condition.status !== void 0 && condition.status !== "OK").map((condition) => ({
|
|
690
|
+
metricKey: condition.metricKey ?? "unknown",
|
|
691
|
+
status: condition.status ?? "ERROR",
|
|
692
|
+
comparator: condition.comparator,
|
|
693
|
+
actualValue: condition.actualValue,
|
|
694
|
+
errorThreshold: condition.errorThreshold
|
|
695
|
+
}));
|
|
696
|
+
return {
|
|
697
|
+
ok: true,
|
|
698
|
+
status: response.status,
|
|
699
|
+
data: {
|
|
700
|
+
status: "available",
|
|
701
|
+
qualityGateStatus: projectStatus?.status,
|
|
702
|
+
gateViolations,
|
|
703
|
+
issues: [],
|
|
704
|
+
securityHotspots: []
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async function fetchSonarIssues(projectKey, prNumber) {
|
|
709
|
+
const pageSize = 100;
|
|
710
|
+
const issues = [];
|
|
711
|
+
const components = /* @__PURE__ */ new Map();
|
|
712
|
+
const rules = /* @__PURE__ */ new Map();
|
|
713
|
+
let page = 1;
|
|
714
|
+
let total = null;
|
|
715
|
+
while (true) {
|
|
716
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=${String(pageSize)}&p=${String(page)}&additionalFields=_all`;
|
|
717
|
+
const response = await fetchSonarJson(apiUrl);
|
|
718
|
+
if (!response.ok) {
|
|
719
|
+
return response;
|
|
720
|
+
}
|
|
721
|
+
const paging = response.data.paging;
|
|
722
|
+
total ??= paging?.total ?? null;
|
|
723
|
+
mapSonarIssuesPage(response.data, issues, components, rules);
|
|
724
|
+
const fetchedCount = page * (paging?.pageSize ?? pageSize);
|
|
725
|
+
if (paging?.total === void 0 || fetchedCount >= paging.total) {
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
page += 1;
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
ok: true,
|
|
732
|
+
status: 200,
|
|
733
|
+
data: {
|
|
734
|
+
total,
|
|
735
|
+
issues
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
async function fetchSonarHotspotDetail(hotspotKey) {
|
|
740
|
+
const apiUrl = `https://sonarcloud.io/api/hotspots/show?hotspot=${encodeURIComponent(hotspotKey)}`;
|
|
741
|
+
return fetchSonarJson(apiUrl);
|
|
742
|
+
}
|
|
743
|
+
async function fetchSonarHotspots(projectKey, prNumber) {
|
|
744
|
+
const pageSize = 100;
|
|
745
|
+
const rawHotspots = [];
|
|
746
|
+
const components = /* @__PURE__ */ new Map();
|
|
747
|
+
let page = 1;
|
|
748
|
+
while (true) {
|
|
749
|
+
const apiUrl = `https://sonarcloud.io/api/hotspots/search?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&onlyMine=false&sinceLeakPeriod=true&ps=${String(pageSize)}&p=${String(page)}`;
|
|
750
|
+
const response = await fetchSonarJson(apiUrl);
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
return response;
|
|
753
|
+
}
|
|
754
|
+
const paging = response.data.paging;
|
|
755
|
+
mapSonarHotspotsPage(response.data, rawHotspots, components);
|
|
756
|
+
const fetchedCount = page * (paging?.pageSize ?? pageSize);
|
|
757
|
+
if (paging?.total === void 0 || fetchedCount >= paging.total) {
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
page += 1;
|
|
761
|
+
}
|
|
762
|
+
const detailResults = await Promise.all(rawHotspots.map((hotspot) => fetchSonarHotspotDetail(hotspot.key ?? "")));
|
|
763
|
+
const securityHotspots = [];
|
|
764
|
+
for (let index = 0; index < rawHotspots.length; index += 1) {
|
|
765
|
+
const hotspot = rawHotspots[index];
|
|
766
|
+
const detailResult = detailResults[index];
|
|
767
|
+
if (detailResult && !detailResult.ok && detailResult.status === 401) {
|
|
768
|
+
return detailResult;
|
|
769
|
+
}
|
|
770
|
+
securityHotspots.push(mapSonarHotspot(hotspot, components, detailResult?.ok ? detailResult.data : void 0));
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
ok: true,
|
|
774
|
+
status: 200,
|
|
775
|
+
data: securityHotspots
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function sonarPrivateFailureSummary(sonarcloudUrl) {
|
|
779
|
+
return {
|
|
780
|
+
status: "private",
|
|
781
|
+
gateViolations: [],
|
|
782
|
+
issues: [],
|
|
783
|
+
securityHotspots: [],
|
|
784
|
+
privateMessage: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
|
|
788
|
+
const [gateResult, issuesResult, hotspotsResult] = await Promise.all([
|
|
789
|
+
fetchSonarGateViolations(projectKey, prNumber),
|
|
790
|
+
fetchSonarIssues(projectKey, prNumber),
|
|
791
|
+
fetchSonarHotspots(projectKey, prNumber)
|
|
792
|
+
]);
|
|
793
|
+
if (!gateResult.ok && gateResult.status === 401) {
|
|
794
|
+
return {
|
|
795
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
796
|
+
issueTotal: null
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (!issuesResult.ok && issuesResult.status === 401) {
|
|
800
|
+
return {
|
|
801
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
802
|
+
issueTotal: null
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
if (!hotspotsResult.ok && hotspotsResult.status === 401) {
|
|
806
|
+
return {
|
|
807
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
808
|
+
issueTotal: null
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const gateViolations = gateResult.ok ? gateResult.data.gateViolations : [];
|
|
812
|
+
const issues = issuesResult.ok ? issuesResult.data.issues : [];
|
|
813
|
+
const securityHotspots = hotspotsResult.ok ? hotspotsResult.data : [];
|
|
814
|
+
const qualityGateStatus = gateResult.ok ? gateResult.data.qualityGateStatus : void 0;
|
|
815
|
+
const issueTotal = issuesResult.ok ? issuesResult.data.total : null;
|
|
816
|
+
if (gateViolations.length === 0 && issues.length === 0 && securityHotspots.length === 0 && !qualityGateStatus) {
|
|
817
|
+
return {
|
|
818
|
+
summary: {
|
|
819
|
+
status: "unavailable",
|
|
820
|
+
gateViolations: [],
|
|
821
|
+
issues: [],
|
|
822
|
+
securityHotspots: [],
|
|
823
|
+
unavailableMessage: "SonarCloud failure details are unavailable right now."
|
|
824
|
+
},
|
|
825
|
+
issueTotal
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
summary: {
|
|
830
|
+
status: "available",
|
|
831
|
+
qualityGateStatus,
|
|
832
|
+
gateViolations,
|
|
833
|
+
issues,
|
|
834
|
+
securityHotspots
|
|
835
|
+
},
|
|
836
|
+
issueTotal
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async function getPrInfoGh(branch) {
|
|
339
840
|
const { stdout, stderr, status } = run2("gh", [
|
|
340
841
|
"pr",
|
|
341
842
|
"view",
|
|
@@ -365,9 +866,36 @@ function getPrInfoGh(branch) {
|
|
|
365
866
|
detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
|
|
366
867
|
};
|
|
367
868
|
});
|
|
368
|
-
|
|
869
|
+
const sonarCheck = checks.find((c) => isSonarUrl(c.detailsUrl));
|
|
870
|
+
let sonarcloudUrl;
|
|
871
|
+
let sonarNewIssues;
|
|
872
|
+
let sonarFailures;
|
|
873
|
+
if (sonarCheck) {
|
|
874
|
+
sonarcloudUrl = sonarCheck.detailsUrl;
|
|
875
|
+
const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
|
|
876
|
+
if (projectKey) {
|
|
877
|
+
if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
|
|
878
|
+
const sonarSummary = await fetchSonarFailureSummary(projectKey, raw.number, sonarcloudUrl);
|
|
879
|
+
sonarFailures = sonarSummary.summary;
|
|
880
|
+
sonarNewIssues = sonarSummary.issueTotal;
|
|
881
|
+
} else {
|
|
882
|
+
sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
|
|
883
|
+
}
|
|
884
|
+
} else {
|
|
885
|
+
sonarNewIssues = null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
number: raw.number,
|
|
890
|
+
title: raw.title,
|
|
891
|
+
state: raw.state,
|
|
892
|
+
url: raw.url,
|
|
893
|
+
checks,
|
|
894
|
+
...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues },
|
|
895
|
+
...sonarFailures === void 0 ? {} : { sonarFailures }
|
|
896
|
+
};
|
|
369
897
|
}
|
|
370
|
-
function getPrInfo2(branch) {
|
|
898
|
+
async function getPrInfo2(branch) {
|
|
371
899
|
const config = readConfig();
|
|
372
900
|
if (config.remoteType === "azdo") {
|
|
373
901
|
return getPrInfo();
|
|
@@ -471,6 +999,23 @@ function getPrComments(branch) {
|
|
|
471
999
|
}
|
|
472
1000
|
return getPrCommentsGh(branch);
|
|
473
1001
|
}
|
|
1002
|
+
function resolveCurrentBranchComments() {
|
|
1003
|
+
let branch;
|
|
1004
|
+
try {
|
|
1005
|
+
branch = getCurrentBranch();
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
return { ok: false, kind: "error", message: err.message };
|
|
1008
|
+
}
|
|
1009
|
+
let raw;
|
|
1010
|
+
try {
|
|
1011
|
+
raw = getPrComments(branch);
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
return { ok: false, kind: "error", message: err.message };
|
|
1014
|
+
}
|
|
1015
|
+
if (raw === "unsupported") return { ok: false, kind: "unsupported" };
|
|
1016
|
+
if (raw === null) return { ok: false, kind: "no-pr", branch };
|
|
1017
|
+
return { ok: true, branch, comments: raw };
|
|
1018
|
+
}
|
|
474
1019
|
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
475
1020
|
function getLatestTagOnMaster() {
|
|
476
1021
|
const { stdout, status } = run2("git", [
|
|
@@ -553,33 +1098,131 @@ function formatChecks(checks) {
|
|
|
553
1098
|
const sym = checkSymbol(check);
|
|
554
1099
|
const pending = check.status !== "COMPLETED" ? " (pending)" : "";
|
|
555
1100
|
lines.push(` ${sym} ${check.name}${pending}`);
|
|
556
|
-
if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) {
|
|
557
|
-
const desc = check.description.trim();
|
|
558
|
-
const url = check.detailsUrl.trim();
|
|
559
|
-
if (desc) lines.push(` Details: ${desc}`);
|
|
560
|
-
if (url) lines.push(` URL: ${url}`);
|
|
561
|
-
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
562
|
-
}
|
|
563
1101
|
}
|
|
564
1102
|
return lines.join("\n") + "\n";
|
|
565
1103
|
}
|
|
566
1104
|
function sleep(ms) {
|
|
567
|
-
return new Promise((
|
|
1105
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1106
|
+
}
|
|
1107
|
+
function isSonarCheck(check) {
|
|
1108
|
+
try {
|
|
1109
|
+
const hostname = new URL(check.detailsUrl).hostname;
|
|
1110
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
1111
|
+
} catch {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
568
1114
|
}
|
|
569
1115
|
function formatFailedChecks(failed) {
|
|
570
|
-
const lines = [];
|
|
1116
|
+
const lines = ["FailedChecks:"];
|
|
571
1117
|
for (const check of failed) {
|
|
572
1118
|
lines.push(` \u2717 ${check.name}`);
|
|
573
1119
|
const desc = check.description.trim();
|
|
574
1120
|
const url = check.detailsUrl.trim();
|
|
575
1121
|
if (desc) lines.push(` Details: ${desc}`);
|
|
576
|
-
if (url) lines.push(` URL: ${url}`);
|
|
1122
|
+
if (url && (isSonarCheck(check) || !desc)) lines.push(` URL: ${url}`);
|
|
577
1123
|
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
578
1124
|
}
|
|
579
1125
|
return lines.join("\n") + "\n";
|
|
580
1126
|
}
|
|
1127
|
+
function formatGateViolation(violation) {
|
|
1128
|
+
const parts = [sanitizeText(violation.metricKey)];
|
|
1129
|
+
if (violation.actualValue) parts.push(`actual ${sanitizeText(violation.actualValue)}`);
|
|
1130
|
+
if (violation.comparator && violation.errorThreshold) {
|
|
1131
|
+
parts.push(`${sanitizeText(violation.comparator)} ${sanitizeText(violation.errorThreshold)}`);
|
|
1132
|
+
} else if (violation.errorThreshold) {
|
|
1133
|
+
parts.push(`threshold ${sanitizeText(violation.errorThreshold)}`);
|
|
1134
|
+
}
|
|
1135
|
+
return parts.join(" | ");
|
|
1136
|
+
}
|
|
1137
|
+
function formatLocation(path, line) {
|
|
1138
|
+
if (!path) return void 0;
|
|
1139
|
+
const safePath = sanitizeText(path);
|
|
1140
|
+
if (!line) return safePath;
|
|
1141
|
+
return `${safePath}:${String(line)}`;
|
|
1142
|
+
}
|
|
1143
|
+
function formatRuleWithName(rule, ruleName) {
|
|
1144
|
+
const safeRule = sanitizeText(rule);
|
|
1145
|
+
if (!ruleName) return safeRule;
|
|
1146
|
+
return `${safeRule} (${sanitizeText(ruleName)})`;
|
|
1147
|
+
}
|
|
1148
|
+
function formatSonarIssue(issue) {
|
|
1149
|
+
const lines = [` - ${sanitizeText(issue.message)}`];
|
|
1150
|
+
const location = formatLocation(issue.path, issue.line);
|
|
1151
|
+
if (location) lines.push(` Location: ${location}`);
|
|
1152
|
+
if (issue.severity || issue.type) {
|
|
1153
|
+
const labels = [issue.severity, issue.type].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
|
|
1154
|
+
lines.push(` Classification: ${labels}`);
|
|
1155
|
+
}
|
|
1156
|
+
if (issue.rule) lines.push(` Rule: ${sanitizeText(issue.rule)}`);
|
|
1157
|
+
if (issue.explanation) lines.push(` Explanation: ${sanitizeText(issue.explanation)}`);
|
|
1158
|
+
return lines;
|
|
1159
|
+
}
|
|
1160
|
+
function formatSonarHotspot(hotspot) {
|
|
1161
|
+
const lines = [` - ${sanitizeText(hotspot.message)}`];
|
|
1162
|
+
const location = formatLocation(hotspot.path, hotspot.line);
|
|
1163
|
+
if (location) lines.push(` Location: ${location}`);
|
|
1164
|
+
if (hotspot.status) lines.push(` Status: ${sanitizeText(hotspot.status)}`);
|
|
1165
|
+
if (hotspot.vulnerabilityProbability || hotspot.securityCategory) {
|
|
1166
|
+
const labels = [hotspot.vulnerabilityProbability, hotspot.securityCategory].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
|
|
1167
|
+
lines.push(` Classification: ${labels}`);
|
|
1168
|
+
}
|
|
1169
|
+
if (hotspot.rule) {
|
|
1170
|
+
lines.push(` Rule: ${formatRuleWithName(hotspot.rule, hotspot.ruleName)}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (hotspot.riskDescription) lines.push(` Risk: ${sanitizeText(hotspot.riskDescription)}`);
|
|
1173
|
+
if (hotspot.vulnerabilityDescription) lines.push(` Review: ${sanitizeText(hotspot.vulnerabilityDescription)}`);
|
|
1174
|
+
if (hotspot.fixRecommendations) lines.push(` Fix: ${sanitizeText(hotspot.fixRecommendations)}`);
|
|
1175
|
+
return lines;
|
|
1176
|
+
}
|
|
1177
|
+
function appendGateViolations(lines, gateViolations) {
|
|
1178
|
+
if (gateViolations.length === 0) return;
|
|
1179
|
+
lines.push(" Gate Violations:");
|
|
1180
|
+
for (const violation of gateViolations) {
|
|
1181
|
+
lines.push(` - ${formatGateViolation(violation)}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function appendSonarIssues(lines, issues) {
|
|
1185
|
+
if (issues.length === 0) return;
|
|
1186
|
+
lines.push(" Issues:");
|
|
1187
|
+
for (const issue of issues) {
|
|
1188
|
+
lines.push(...formatSonarIssue(issue));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function appendSecurityHotspots(lines, securityHotspots) {
|
|
1192
|
+
if (securityHotspots.length === 0) return;
|
|
1193
|
+
lines.push(" Security Hotspots:");
|
|
1194
|
+
for (const hotspot of securityHotspots) {
|
|
1195
|
+
lines.push(...formatSonarHotspot(hotspot));
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function hasSonarFailureDetails(sonarFailures) {
|
|
1199
|
+
return Boolean(sonarFailures.qualityGateStatus) || sonarFailures.gateViolations.length > 0 || sonarFailures.issues.length > 0 || sonarFailures.securityHotspots.length > 0;
|
|
1200
|
+
}
|
|
1201
|
+
function formatSonarFailures(sonarFailures, sonarcloudUrl) {
|
|
1202
|
+
const lines = ["Sonar Failures:"];
|
|
1203
|
+
if (sonarFailures.status === "private") {
|
|
1204
|
+
lines.push(` Note: ${sanitizeText(sonarFailures.privateMessage ?? "SonarCloud project is private.")}`);
|
|
1205
|
+
return lines.join("\n") + "\n";
|
|
1206
|
+
}
|
|
1207
|
+
if (sonarFailures.status === "unavailable") {
|
|
1208
|
+
lines.push(` Note: ${sanitizeText(sonarFailures.unavailableMessage ?? "SonarCloud failure details are unavailable.")}`);
|
|
1209
|
+
if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
|
|
1210
|
+
return lines.join("\n") + "\n";
|
|
1211
|
+
}
|
|
1212
|
+
if (sonarFailures.qualityGateStatus) {
|
|
1213
|
+
lines.push(` Quality Gate: ${sanitizeText(sonarFailures.qualityGateStatus)}`);
|
|
1214
|
+
}
|
|
1215
|
+
appendGateViolations(lines, sonarFailures.gateViolations);
|
|
1216
|
+
appendSonarIssues(lines, sonarFailures.issues);
|
|
1217
|
+
appendSecurityHotspots(lines, sonarFailures.securityHotspots);
|
|
1218
|
+
if (!hasSonarFailureDetails(sonarFailures)) {
|
|
1219
|
+
lines.push(" Note: SonarCloud reported a failure but returned no violation, issue, or hotspot details.");
|
|
1220
|
+
if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
|
|
1221
|
+
}
|
|
1222
|
+
return lines.join("\n") + "\n";
|
|
1223
|
+
}
|
|
581
1224
|
var POLL_INTERVAL_MS = 1e4;
|
|
582
|
-
var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then
|
|
1225
|
+
var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then print the normal get-pr-info output").addHelpText(
|
|
583
1226
|
"after",
|
|
584
1227
|
`
|
|
585
1228
|
Check status symbols:
|
|
@@ -588,10 +1231,11 @@ Check status symbols:
|
|
|
588
1231
|
\u25CF Pending (status: QUEUED or IN_PROGRESS)
|
|
589
1232
|
\u25CB Skipped (conclusion: SKIPPED or NEUTRAL)
|
|
590
1233
|
|
|
591
|
-
|
|
1234
|
+
When checks fail, details are printed in a trailing FailedChecks section.
|
|
592
1235
|
See docs/git.md for full output reference.`
|
|
593
1236
|
).action(async (options) => {
|
|
594
1237
|
let branch;
|
|
1238
|
+
let pr = null;
|
|
595
1239
|
try {
|
|
596
1240
|
branch = getCurrentBranch();
|
|
597
1241
|
} catch (err) {
|
|
@@ -600,54 +1244,31 @@ See docs/git.md for full output reference.`
|
|
|
600
1244
|
process.exit(1);
|
|
601
1245
|
}
|
|
602
1246
|
if (options.waitFinishChecks) {
|
|
603
|
-
let pr2;
|
|
604
1247
|
while (true) {
|
|
605
1248
|
try {
|
|
606
|
-
|
|
1249
|
+
pr = await getPrInfo2(branch);
|
|
607
1250
|
} catch (err) {
|
|
608
1251
|
process.stderr.write(`Error: ${err.message}
|
|
609
1252
|
`);
|
|
610
1253
|
process.exit(1);
|
|
611
1254
|
}
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
`);
|
|
615
|
-
process.exit(1);
|
|
1255
|
+
if (pr === null) {
|
|
1256
|
+
break;
|
|
616
1257
|
}
|
|
617
|
-
const running =
|
|
1258
|
+
const running = pr.checks.filter((c) => c.status !== "COMPLETED");
|
|
618
1259
|
if (running.length === 0) break;
|
|
619
1260
|
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
620
1261
|
`);
|
|
621
1262
|
await sleep(POLL_INTERVAL_MS);
|
|
622
1263
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
process.stdout.write(`All checks passed. \u2713
|
|
629
|
-
`);
|
|
630
|
-
}
|
|
631
|
-
process.exit(0);
|
|
632
|
-
} else {
|
|
633
|
-
if (options.json) {
|
|
634
|
-
process.stdout.write(JSON.stringify({ result: "failed", failed }, null, 2) + "\n");
|
|
635
|
-
} else {
|
|
636
|
-
process.stdout.write(`${failed.length} check(s) failed:
|
|
1264
|
+
} else {
|
|
1265
|
+
try {
|
|
1266
|
+
pr = await getPrInfo2(branch);
|
|
1267
|
+
} catch (err) {
|
|
1268
|
+
process.stderr.write(`Error: ${err.message}
|
|
637
1269
|
`);
|
|
638
|
-
process.stdout.write(formatFailedChecks(failed));
|
|
639
|
-
}
|
|
640
1270
|
process.exit(1);
|
|
641
1271
|
}
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
let pr;
|
|
645
|
-
try {
|
|
646
|
-
pr = getPrInfo2(branch);
|
|
647
|
-
} catch (err) {
|
|
648
|
-
process.stderr.write(`Error: ${err.message}
|
|
649
|
-
`);
|
|
650
|
-
process.exit(1);
|
|
651
1272
|
}
|
|
652
1273
|
if (pr === null) {
|
|
653
1274
|
process.stdout.write(`No pull request found for branch: ${branch}
|
|
@@ -657,13 +1278,27 @@ See docs/git.md for full output reference.`
|
|
|
657
1278
|
if (options.json) {
|
|
658
1279
|
process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
|
|
659
1280
|
} else {
|
|
1281
|
+
const failed = pr.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
660
1282
|
process.stdout.write(`PR: #${pr.number}
|
|
661
1283
|
Title: ${pr.title}
|
|
662
1284
|
State: ${pr.state}
|
|
663
1285
|
URL: ${pr.url}
|
|
664
1286
|
`);
|
|
1287
|
+
if (pr.sonarcloudUrl !== void 0) {
|
|
1288
|
+
process.stdout.write(`Sonar: ${pr.sonarcloudUrl}
|
|
1289
|
+
`);
|
|
1290
|
+
const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
|
|
1291
|
+
process.stdout.write(`Sonar New Issues: ${issueStr}
|
|
1292
|
+
`);
|
|
1293
|
+
}
|
|
665
1294
|
process.stdout.write(formatCheckSummary(pr.checks));
|
|
666
1295
|
process.stdout.write(formatChecks(pr.checks));
|
|
1296
|
+
if (failed.length > 0) {
|
|
1297
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
1298
|
+
}
|
|
1299
|
+
if (pr.sonarFailures !== void 0) {
|
|
1300
|
+
process.stdout.write(formatSonarFailures(pr.sonarFailures, pr.sonarcloudUrl));
|
|
1301
|
+
}
|
|
667
1302
|
}
|
|
668
1303
|
});
|
|
669
1304
|
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
@@ -724,7 +1359,7 @@ ${safeBody}`);
|
|
|
724
1359
|
}
|
|
725
1360
|
process.stdout.write(lines.join("\n\n") + "\n");
|
|
726
1361
|
});
|
|
727
|
-
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
1362
|
+
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(async () => {
|
|
728
1363
|
let branch;
|
|
729
1364
|
try {
|
|
730
1365
|
branch = getCurrentBranch();
|
|
@@ -745,7 +1380,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
745
1380
|
}
|
|
746
1381
|
let pr;
|
|
747
1382
|
try {
|
|
748
|
-
pr = getPrInfo2(branch);
|
|
1383
|
+
pr = await getPrInfo2(branch);
|
|
749
1384
|
} catch (err) {
|
|
750
1385
|
process.stderr.write(`Error: ${err.message}
|
|
751
1386
|
`);
|
|
@@ -875,10 +1510,8 @@ minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
|
|
|
875
1510
|
var gitCommand = new Command2("git").description("Git workflow commands (some require gh CLI)").addCommand(getPrInfoCmd).addCommand(getPrCommentsCmd).addCommand(finishFeatureCmd).addCommand(publishReleaseCmd);
|
|
876
1511
|
|
|
877
1512
|
// src/commands/getReady.ts
|
|
1513
|
+
import { createInterface as createInterface2 } from "readline";
|
|
878
1514
|
import { Command as Command3 } from "commander";
|
|
879
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
880
|
-
import { existsSync } from "fs";
|
|
881
|
-
import { delimiter, join as join2 } from "path";
|
|
882
1515
|
|
|
883
1516
|
// src/config/githubService.ts
|
|
884
1517
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -897,14 +1530,14 @@ function run3(cmd, args) {
|
|
|
897
1530
|
status: result.status ?? 1
|
|
898
1531
|
};
|
|
899
1532
|
}
|
|
900
|
-
function listIssues(technique, value) {
|
|
1533
|
+
function listIssues(technique, value, limit = 10) {
|
|
901
1534
|
const baseArgs = [
|
|
902
1535
|
"issue",
|
|
903
1536
|
"list",
|
|
904
1537
|
"--state",
|
|
905
1538
|
"open",
|
|
906
1539
|
"--limit",
|
|
907
|
-
|
|
1540
|
+
String(limit),
|
|
908
1541
|
"--json",
|
|
909
1542
|
"number,title,body,url"
|
|
910
1543
|
];
|
|
@@ -924,11 +1557,7 @@ function listIssues(technique, value) {
|
|
|
924
1557
|
if (status !== 0) {
|
|
925
1558
|
throw new Error(stderr.trim() || "Failed to query GitHub issues. Is `gh` installed and authenticated?");
|
|
926
1559
|
}
|
|
927
|
-
|
|
928
|
-
if (issues.length === 0) {
|
|
929
|
-
return null;
|
|
930
|
-
}
|
|
931
|
-
return issues[0];
|
|
1560
|
+
return JSON.parse(stdout);
|
|
932
1561
|
}
|
|
933
1562
|
function postComment(issueNumber, body) {
|
|
934
1563
|
const { stderr, status } = run3("gh", ["issue", "comment", String(issueNumber), "--body", body]);
|
|
@@ -937,40 +1566,222 @@ function postComment(issueNumber, body) {
|
|
|
937
1566
|
}
|
|
938
1567
|
}
|
|
939
1568
|
|
|
940
|
-
// src/
|
|
1569
|
+
// src/claude/claudeService.ts
|
|
1570
|
+
import { spawn, spawnSync as spawnSync4 } from "child_process";
|
|
1571
|
+
import { createInterface } from "readline";
|
|
1572
|
+
import { existsSync } from "fs";
|
|
1573
|
+
import { delimiter, join as join3 } from "path";
|
|
1574
|
+
|
|
1575
|
+
// src/cli/spawnUtils.ts
|
|
1576
|
+
function truncate(str, max) {
|
|
1577
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
1578
|
+
}
|
|
1579
|
+
function handleSpawnError(error, toolName) {
|
|
1580
|
+
if (!error) return;
|
|
1581
|
+
const err = error;
|
|
1582
|
+
if (err.code === "ENOENT") {
|
|
1583
|
+
process.stderr.write(`Error: \`${toolName}\` CLI is not installed or not on PATH.
|
|
1584
|
+
`);
|
|
1585
|
+
process.exit(1);
|
|
1586
|
+
}
|
|
1587
|
+
process.stderr.write(`Error: ${err.message}
|
|
1588
|
+
`);
|
|
1589
|
+
process.exit(1);
|
|
1590
|
+
}
|
|
1591
|
+
function handleExitCode(status, toolName) {
|
|
1592
|
+
if (status === null) {
|
|
1593
|
+
process.stderr.write(`Error: ${toolName} terminated abnormally (exit code is null, likely due to a signal).
|
|
1594
|
+
`);
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1597
|
+
if (status !== 0) {
|
|
1598
|
+
process.stderr.write(`Error: ${toolName} exited with code ${status}.
|
|
1599
|
+
`);
|
|
1600
|
+
process.exit(status);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// src/claude/claudeService.ts
|
|
941
1605
|
function resolveCommand(name) {
|
|
942
1606
|
const pathDirs = (process.env["PATH"] ?? "").split(delimiter);
|
|
943
1607
|
for (const dir of pathDirs) {
|
|
944
|
-
const candidate =
|
|
1608
|
+
const candidate = join3(dir, name);
|
|
945
1609
|
if (existsSync(candidate)) return candidate;
|
|
946
1610
|
}
|
|
947
1611
|
return name;
|
|
948
1612
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1613
|
+
var MODEL_IDS = {
|
|
1614
|
+
opus: "claude-opus-4-6",
|
|
1615
|
+
sonnet: "claude-sonnet-4-6",
|
|
1616
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
1617
|
+
};
|
|
1618
|
+
function resolveModelOption(opts) {
|
|
1619
|
+
const selected = ["opus", "sonnet", "haiku"].filter((m) => opts[m]);
|
|
1620
|
+
if (selected.length > 1) {
|
|
1621
|
+
process.stderr.write(`Error: --${selected[0]} and --${selected[1]} are mutually exclusive.
|
|
1622
|
+
`);
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
return selected.length === 1 ? MODEL_IDS[selected[0]] : void 0;
|
|
1626
|
+
}
|
|
1627
|
+
function invokeClaudeCode(prompt, options = {}) {
|
|
1628
|
+
if (options.verbose) {
|
|
1629
|
+
return invokeClaudeCodeVerbose(prompt, options.yolo ?? false, options.model);
|
|
1630
|
+
}
|
|
1631
|
+
invokeClaudeCodeSync(prompt, options.yolo ?? false, options.model);
|
|
1632
|
+
}
|
|
1633
|
+
function invokeClaudeCodeSync(prompt, yolo, model) {
|
|
953
1634
|
const claudeBin = resolveCommand("claude");
|
|
954
|
-
const args =
|
|
1635
|
+
const args = [];
|
|
1636
|
+
if (yolo) args.push("--dangerously-skip-permissions");
|
|
1637
|
+
if (model) args.push("--model", model);
|
|
1638
|
+
args.push("-p", prompt);
|
|
955
1639
|
const result = spawnSync4(claudeBin, args, { encoding: "utf8", stdio: "inherit" });
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1640
|
+
handleSpawnError(result.error, "claude");
|
|
1641
|
+
handleExitCode(result.status, "Claude Code");
|
|
1642
|
+
}
|
|
1643
|
+
function invokeClaudeCodeVerbose(prompt, yolo, model) {
|
|
1644
|
+
return new Promise((resolve3) => {
|
|
1645
|
+
const claudeBin = resolveCommand("claude");
|
|
1646
|
+
const args = [];
|
|
1647
|
+
if (yolo) args.push("--dangerously-skip-permissions");
|
|
1648
|
+
if (model) args.push("--model", model);
|
|
1649
|
+
args.push("--verbose", "--output-format", "stream-json", "-p", prompt);
|
|
1650
|
+
const child = spawn(claudeBin, args, { stdio: ["inherit", "pipe", "inherit"] });
|
|
1651
|
+
const rl = createInterface({ input: child.stdout });
|
|
1652
|
+
let turnCount = 0;
|
|
1653
|
+
child.on("error", (err) => {
|
|
1654
|
+
handleSpawnError(err, "claude");
|
|
1655
|
+
});
|
|
1656
|
+
rl.on("line", (line) => {
|
|
1657
|
+
try {
|
|
1658
|
+
const event = JSON.parse(line);
|
|
1659
|
+
formatEvent(event, turnCount);
|
|
1660
|
+
if (event["type"] === "assistant") turnCount++;
|
|
1661
|
+
} catch {
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
child.on("close", (code) => {
|
|
1665
|
+
handleExitCode(code, "Claude Code");
|
|
1666
|
+
resolve3();
|
|
1667
|
+
});
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
function formatEvent(event, turnCount) {
|
|
1671
|
+
const type = event["type"];
|
|
1672
|
+
if (type === "assistant") {
|
|
1673
|
+
const message = event["message"];
|
|
1674
|
+
const content = message?.["content"];
|
|
1675
|
+
if (!content) return;
|
|
1676
|
+
for (const block of content) {
|
|
1677
|
+
if (block["type"] === "tool_use") {
|
|
1678
|
+
const toolName = block["name"];
|
|
1679
|
+
const input = block["input"];
|
|
1680
|
+
const summary = summarizeTool(toolName, input);
|
|
1681
|
+
process.stderr.write(` [step ${turnCount + 1}] ${summary}
|
|
1682
|
+
`);
|
|
1683
|
+
} else if (block["type"] === "text") {
|
|
1684
|
+
const text = block["text"] ?? "";
|
|
1685
|
+
if (text.length > 0) {
|
|
1686
|
+
const preview = text.length > 120 ? text.slice(0, 120) + "..." : text;
|
|
1687
|
+
const firstLine = preview.split("\n")[0];
|
|
1688
|
+
process.stderr.write(` [step ${turnCount + 1}] ${firstLine}
|
|
1689
|
+
`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
961
1692
|
}
|
|
962
|
-
|
|
1693
|
+
} else if (type === "result") {
|
|
1694
|
+
const result = event["result"];
|
|
1695
|
+
const cost = event["cost_usd"];
|
|
1696
|
+
const duration = event["duration_ms"];
|
|
1697
|
+
const turns = event["num_turns"];
|
|
1698
|
+
process.stderr.write("\n--- Result ---\n");
|
|
1699
|
+
if (cost !== void 0 || duration !== void 0 || turns !== void 0) {
|
|
1700
|
+
const parts = [];
|
|
1701
|
+
if (turns !== void 0) parts.push(`${turns} turns`);
|
|
1702
|
+
if (duration !== void 0) parts.push(`${(duration / 1e3).toFixed(1)}s`);
|
|
1703
|
+
if (cost !== void 0) parts.push(`$${cost.toFixed(4)}`);
|
|
1704
|
+
process.stderr.write(` [info] ${parts.join(" | ")}
|
|
963
1705
|
`);
|
|
964
|
-
|
|
1706
|
+
}
|
|
1707
|
+
if (result) {
|
|
1708
|
+
process.stdout.write(result + "\n");
|
|
1709
|
+
}
|
|
965
1710
|
}
|
|
966
|
-
|
|
967
|
-
|
|
1711
|
+
}
|
|
1712
|
+
function summarizeTool(name, input) {
|
|
1713
|
+
if (!input) return `tool: ${name}`;
|
|
1714
|
+
switch (name) {
|
|
1715
|
+
case "Read":
|
|
1716
|
+
return `reading ${input["file_path"] ?? "file"}`;
|
|
1717
|
+
case "Write":
|
|
1718
|
+
return `writing ${input["file_path"] ?? "file"}`;
|
|
1719
|
+
case "Edit":
|
|
1720
|
+
return `editing ${input["file_path"] ?? "file"}`;
|
|
1721
|
+
case "Bash":
|
|
1722
|
+
return `running: ${truncate(String(input["command"] ?? ""), 80)}`;
|
|
1723
|
+
case "Glob":
|
|
1724
|
+
return `searching files: ${input["pattern"] ?? ""}`;
|
|
1725
|
+
case "Grep":
|
|
1726
|
+
return `searching content: ${truncate(String(input["pattern"] ?? ""), 60)}`;
|
|
1727
|
+
case "Agent":
|
|
1728
|
+
return `spawning agent: ${input["description"] ?? name}`;
|
|
1729
|
+
default:
|
|
1730
|
+
return `tool: ${name}`;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/codex/codexService.ts
|
|
1735
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
1736
|
+
function invokeCodexCode(prompt, options = {}) {
|
|
1737
|
+
if (options.verbose) {
|
|
1738
|
+
process.stderr.write("Warning: --verbose is not supported for Codex and will be ignored.\n");
|
|
1739
|
+
}
|
|
1740
|
+
invokeCodexCodeSync(prompt, options.yolo ?? false, options.model);
|
|
1741
|
+
}
|
|
1742
|
+
function invokeCodexCodeSync(prompt, yolo, model) {
|
|
1743
|
+
const codexBin = resolveCommand("codex");
|
|
1744
|
+
const args = ["exec"];
|
|
1745
|
+
if (yolo) args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
1746
|
+
if (model) args.push("--model", model);
|
|
1747
|
+
args.push(prompt);
|
|
1748
|
+
const result = spawnSync5(codexBin, args, { encoding: "utf8", stdio: "inherit" });
|
|
1749
|
+
handleSpawnError(result.error, "codex");
|
|
1750
|
+
handleExitCode(result.status, "Codex");
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/commands/getReady.ts
|
|
1754
|
+
function writeOverflowHint(output, issues, limit) {
|
|
1755
|
+
if (issues.length === limit) {
|
|
1756
|
+
output.write(`(Showing first ${limit} matching issues \u2014 there may be more. Use --limit to fetch more.)
|
|
968
1757
|
`);
|
|
969
|
-
process.exit(result.status ?? 1);
|
|
970
1758
|
}
|
|
971
1759
|
}
|
|
972
|
-
|
|
973
|
-
|
|
1760
|
+
function writeIssueList(output, issues, limit) {
|
|
1761
|
+
output.write("\nAvailable issues:\n");
|
|
1762
|
+
for (let i = 0; i < issues.length; i++) {
|
|
1763
|
+
output.write(` [${i + 1}] #${issues[i].number} - ${issues[i].title}
|
|
1764
|
+
`);
|
|
1765
|
+
}
|
|
1766
|
+
writeOverflowHint(output, issues, limit);
|
|
1767
|
+
}
|
|
1768
|
+
async function promptSelection(issues, limit, output) {
|
|
1769
|
+
writeIssueList(output, issues, limit);
|
|
1770
|
+
const rl = createInterface2({ input: process.stdin, output });
|
|
1771
|
+
const answer = await new Promise(
|
|
1772
|
+
(resolve3) => rl.question(`
|
|
1773
|
+
Select issue (1-${issues.length}): `, resolve3)
|
|
1774
|
+
);
|
|
1775
|
+
rl.close();
|
|
1776
|
+
const n = Number.parseInt(answer.trim(), 10);
|
|
1777
|
+
if (Number.isNaN(n) || n < 1 || n > issues.length) {
|
|
1778
|
+
process.stderr.write(`Error: Invalid selection "${answer.trim()}". Enter a number between 1 and ${issues.length}.
|
|
1779
|
+
`);
|
|
1780
|
+
process.exit(1);
|
|
1781
|
+
}
|
|
1782
|
+
return issues[n - 1];
|
|
1783
|
+
}
|
|
1784
|
+
function validateConfig(config) {
|
|
974
1785
|
if (config.remoteType !== "gh") {
|
|
975
1786
|
process.stderr.write(
|
|
976
1787
|
"Error: implement-next is not supported in Azure DevOps mode. Work item discovery is not available in azdo-cli. See docs/azdo-gap.md for details.\n"
|
|
@@ -989,24 +1800,58 @@ var implementNextCommand = new Command3("implement-next").description("Find the
|
|
|
989
1800
|
);
|
|
990
1801
|
process.exit(1);
|
|
991
1802
|
}
|
|
1803
|
+
}
|
|
1804
|
+
async function resolveIssue(issues, options, limit) {
|
|
1805
|
+
const selectionOutput = options.json ? process.stderr : process.stdout;
|
|
1806
|
+
if (issues.length === 0) {
|
|
1807
|
+
process.stdout.write("No issues found matching the configured filter.\n");
|
|
1808
|
+
process.exit(0);
|
|
1809
|
+
}
|
|
1810
|
+
if (issues.length > 1 && options.queryOnly) {
|
|
1811
|
+
writeIssueList(selectionOutput, issues, limit);
|
|
1812
|
+
process.exit(0);
|
|
1813
|
+
}
|
|
992
1814
|
let issue;
|
|
1815
|
+
if (issues.length === 1) {
|
|
1816
|
+
issue = issues[0];
|
|
1817
|
+
selectionOutput.write(`Issue: #${issue.number}
|
|
1818
|
+
Title: ${issue.title}
|
|
1819
|
+
`);
|
|
1820
|
+
} else if (options.takeFirst) {
|
|
1821
|
+
issue = issues[0];
|
|
1822
|
+
selectionOutput.write(`Selecting issue #${issue.number}: ${issue.title}
|
|
1823
|
+
`);
|
|
1824
|
+
} else {
|
|
1825
|
+
issue = await promptSelection(issues, limit, selectionOutput);
|
|
1826
|
+
selectionOutput.write(`
|
|
1827
|
+
Issue: #${issue.number}
|
|
1828
|
+
Title: ${issue.title}
|
|
1829
|
+
`);
|
|
1830
|
+
}
|
|
1831
|
+
return issue;
|
|
1832
|
+
}
|
|
1833
|
+
var implementNextCommand = new Command3("implement-next").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke the AI code assistant (Claude or Codex)").option("--json", "Output issue details as JSON").option("--no-claude", "Skip all AI invocation (Claude or Codex) after claiming the issue").option("--codex", "Use Codex CLI instead of Claude Code").option("--query-only", "Print issue content and exit without claiming or invoking any AI tools").option("--yolo", "Launch with --dangerously-skip-permissions (Claude) or --dangerously-bypass-approvals-and-sandbox (Codex)").option("--verbose", "Show step-by-step progress summary and final result").option("--opus", "Use claude-opus-4-6").option("--sonnet", "Use claude-sonnet-4-6").option("--haiku", "Use claude-haiku-4-5-20251001").option("--take-first", "When multiple issues match, pick the first without prompting").option("--limit <n>", "Max issues to fetch and display (default: 10)", "10").action(async (options) => {
|
|
1834
|
+
const config = readConfig();
|
|
1835
|
+
validateConfig(config);
|
|
1836
|
+
const limit = Number.parseInt(options.limit, 10);
|
|
1837
|
+
if (Number.isNaN(limit) || limit <= 0) {
|
|
1838
|
+
process.stderr.write(`Error: --limit must be a positive integer (got "${options.limit}").
|
|
1839
|
+
`);
|
|
1840
|
+
process.exit(1);
|
|
1841
|
+
}
|
|
1842
|
+
let issues;
|
|
993
1843
|
try {
|
|
994
|
-
|
|
1844
|
+
issues = listIssues(config.issueDiscoveryTechnique, config.issueDiscoveryValue, limit);
|
|
995
1845
|
} catch (err) {
|
|
996
1846
|
process.stderr.write(`Error: ${err.message}
|
|
997
1847
|
`);
|
|
998
1848
|
process.exit(1);
|
|
999
1849
|
}
|
|
1000
|
-
|
|
1001
|
-
process.stdout.write("No issues found matching the configured filter.\n");
|
|
1002
|
-
process.exit(0);
|
|
1003
|
-
}
|
|
1850
|
+
const issue = await resolveIssue(issues, options, limit);
|
|
1004
1851
|
if (options.json) {
|
|
1005
1852
|
process.stdout.write(JSON.stringify({ number: issue.number, title: issue.title, body: issue.body, url: issue.url }, null, 2) + "\n");
|
|
1006
1853
|
} else {
|
|
1007
|
-
process.stdout.write(`
|
|
1008
|
-
Title: ${issue.title}
|
|
1009
|
-
URL: ${issue.url}
|
|
1854
|
+
process.stdout.write(`URL: ${issue.url}
|
|
1010
1855
|
|
|
1011
1856
|
${issue.body}
|
|
1012
1857
|
`);
|
|
@@ -1022,16 +1867,209 @@ ${issue.body}
|
|
|
1022
1867
|
process.exit(1);
|
|
1023
1868
|
}
|
|
1024
1869
|
if (options.claude !== false) {
|
|
1025
|
-
|
|
1870
|
+
const systemPrompt = config.claudeSystemPrompt ?? DEFAULT_CLAUDE_SYSTEM_PROMPT;
|
|
1871
|
+
const prompt = `Resolving issue #${issue.number}:
|
|
1872
|
+
|
|
1873
|
+
${systemPrompt}
|
|
1874
|
+
|
|
1875
|
+
${issue.body}`;
|
|
1876
|
+
if (options.codex) {
|
|
1877
|
+
await invokeCodexCode(prompt, { yolo: options.yolo, verbose: options.verbose });
|
|
1878
|
+
} else {
|
|
1879
|
+
const model = resolveModelOption(options);
|
|
1880
|
+
await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: options.verbose, model });
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
// src/commands/execute.ts
|
|
1886
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1887
|
+
import { Command as Command4 } from "commander";
|
|
1888
|
+
function readStdin() {
|
|
1889
|
+
return new Promise((resolve3, reject) => {
|
|
1890
|
+
const chunks = [];
|
|
1891
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
1892
|
+
process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8").trim()));
|
|
1893
|
+
process.stdin.on("error", reject);
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
async function resolvePrompt(options) {
|
|
1897
|
+
const hasCli = options.prompt !== void 0;
|
|
1898
|
+
const hasFile = options.filePrompt !== void 0;
|
|
1899
|
+
const hasStdin = !process.stdin.isTTY;
|
|
1900
|
+
if (hasCli && (hasFile || hasStdin)) {
|
|
1901
|
+
process.stderr.write("Error: --prompt is mutually exclusive with --file-prompt and stdin.\n");
|
|
1902
|
+
process.exit(1);
|
|
1903
|
+
}
|
|
1904
|
+
if (hasCli) {
|
|
1905
|
+
return options.prompt;
|
|
1906
|
+
}
|
|
1907
|
+
if (hasFile) {
|
|
1908
|
+
const path = options.filePrompt;
|
|
1909
|
+
try {
|
|
1910
|
+
return readFileSync3(path, "utf8");
|
|
1911
|
+
} catch {
|
|
1912
|
+
process.stderr.write(`Error: Cannot read file: ${path}
|
|
1913
|
+
`);
|
|
1914
|
+
process.exit(1);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (hasStdin) {
|
|
1918
|
+
const content = await readStdin();
|
|
1919
|
+
if (content.length === 0) {
|
|
1920
|
+
process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
|
|
1921
|
+
process.exit(1);
|
|
1922
|
+
}
|
|
1923
|
+
return content;
|
|
1924
|
+
}
|
|
1925
|
+
process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
|
|
1926
|
+
process.exit(1);
|
|
1927
|
+
}
|
|
1928
|
+
var executeCommand = new Command4("execute").description("Delegate work to an AI executor").requiredOption("--with <executor>", "Executor to use: claude or codex").option("--prompt <string>", "Prompt to send to the executor").option("--file-prompt <path>", "Path to a file whose content is used as the prompt").option("--silent", "Suppress step-by-step output; show only the final summary").option("--model <string>", "Model identifier to pass to the executor").action(async (options) => {
|
|
1929
|
+
const executor = options.with.toLowerCase();
|
|
1930
|
+
if (executor !== "claude" && executor !== "codex") {
|
|
1931
|
+
process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
|
|
1932
|
+
`);
|
|
1933
|
+
process.exit(1);
|
|
1934
|
+
}
|
|
1935
|
+
const prompt = await resolvePrompt(options);
|
|
1936
|
+
if (executor === "codex") {
|
|
1937
|
+
invokeCodexCode(prompt, { yolo: true, model: options.model });
|
|
1938
|
+
} else {
|
|
1939
|
+
await invokeClaudeCode(prompt, { yolo: true, verbose: !options.silent, model: options.model });
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
// src/commands/executePrompt.ts
|
|
1944
|
+
import { Command as Command5 } from "commander";
|
|
1945
|
+
var PUSH_INSTRUCTION = "Once all changes are complete, stage every modified file, create a single commit with a clear and descriptive commit message that summarises what was fixed, and push the branch to the remote.";
|
|
1946
|
+
function withPush(prompt, push) {
|
|
1947
|
+
return push ? `${prompt}
|
|
1948
|
+
|
|
1949
|
+
${PUSH_INSTRUCTION}` : prompt;
|
|
1950
|
+
}
|
|
1951
|
+
function formatPrInfoContext(pr) {
|
|
1952
|
+
return JSON.stringify(pr, null, 2);
|
|
1953
|
+
}
|
|
1954
|
+
function addAiOptions(cmd) {
|
|
1955
|
+
return cmd.option("--codex", "Use Codex CLI instead of Claude Code").option("--verbose", "Show step-by-step progress (Claude only; ignored for Codex)").option("--push", "Append instruction to commit and push changes after the AI finishes").option("--opus", "Use claude-opus-4-6 (Claude only)").option("--sonnet", "Use claude-sonnet-4-6 (Claude only)").option("--haiku", "Use claude-haiku-4-5-20251001 (Claude only)");
|
|
1956
|
+
}
|
|
1957
|
+
var executeSonarCmd = addAiOptions(
|
|
1958
|
+
new Command5("sonar").description(
|
|
1959
|
+
"Check the current branch for a SonarCloud analysis and invoke the AI with the Sonar prompt and analysis URL"
|
|
1960
|
+
)
|
|
1961
|
+
).action(async (options) => {
|
|
1962
|
+
let branch;
|
|
1963
|
+
try {
|
|
1964
|
+
branch = getCurrentBranch();
|
|
1965
|
+
} catch (err) {
|
|
1966
|
+
process.stderr.write(`Error: ${err.message}
|
|
1967
|
+
`);
|
|
1968
|
+
process.exit(1);
|
|
1969
|
+
}
|
|
1970
|
+
let pr;
|
|
1971
|
+
try {
|
|
1972
|
+
pr = await getPrInfo2(branch);
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
process.stderr.write(`Error: ${err.message}
|
|
1975
|
+
`);
|
|
1976
|
+
process.exit(1);
|
|
1977
|
+
}
|
|
1978
|
+
if (pr === null) {
|
|
1979
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
1980
|
+
`);
|
|
1981
|
+
process.exit(1);
|
|
1982
|
+
}
|
|
1983
|
+
if (!pr.sonarcloudUrl) {
|
|
1984
|
+
process.stderr.write(
|
|
1985
|
+
`Error: No SonarCloud analysis found for PR #${pr.number}. Ensure a SonarCloud check is configured on this repository.
|
|
1986
|
+
`
|
|
1987
|
+
);
|
|
1988
|
+
process.exit(1);
|
|
1989
|
+
}
|
|
1990
|
+
const config = readConfig();
|
|
1991
|
+
const sonarPromptText = config.prompts?.sonar ?? DEFAULT_SONAR_PROMPT;
|
|
1992
|
+
const fullPrompt = withPush(
|
|
1993
|
+
`${sonarPromptText}
|
|
1994
|
+
|
|
1995
|
+
SonarCloud analysis URL: ${pr.sonarcloudUrl}
|
|
1996
|
+
|
|
1997
|
+
Current PR context from automata git get-pr-info --json:
|
|
1998
|
+
${formatPrInfoContext(pr)}`,
|
|
1999
|
+
options.push
|
|
2000
|
+
);
|
|
2001
|
+
if (options.codex) {
|
|
2002
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
2003
|
+
} else {
|
|
2004
|
+
const model = resolveModelOption(options);
|
|
2005
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
function formatComments(comments) {
|
|
2009
|
+
return comments.map((c) => {
|
|
2010
|
+
const loc = c.line === null ? `${c.path}:(file)` : `${c.path}:${String(c.line)}`;
|
|
2011
|
+
return `[${c.author}] on ${loc}
|
|
2012
|
+
${c.body}`;
|
|
2013
|
+
}).join("\n\n");
|
|
2014
|
+
}
|
|
2015
|
+
var executeFixCommentsCmd = addAiOptions(
|
|
2016
|
+
new Command5("fix-comments").description(
|
|
2017
|
+
"Fetch open review comments on the current PR and invoke the AI with the Fix-Comments prompt"
|
|
2018
|
+
)
|
|
2019
|
+
).action(async (options) => {
|
|
2020
|
+
const result = resolveCurrentBranchComments();
|
|
2021
|
+
if (!result.ok) {
|
|
2022
|
+
if (result.kind === "error") {
|
|
2023
|
+
process.stderr.write(`Error: ${result.message}
|
|
2024
|
+
`);
|
|
2025
|
+
process.exit(1);
|
|
2026
|
+
}
|
|
2027
|
+
if (result.kind === "unsupported") {
|
|
2028
|
+
process.stderr.write(
|
|
2029
|
+
`Error: fix-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
|
|
2030
|
+
`
|
|
2031
|
+
);
|
|
2032
|
+
process.exit(1);
|
|
2033
|
+
}
|
|
2034
|
+
process.stderr.write(`Error: No pull request found for branch: ${result.branch}
|
|
2035
|
+
`);
|
|
2036
|
+
process.exit(1);
|
|
2037
|
+
}
|
|
2038
|
+
const { comments } = result;
|
|
2039
|
+
if (comments.length === 0) {
|
|
2040
|
+
process.stderr.write(`Error: No open review comments found on the pull request.
|
|
2041
|
+
`);
|
|
2042
|
+
process.exit(1);
|
|
2043
|
+
}
|
|
2044
|
+
process.stdout.write(`Found ${String(comments.length)} open review comment${comments.length === 1 ? "" : "s"} on PR. Invoking AI\u2026
|
|
2045
|
+
`);
|
|
2046
|
+
const config = readConfig();
|
|
2047
|
+
const promptText = config.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT;
|
|
2048
|
+
const fullPrompt = withPush(
|
|
2049
|
+
`${promptText}
|
|
2050
|
+
|
|
2051
|
+
Open review comments:
|
|
2052
|
+
|
|
2053
|
+
${formatComments(comments)}`,
|
|
2054
|
+
options.push
|
|
2055
|
+
);
|
|
2056
|
+
if (options.codex) {
|
|
2057
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
2058
|
+
} else {
|
|
2059
|
+
const model = resolveModelOption(options);
|
|
2060
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
1026
2061
|
}
|
|
1027
2062
|
});
|
|
2063
|
+
var executePromptCommand = new Command5("execute-prompt").description("Execute a configured custom prompt using an AI assistant").addCommand(executeSonarCmd).addCommand(executeFixCommentsCmd);
|
|
1028
2064
|
|
|
1029
2065
|
// src/index.ts
|
|
1030
|
-
var program = new
|
|
2066
|
+
var program = new Command6();
|
|
1031
2067
|
program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
|
|
1032
2068
|
program.addCommand(configCommand);
|
|
1033
2069
|
program.addCommand(gitCommand);
|
|
1034
2070
|
program.addCommand(implementNextCommand);
|
|
2071
|
+
program.addCommand(executeCommand);
|
|
2072
|
+
program.addCommand(executePromptCommand);
|
|
1035
2073
|
program.showHelpAfterError();
|
|
1036
2074
|
program.parse();
|
|
1037
2075
|
if (process.argv.length <= 2) {
|