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