automata-cli 0.3.0-develop.77 → 0.3.0-develop.93
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/dist/index.js +382 -66
- 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";
|
|
@@ -23,6 +23,8 @@ import { Box, Text, useInput, useApp } from "ink";
|
|
|
23
23
|
// src/config/configStore.ts
|
|
24
24
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
25
25
|
import { join } from "path";
|
|
26
|
+
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.";
|
|
27
|
+
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
28
|
var CONFIG_DIR = ".automata";
|
|
27
29
|
var CONFIG_FILE = "config.json";
|
|
28
30
|
function configPath() {
|
|
@@ -53,22 +55,47 @@ var TECHNIQUE_OPTIONS = [
|
|
|
53
55
|
{ label: "By Assignee", value: "assignee" },
|
|
54
56
|
{ label: "By Title Contains", value: "title-contains" }
|
|
55
57
|
];
|
|
58
|
+
var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts"];
|
|
59
|
+
var PROMPTS_MENU_OPTIONS = ["Sonar", "Fix-Comments"];
|
|
56
60
|
function ConfigWizard() {
|
|
57
61
|
const existing = readConfig();
|
|
58
62
|
const initialRemoteIndex = REMOTE_OPTIONS.findIndex((o) => o.value === existing.remoteType);
|
|
59
63
|
const initialTechIndex = TECHNIQUE_OPTIONS.findIndex((o) => o.value === existing.issueDiscoveryTechnique);
|
|
60
|
-
const [screen, setScreen] = useState("
|
|
64
|
+
const [screen, setScreen] = useState("main");
|
|
65
|
+
const [mainMenuIndex, setMainMenuIndex] = useState(0);
|
|
61
66
|
const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(initialRemoteIndex >= 0 ? initialRemoteIndex : 0);
|
|
62
67
|
const [selectedTechIndex, setSelectedTechIndex] = useState(initialTechIndex >= 0 ? initialTechIndex : 0);
|
|
63
68
|
const [discoveryValue, setDiscoveryValue] = useState(existing.issueDiscoveryValue ?? "");
|
|
64
69
|
const [systemPrompt, setSystemPrompt] = useState(existing.claudeSystemPrompt ?? "");
|
|
70
|
+
const [promptsMenuIndex, setPromptsMenuIndex] = useState(0);
|
|
71
|
+
const [sonarPrompt, setSonarPrompt] = useState(existing.prompts?.sonar ?? DEFAULT_SONAR_PROMPT);
|
|
72
|
+
const [fixCommentsPrompt, setFixCommentsPrompt] = useState(
|
|
73
|
+
existing.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT
|
|
74
|
+
);
|
|
65
75
|
const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
|
|
66
76
|
const [pendingTechnique, setPendingTechnique] = useState(
|
|
67
77
|
existing.issueDiscoveryTechnique ?? "label"
|
|
68
78
|
);
|
|
69
79
|
const { exit } = useApp();
|
|
70
80
|
useInput((input, key) => {
|
|
71
|
-
if (screen === "
|
|
81
|
+
if (screen === "main") {
|
|
82
|
+
if (key.upArrow) {
|
|
83
|
+
setMainMenuIndex((i) => i > 0 ? i - 1 : MAIN_MENU_OPTIONS.length - 1);
|
|
84
|
+
} else if (key.downArrow) {
|
|
85
|
+
setMainMenuIndex((i) => i < MAIN_MENU_OPTIONS.length - 1 ? i + 1 : 0);
|
|
86
|
+
} else if (key.return) {
|
|
87
|
+
const chosen = MAIN_MENU_OPTIONS[mainMenuIndex];
|
|
88
|
+
if (chosen === "Remote / Mode") {
|
|
89
|
+
setScreen("remote");
|
|
90
|
+
} else if (chosen === "Implement-Next") {
|
|
91
|
+
setScreen("technique");
|
|
92
|
+
} else {
|
|
93
|
+
setScreen("prompts-menu");
|
|
94
|
+
}
|
|
95
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
96
|
+
exit();
|
|
97
|
+
}
|
|
98
|
+
} else if (screen === "remote") {
|
|
72
99
|
if (key.upArrow) {
|
|
73
100
|
setSelectedRemoteIndex((i) => i > 0 ? i - 1 : REMOTE_OPTIONS.length - 1);
|
|
74
101
|
} else if (key.downArrow) {
|
|
@@ -82,7 +109,9 @@ function ConfigWizard() {
|
|
|
82
109
|
writeConfig({ ...existing, remoteType: chosen.value });
|
|
83
110
|
exit();
|
|
84
111
|
}
|
|
85
|
-
} else if (key.escape
|
|
112
|
+
} else if (key.escape) {
|
|
113
|
+
setScreen("main");
|
|
114
|
+
} else if (key.ctrl && input === "c") {
|
|
86
115
|
exit();
|
|
87
116
|
}
|
|
88
117
|
} else if (screen === "technique") {
|
|
@@ -94,7 +123,9 @@ function ConfigWizard() {
|
|
|
94
123
|
const chosen = TECHNIQUE_OPTIONS[selectedTechIndex];
|
|
95
124
|
setPendingTechnique(chosen.value);
|
|
96
125
|
setScreen("value");
|
|
97
|
-
} else if (key.escape
|
|
126
|
+
} else if (key.escape) {
|
|
127
|
+
setScreen("main");
|
|
128
|
+
} else if (key.ctrl && input === "c") {
|
|
98
129
|
exit();
|
|
99
130
|
}
|
|
100
131
|
} else if (screen === "value") {
|
|
@@ -102,7 +133,9 @@ function ConfigWizard() {
|
|
|
102
133
|
setScreen("system-prompt");
|
|
103
134
|
} else if (key.backspace || key.delete) {
|
|
104
135
|
setDiscoveryValue((v) => v.slice(0, -1));
|
|
105
|
-
} else if (key.escape
|
|
136
|
+
} else if (key.escape) {
|
|
137
|
+
setScreen("main");
|
|
138
|
+
} else if (key.ctrl && input === "c") {
|
|
106
139
|
exit();
|
|
107
140
|
} else if (input && !key.ctrl && !key.meta) {
|
|
108
141
|
setDiscoveryValue((v) => v + input);
|
|
@@ -119,29 +152,94 @@ function ConfigWizard() {
|
|
|
119
152
|
exit();
|
|
120
153
|
} else if (key.backspace || key.delete) {
|
|
121
154
|
setSystemPrompt((v) => v.slice(0, -1));
|
|
122
|
-
} else if (key.escape
|
|
155
|
+
} else if (key.escape) {
|
|
156
|
+
setScreen("main");
|
|
157
|
+
} else if (key.ctrl && input === "c") {
|
|
123
158
|
exit();
|
|
124
159
|
} else if (input && !key.ctrl && !key.meta) {
|
|
125
160
|
setSystemPrompt((v) => v + input);
|
|
126
161
|
}
|
|
162
|
+
} else if (screen === "prompts-menu") {
|
|
163
|
+
if (key.upArrow) {
|
|
164
|
+
setPromptsMenuIndex((i) => i > 0 ? i - 1 : PROMPTS_MENU_OPTIONS.length - 1);
|
|
165
|
+
} else if (key.downArrow) {
|
|
166
|
+
setPromptsMenuIndex((i) => i < PROMPTS_MENU_OPTIONS.length - 1 ? i + 1 : 0);
|
|
167
|
+
} else if (key.return) {
|
|
168
|
+
const chosen = PROMPTS_MENU_OPTIONS[promptsMenuIndex];
|
|
169
|
+
if (chosen === "Sonar") {
|
|
170
|
+
setScreen("sonar-prompt");
|
|
171
|
+
} else {
|
|
172
|
+
setScreen("fix-comments-prompt");
|
|
173
|
+
}
|
|
174
|
+
} else if (key.escape) {
|
|
175
|
+
setScreen("main");
|
|
176
|
+
} else if (key.ctrl && input === "c") {
|
|
177
|
+
exit();
|
|
178
|
+
}
|
|
179
|
+
} else if (screen === "sonar-prompt") {
|
|
180
|
+
if (key.return) {
|
|
181
|
+
const current = readConfig();
|
|
182
|
+
writeConfig({
|
|
183
|
+
...current,
|
|
184
|
+
prompts: { ...current.prompts, sonar: sonarPrompt || void 0 }
|
|
185
|
+
});
|
|
186
|
+
setScreen("prompts-menu");
|
|
187
|
+
} else if (key.backspace || key.delete) {
|
|
188
|
+
setSonarPrompt((v) => v.slice(0, -1));
|
|
189
|
+
} else if (key.escape) {
|
|
190
|
+
setScreen("prompts-menu");
|
|
191
|
+
} else if (key.ctrl && input === "c") {
|
|
192
|
+
exit();
|
|
193
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
194
|
+
setSonarPrompt((v) => v + input);
|
|
195
|
+
}
|
|
196
|
+
} else if (screen === "fix-comments-prompt") {
|
|
197
|
+
if (key.return) {
|
|
198
|
+
const current = readConfig();
|
|
199
|
+
writeConfig({
|
|
200
|
+
...current,
|
|
201
|
+
prompts: { ...current.prompts, fixComments: fixCommentsPrompt || void 0 }
|
|
202
|
+
});
|
|
203
|
+
setScreen("prompts-menu");
|
|
204
|
+
} else if (key.backspace || key.delete) {
|
|
205
|
+
setFixCommentsPrompt((v) => v.slice(0, -1));
|
|
206
|
+
} else if (key.escape) {
|
|
207
|
+
setScreen("prompts-menu");
|
|
208
|
+
} else if (key.ctrl && input === "c") {
|
|
209
|
+
exit();
|
|
210
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
211
|
+
setFixCommentsPrompt((v) => v + input);
|
|
212
|
+
}
|
|
127
213
|
}
|
|
128
214
|
});
|
|
129
|
-
if (screen === "
|
|
215
|
+
if (screen === "main") {
|
|
130
216
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
131
217
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Automata" }),
|
|
132
218
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
219
|
+
MAIN_MENU_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === mainMenuIndex ? "cyan" : void 0, children: [
|
|
220
|
+
index === mainMenuIndex ? "\u276F " : " ",
|
|
221
|
+
option
|
|
222
|
+
] }) }, option)),
|
|
223
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
224
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to cancel" })
|
|
225
|
+
] });
|
|
226
|
+
}
|
|
227
|
+
if (screen === "remote") {
|
|
228
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
229
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Remote / Mode" }),
|
|
230
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
133
231
|
/* @__PURE__ */ jsx(Text, { children: "Remote environment type:" }),
|
|
134
232
|
REMOTE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedRemoteIndex ? "cyan" : void 0, children: [
|
|
135
233
|
index === selectedRemoteIndex ? "\u276F " : " ",
|
|
136
234
|
option.label
|
|
137
235
|
] }) }, option.value)),
|
|
138
236
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
139
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
|
|
237
|
+
/* @__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
238
|
] });
|
|
141
239
|
}
|
|
142
240
|
if (screen === "technique") {
|
|
143
241
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
144
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
242
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Issue Discovery Technique" }),
|
|
145
243
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
146
244
|
/* @__PURE__ */ jsx(Text, { children: "How to find the next issue to work on:" }),
|
|
147
245
|
TECHNIQUE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedTechIndex ? "cyan" : void 0, children: [
|
|
@@ -149,13 +247,13 @@ function ConfigWizard() {
|
|
|
149
247
|
option.label
|
|
150
248
|
] }) }, option.value)),
|
|
151
249
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
152
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
|
|
250
|
+
/* @__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
251
|
] });
|
|
154
252
|
}
|
|
155
253
|
if (screen === "value") {
|
|
156
254
|
const techLabel = TECHNIQUE_OPTIONS.find((t) => t.value === pendingTechnique)?.label ?? pendingTechnique;
|
|
157
255
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
158
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
256
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Issue Discovery Value" }),
|
|
159
257
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
160
258
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
161
259
|
techLabel,
|
|
@@ -167,22 +265,66 @@ function ConfigWizard() {
|
|
|
167
265
|
] })
|
|
168
266
|
] }),
|
|
169
267
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
170
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type value \xB7 Enter to
|
|
268
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type value \xB7 Enter to continue \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
269
|
+
] });
|
|
270
|
+
}
|
|
271
|
+
if (screen === "system-prompt") {
|
|
272
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
273
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Implement-Next \u2014 Claude System Prompt" }),
|
|
274
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
275
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
276
|
+
"System prompt (optional):",
|
|
277
|
+
" ",
|
|
278
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
279
|
+
systemPrompt,
|
|
280
|
+
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
281
|
+
] })
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
284
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save and exit \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
285
|
+
] });
|
|
286
|
+
}
|
|
287
|
+
if (screen === "prompts-menu") {
|
|
288
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
289
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts" }),
|
|
290
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
291
|
+
PROMPTS_MENU_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === promptsMenuIndex ? "cyan" : void 0, children: [
|
|
292
|
+
index === promptsMenuIndex ? "\u276F " : " ",
|
|
293
|
+
option
|
|
294
|
+
] }) }, option)),
|
|
295
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
296
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to edit \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
297
|
+
] });
|
|
298
|
+
}
|
|
299
|
+
if (screen === "sonar-prompt") {
|
|
300
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
301
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts \u2014 Sonar" }),
|
|
302
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
303
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
304
|
+
"Sonar prompt:",
|
|
305
|
+
" ",
|
|
306
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
307
|
+
sonarPrompt,
|
|
308
|
+
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
309
|
+
] })
|
|
310
|
+
] }),
|
|
311
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
312
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
171
313
|
] });
|
|
172
314
|
}
|
|
173
315
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
174
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "
|
|
316
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts \u2014 Fix-Comments" }),
|
|
175
317
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
176
318
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
177
|
-
"
|
|
319
|
+
"Fix-Comments prompt:",
|
|
178
320
|
" ",
|
|
179
321
|
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
180
|
-
|
|
322
|
+
fixCommentsPrompt,
|
|
181
323
|
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
182
324
|
] })
|
|
183
325
|
] }),
|
|
184
326
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
185
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save
|
|
327
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save \xB7 Esc to go back \xB7 Ctrl+C to cancel" })
|
|
186
328
|
] });
|
|
187
329
|
}
|
|
188
330
|
|
|
@@ -335,7 +477,31 @@ function fetchCheckRunOutputs(ownerRepo, sha) {
|
|
|
335
477
|
}
|
|
336
478
|
return map;
|
|
337
479
|
}
|
|
338
|
-
function
|
|
480
|
+
function extractSonarProjectKey(url) {
|
|
481
|
+
try {
|
|
482
|
+
const parsed = new URL(url);
|
|
483
|
+
const id = parsed.searchParams.get("id");
|
|
484
|
+
return id ?? null;
|
|
485
|
+
} catch {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function fetchSonarNewIssues(projectKey, prNumber) {
|
|
490
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
|
|
491
|
+
const controller = new AbortController();
|
|
492
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
493
|
+
try {
|
|
494
|
+
const response = await fetch(apiUrl, { signal: controller.signal });
|
|
495
|
+
if (!response.ok) return null;
|
|
496
|
+
const data = await response.json();
|
|
497
|
+
return data.paging?.total ?? null;
|
|
498
|
+
} catch {
|
|
499
|
+
return null;
|
|
500
|
+
} finally {
|
|
501
|
+
clearTimeout(timeoutId);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function getPrInfoGh(branch) {
|
|
339
505
|
const { stdout, stderr, status } = run2("gh", [
|
|
340
506
|
"pr",
|
|
341
507
|
"view",
|
|
@@ -365,9 +531,35 @@ function getPrInfoGh(branch) {
|
|
|
365
531
|
detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
|
|
366
532
|
};
|
|
367
533
|
});
|
|
368
|
-
|
|
534
|
+
const sonarCheck = checks.find((c) => {
|
|
535
|
+
try {
|
|
536
|
+
const hostname = new URL(c.detailsUrl).hostname;
|
|
537
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
538
|
+
} catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
let sonarcloudUrl;
|
|
543
|
+
let sonarNewIssues;
|
|
544
|
+
if (sonarCheck) {
|
|
545
|
+
sonarcloudUrl = sonarCheck.detailsUrl;
|
|
546
|
+
const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
|
|
547
|
+
if (projectKey) {
|
|
548
|
+
sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
|
|
549
|
+
} else {
|
|
550
|
+
sonarNewIssues = null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
number: raw.number,
|
|
555
|
+
title: raw.title,
|
|
556
|
+
state: raw.state,
|
|
557
|
+
url: raw.url,
|
|
558
|
+
checks,
|
|
559
|
+
...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues }
|
|
560
|
+
};
|
|
369
561
|
}
|
|
370
|
-
function getPrInfo2(branch) {
|
|
562
|
+
async function getPrInfo2(branch) {
|
|
371
563
|
const config = readConfig();
|
|
372
564
|
if (config.remoteType === "azdo") {
|
|
373
565
|
return getPrInfo();
|
|
@@ -471,6 +663,23 @@ function getPrComments(branch) {
|
|
|
471
663
|
}
|
|
472
664
|
return getPrCommentsGh(branch);
|
|
473
665
|
}
|
|
666
|
+
function resolveCurrentBranchComments() {
|
|
667
|
+
let branch;
|
|
668
|
+
try {
|
|
669
|
+
branch = getCurrentBranch();
|
|
670
|
+
} catch (err) {
|
|
671
|
+
return { ok: false, kind: "error", message: err.message };
|
|
672
|
+
}
|
|
673
|
+
let raw;
|
|
674
|
+
try {
|
|
675
|
+
raw = getPrComments(branch);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
return { ok: false, kind: "error", message: err.message };
|
|
678
|
+
}
|
|
679
|
+
if (raw === "unsupported") return { ok: false, kind: "unsupported" };
|
|
680
|
+
if (raw === null) return { ok: false, kind: "no-pr", branch };
|
|
681
|
+
return { ok: true, branch, comments: raw };
|
|
682
|
+
}
|
|
474
683
|
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
475
684
|
function getLatestTagOnMaster() {
|
|
476
685
|
const { stdout, status } = run2("git", [
|
|
@@ -553,33 +762,34 @@ function formatChecks(checks) {
|
|
|
553
762
|
const sym = checkSymbol(check);
|
|
554
763
|
const pending = check.status !== "COMPLETED" ? " (pending)" : "";
|
|
555
764
|
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
765
|
}
|
|
564
766
|
return lines.join("\n") + "\n";
|
|
565
767
|
}
|
|
566
768
|
function sleep(ms) {
|
|
567
769
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
568
770
|
}
|
|
771
|
+
function isSonarCheck(check) {
|
|
772
|
+
try {
|
|
773
|
+
const hostname = new URL(check.detailsUrl).hostname;
|
|
774
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
775
|
+
} catch {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
569
779
|
function formatFailedChecks(failed) {
|
|
570
|
-
const lines = [];
|
|
780
|
+
const lines = ["FailedChecks:"];
|
|
571
781
|
for (const check of failed) {
|
|
572
782
|
lines.push(` \u2717 ${check.name}`);
|
|
573
783
|
const desc = check.description.trim();
|
|
574
784
|
const url = check.detailsUrl.trim();
|
|
575
785
|
if (desc) lines.push(` Details: ${desc}`);
|
|
576
|
-
if (url) lines.push(` URL: ${url}`);
|
|
786
|
+
if (url && (isSonarCheck(check) || !desc)) lines.push(` URL: ${url}`);
|
|
577
787
|
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
578
788
|
}
|
|
579
789
|
return lines.join("\n") + "\n";
|
|
580
790
|
}
|
|
581
791
|
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
|
|
792
|
+
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
793
|
"after",
|
|
584
794
|
`
|
|
585
795
|
Check status symbols:
|
|
@@ -588,10 +798,11 @@ Check status symbols:
|
|
|
588
798
|
\u25CF Pending (status: QUEUED or IN_PROGRESS)
|
|
589
799
|
\u25CB Skipped (conclusion: SKIPPED or NEUTRAL)
|
|
590
800
|
|
|
591
|
-
|
|
801
|
+
When checks fail, details are printed in a trailing FailedChecks section.
|
|
592
802
|
See docs/git.md for full output reference.`
|
|
593
803
|
).action(async (options) => {
|
|
594
804
|
let branch;
|
|
805
|
+
let pr = null;
|
|
595
806
|
try {
|
|
596
807
|
branch = getCurrentBranch();
|
|
597
808
|
} catch (err) {
|
|
@@ -600,54 +811,31 @@ See docs/git.md for full output reference.`
|
|
|
600
811
|
process.exit(1);
|
|
601
812
|
}
|
|
602
813
|
if (options.waitFinishChecks) {
|
|
603
|
-
let pr2;
|
|
604
814
|
while (true) {
|
|
605
815
|
try {
|
|
606
|
-
|
|
816
|
+
pr = await getPrInfo2(branch);
|
|
607
817
|
} catch (err) {
|
|
608
818
|
process.stderr.write(`Error: ${err.message}
|
|
609
819
|
`);
|
|
610
820
|
process.exit(1);
|
|
611
821
|
}
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
`);
|
|
615
|
-
process.exit(1);
|
|
822
|
+
if (pr === null) {
|
|
823
|
+
break;
|
|
616
824
|
}
|
|
617
|
-
const running =
|
|
825
|
+
const running = pr.checks.filter((c) => c.status !== "COMPLETED");
|
|
618
826
|
if (running.length === 0) break;
|
|
619
827
|
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
620
828
|
`);
|
|
621
829
|
await sleep(POLL_INTERVAL_MS);
|
|
622
830
|
}
|
|
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:
|
|
831
|
+
} else {
|
|
832
|
+
try {
|
|
833
|
+
pr = await getPrInfo2(branch);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
process.stderr.write(`Error: ${err.message}
|
|
637
836
|
`);
|
|
638
|
-
process.stdout.write(formatFailedChecks(failed));
|
|
639
|
-
}
|
|
640
837
|
process.exit(1);
|
|
641
838
|
}
|
|
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
839
|
}
|
|
652
840
|
if (pr === null) {
|
|
653
841
|
process.stdout.write(`No pull request found for branch: ${branch}
|
|
@@ -657,13 +845,24 @@ See docs/git.md for full output reference.`
|
|
|
657
845
|
if (options.json) {
|
|
658
846
|
process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
|
|
659
847
|
} else {
|
|
848
|
+
const failed = pr.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
660
849
|
process.stdout.write(`PR: #${pr.number}
|
|
661
850
|
Title: ${pr.title}
|
|
662
851
|
State: ${pr.state}
|
|
663
852
|
URL: ${pr.url}
|
|
664
853
|
`);
|
|
854
|
+
if (pr.sonarcloudUrl !== void 0) {
|
|
855
|
+
process.stdout.write(`Sonar: ${pr.sonarcloudUrl}
|
|
856
|
+
`);
|
|
857
|
+
const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
|
|
858
|
+
process.stdout.write(`Sonar New Issues: ${issueStr}
|
|
859
|
+
`);
|
|
860
|
+
}
|
|
665
861
|
process.stdout.write(formatCheckSummary(pr.checks));
|
|
666
862
|
process.stdout.write(formatChecks(pr.checks));
|
|
863
|
+
if (failed.length > 0) {
|
|
864
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
865
|
+
}
|
|
667
866
|
}
|
|
668
867
|
});
|
|
669
868
|
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
@@ -724,7 +923,7 @@ ${safeBody}`);
|
|
|
724
923
|
}
|
|
725
924
|
process.stdout.write(lines.join("\n\n") + "\n");
|
|
726
925
|
});
|
|
727
|
-
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
926
|
+
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(async () => {
|
|
728
927
|
let branch;
|
|
729
928
|
try {
|
|
730
929
|
branch = getCurrentBranch();
|
|
@@ -745,7 +944,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
745
944
|
}
|
|
746
945
|
let pr;
|
|
747
946
|
try {
|
|
748
|
-
pr = getPrInfo2(branch);
|
|
947
|
+
pr = await getPrInfo2(branch);
|
|
749
948
|
} catch (err) {
|
|
750
949
|
process.stderr.write(`Error: ${err.message}
|
|
751
950
|
`);
|
|
@@ -1194,13 +1393,130 @@ var testCodexCmd = new Command4("codex").description("Test Codex CLI invocation
|
|
|
1194
1393
|
});
|
|
1195
1394
|
var testCommand = new Command4("test").description("Test commands for verifying automata integrations").addCommand(testClaudeCmd).addCommand(testCodexCmd);
|
|
1196
1395
|
|
|
1396
|
+
// src/commands/executePrompt.ts
|
|
1397
|
+
import { Command as Command5 } from "commander";
|
|
1398
|
+
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.";
|
|
1399
|
+
function withPush(prompt, push) {
|
|
1400
|
+
return push ? `${prompt}
|
|
1401
|
+
|
|
1402
|
+
${PUSH_INSTRUCTION}` : prompt;
|
|
1403
|
+
}
|
|
1404
|
+
function addAiOptions(cmd) {
|
|
1405
|
+
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)");
|
|
1406
|
+
}
|
|
1407
|
+
var executeSonarCmd = addAiOptions(
|
|
1408
|
+
new Command5("sonar").description(
|
|
1409
|
+
"Check the current branch for a SonarCloud analysis and invoke the AI with the Sonar prompt and analysis URL"
|
|
1410
|
+
)
|
|
1411
|
+
).action(async (options) => {
|
|
1412
|
+
let branch;
|
|
1413
|
+
try {
|
|
1414
|
+
branch = getCurrentBranch();
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
process.stderr.write(`Error: ${err.message}
|
|
1417
|
+
`);
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
let pr;
|
|
1421
|
+
try {
|
|
1422
|
+
pr = await getPrInfo2(branch);
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
process.stderr.write(`Error: ${err.message}
|
|
1425
|
+
`);
|
|
1426
|
+
process.exit(1);
|
|
1427
|
+
}
|
|
1428
|
+
if (pr === null) {
|
|
1429
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
1430
|
+
`);
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
if (!pr.sonarcloudUrl) {
|
|
1434
|
+
process.stderr.write(
|
|
1435
|
+
`Error: No SonarCloud analysis found for PR #${pr.number}. Ensure a SonarCloud check is configured on this repository.
|
|
1436
|
+
`
|
|
1437
|
+
);
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
const config = readConfig();
|
|
1441
|
+
const sonarPromptText = config.prompts?.sonar ?? DEFAULT_SONAR_PROMPT;
|
|
1442
|
+
const fullPrompt = withPush(
|
|
1443
|
+
`${sonarPromptText}
|
|
1444
|
+
|
|
1445
|
+
SonarCloud analysis URL: ${pr.sonarcloudUrl}`,
|
|
1446
|
+
options.push
|
|
1447
|
+
);
|
|
1448
|
+
if (options.codex) {
|
|
1449
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
1450
|
+
} else {
|
|
1451
|
+
const model = resolveModelOption(options);
|
|
1452
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
function formatComments(comments) {
|
|
1456
|
+
return comments.map((c) => {
|
|
1457
|
+
const loc = c.line === null ? `${c.path}:(file)` : `${c.path}:${String(c.line)}`;
|
|
1458
|
+
return `[${c.author}] on ${loc}
|
|
1459
|
+
${c.body}`;
|
|
1460
|
+
}).join("\n\n");
|
|
1461
|
+
}
|
|
1462
|
+
var executeFixCommentsCmd = addAiOptions(
|
|
1463
|
+
new Command5("fix-comments").description(
|
|
1464
|
+
"Fetch open review comments on the current PR and invoke the AI with the Fix-Comments prompt"
|
|
1465
|
+
)
|
|
1466
|
+
).action(async (options) => {
|
|
1467
|
+
const result = resolveCurrentBranchComments();
|
|
1468
|
+
if (!result.ok) {
|
|
1469
|
+
if (result.kind === "error") {
|
|
1470
|
+
process.stderr.write(`Error: ${result.message}
|
|
1471
|
+
`);
|
|
1472
|
+
process.exit(1);
|
|
1473
|
+
}
|
|
1474
|
+
if (result.kind === "unsupported") {
|
|
1475
|
+
process.stderr.write(
|
|
1476
|
+
`Error: fix-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
|
|
1477
|
+
`
|
|
1478
|
+
);
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
}
|
|
1481
|
+
process.stderr.write(`Error: No pull request found for branch: ${result.branch}
|
|
1482
|
+
`);
|
|
1483
|
+
process.exit(1);
|
|
1484
|
+
}
|
|
1485
|
+
const { comments } = result;
|
|
1486
|
+
if (comments.length === 0) {
|
|
1487
|
+
process.stderr.write(`Error: No open review comments found on the pull request.
|
|
1488
|
+
`);
|
|
1489
|
+
process.exit(1);
|
|
1490
|
+
}
|
|
1491
|
+
process.stdout.write(`Found ${String(comments.length)} open review comment${comments.length === 1 ? "" : "s"} on PR. Invoking AI\u2026
|
|
1492
|
+
`);
|
|
1493
|
+
const config = readConfig();
|
|
1494
|
+
const promptText = config.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT;
|
|
1495
|
+
const fullPrompt = withPush(
|
|
1496
|
+
`${promptText}
|
|
1497
|
+
|
|
1498
|
+
Open review comments:
|
|
1499
|
+
|
|
1500
|
+
${formatComments(comments)}`,
|
|
1501
|
+
options.push
|
|
1502
|
+
);
|
|
1503
|
+
if (options.codex) {
|
|
1504
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
1505
|
+
} else {
|
|
1506
|
+
const model = resolveModelOption(options);
|
|
1507
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
var executePromptCommand = new Command5("execute-prompt").description("Execute a configured custom prompt using an AI assistant").addCommand(executeSonarCmd).addCommand(executeFixCommentsCmd);
|
|
1511
|
+
|
|
1197
1512
|
// src/index.ts
|
|
1198
|
-
var program = new
|
|
1513
|
+
var program = new Command6();
|
|
1199
1514
|
program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
|
|
1200
1515
|
program.addCommand(configCommand);
|
|
1201
1516
|
program.addCommand(gitCommand);
|
|
1202
1517
|
program.addCommand(implementNextCommand);
|
|
1203
1518
|
program.addCommand(testCommand);
|
|
1519
|
+
program.addCommand(executePromptCommand);
|
|
1204
1520
|
program.showHelpAfterError();
|
|
1205
1521
|
program.parse();
|
|
1206
1522
|
if (process.argv.length <= 2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automata-cli",
|
|
3
|
-
"version": "0.3.0-develop.
|
|
3
|
+
"version": "0.3.0-develop.93",
|
|
4
4
|
"description": "Automata CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@types/node": "^25.5.0",
|
|
34
34
|
"@types/react": "^19.2.14",
|
|
35
35
|
"eslint": "^10.1.0",
|
|
36
|
+
"ink-testing-library": "^4.0.0",
|
|
36
37
|
"prettier": "^3.8.1",
|
|
37
38
|
"tsup": "^8.5.1",
|
|
38
39
|
"typescript": "^5.9.3",
|