automata-cli 0.2.2 → 0.3.0-develop.112
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 +16 -0
- package/dist/index.js +660 -105
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -83,6 +83,22 @@ When no version is given, the latest semver tag on `master` is detected and the
|
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
86
|
+
## `automata implement-next`
|
|
87
|
+
|
|
88
|
+
Find the next open GitHub issue matching the configured filter, claim it, and invoke Claude Code to implement it.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
automata implement-next # find, claim, and implement
|
|
92
|
+
automata implement-next --query-only # print the issue and exit
|
|
93
|
+
automata implement-next --yolo # skip Claude permission prompts
|
|
94
|
+
automata implement-next --json # JSON output
|
|
95
|
+
automata implement-next --no-claude # claim without launching Claude
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [docs/implement-next.md](docs/implement-next.md) for full details.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
86
102
|
## Development
|
|
87
103
|
|
|
88
104
|
### Prerequisites
|
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
|
`);
|
|
@@ -335,7 +546,31 @@ function fetchCheckRunOutputs(ownerRepo, sha) {
|
|
|
335
546
|
}
|
|
336
547
|
return map;
|
|
337
548
|
}
|
|
338
|
-
function
|
|
549
|
+
function extractSonarProjectKey(url) {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = new URL(url);
|
|
552
|
+
const id = parsed.searchParams.get("id");
|
|
553
|
+
return id ?? null;
|
|
554
|
+
} catch {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function fetchSonarNewIssues(projectKey, prNumber) {
|
|
559
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
|
|
560
|
+
const controller = new AbortController();
|
|
561
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
562
|
+
try {
|
|
563
|
+
const response = await fetch(apiUrl, { signal: controller.signal });
|
|
564
|
+
if (!response.ok) return null;
|
|
565
|
+
const data = await response.json();
|
|
566
|
+
return data.paging?.total ?? null;
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
} finally {
|
|
570
|
+
clearTimeout(timeoutId);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async function getPrInfoGh(branch) {
|
|
339
574
|
const { stdout, stderr, status } = run2("gh", [
|
|
340
575
|
"pr",
|
|
341
576
|
"view",
|
|
@@ -365,9 +600,35 @@ function getPrInfoGh(branch) {
|
|
|
365
600
|
detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
|
|
366
601
|
};
|
|
367
602
|
});
|
|
368
|
-
|
|
603
|
+
const sonarCheck = checks.find((c) => {
|
|
604
|
+
try {
|
|
605
|
+
const hostname = new URL(c.detailsUrl).hostname;
|
|
606
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
607
|
+
} catch {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
let sonarcloudUrl;
|
|
612
|
+
let sonarNewIssues;
|
|
613
|
+
if (sonarCheck) {
|
|
614
|
+
sonarcloudUrl = sonarCheck.detailsUrl;
|
|
615
|
+
const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
|
|
616
|
+
if (projectKey) {
|
|
617
|
+
sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
|
|
618
|
+
} else {
|
|
619
|
+
sonarNewIssues = null;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
number: raw.number,
|
|
624
|
+
title: raw.title,
|
|
625
|
+
state: raw.state,
|
|
626
|
+
url: raw.url,
|
|
627
|
+
checks,
|
|
628
|
+
...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues }
|
|
629
|
+
};
|
|
369
630
|
}
|
|
370
|
-
function getPrInfo2(branch) {
|
|
631
|
+
async function getPrInfo2(branch) {
|
|
371
632
|
const config = readConfig();
|
|
372
633
|
if (config.remoteType === "azdo") {
|
|
373
634
|
return getPrInfo();
|
|
@@ -471,6 +732,23 @@ function getPrComments(branch) {
|
|
|
471
732
|
}
|
|
472
733
|
return getPrCommentsGh(branch);
|
|
473
734
|
}
|
|
735
|
+
function resolveCurrentBranchComments() {
|
|
736
|
+
let branch;
|
|
737
|
+
try {
|
|
738
|
+
branch = getCurrentBranch();
|
|
739
|
+
} catch (err) {
|
|
740
|
+
return { ok: false, kind: "error", message: err.message };
|
|
741
|
+
}
|
|
742
|
+
let raw;
|
|
743
|
+
try {
|
|
744
|
+
raw = getPrComments(branch);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
return { ok: false, kind: "error", message: err.message };
|
|
747
|
+
}
|
|
748
|
+
if (raw === "unsupported") return { ok: false, kind: "unsupported" };
|
|
749
|
+
if (raw === null) return { ok: false, kind: "no-pr", branch };
|
|
750
|
+
return { ok: true, branch, comments: raw };
|
|
751
|
+
}
|
|
474
752
|
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
475
753
|
function getLatestTagOnMaster() {
|
|
476
754
|
const { stdout, status } = run2("git", [
|
|
@@ -553,33 +831,34 @@ function formatChecks(checks) {
|
|
|
553
831
|
const sym = checkSymbol(check);
|
|
554
832
|
const pending = check.status !== "COMPLETED" ? " (pending)" : "";
|
|
555
833
|
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
834
|
}
|
|
564
835
|
return lines.join("\n") + "\n";
|
|
565
836
|
}
|
|
566
837
|
function sleep(ms) {
|
|
567
|
-
return new Promise((
|
|
838
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
839
|
+
}
|
|
840
|
+
function isSonarCheck(check) {
|
|
841
|
+
try {
|
|
842
|
+
const hostname = new URL(check.detailsUrl).hostname;
|
|
843
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
568
847
|
}
|
|
569
848
|
function formatFailedChecks(failed) {
|
|
570
|
-
const lines = [];
|
|
849
|
+
const lines = ["FailedChecks:"];
|
|
571
850
|
for (const check of failed) {
|
|
572
851
|
lines.push(` \u2717 ${check.name}`);
|
|
573
852
|
const desc = check.description.trim();
|
|
574
853
|
const url = check.detailsUrl.trim();
|
|
575
854
|
if (desc) lines.push(` Details: ${desc}`);
|
|
576
|
-
if (url) lines.push(` URL: ${url}`);
|
|
855
|
+
if (url && (isSonarCheck(check) || !desc)) lines.push(` URL: ${url}`);
|
|
577
856
|
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
578
857
|
}
|
|
579
858
|
return lines.join("\n") + "\n";
|
|
580
859
|
}
|
|
581
860
|
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
|
|
861
|
+
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
862
|
"after",
|
|
584
863
|
`
|
|
585
864
|
Check status symbols:
|
|
@@ -588,10 +867,11 @@ Check status symbols:
|
|
|
588
867
|
\u25CF Pending (status: QUEUED or IN_PROGRESS)
|
|
589
868
|
\u25CB Skipped (conclusion: SKIPPED or NEUTRAL)
|
|
590
869
|
|
|
591
|
-
|
|
870
|
+
When checks fail, details are printed in a trailing FailedChecks section.
|
|
592
871
|
See docs/git.md for full output reference.`
|
|
593
872
|
).action(async (options) => {
|
|
594
873
|
let branch;
|
|
874
|
+
let pr = null;
|
|
595
875
|
try {
|
|
596
876
|
branch = getCurrentBranch();
|
|
597
877
|
} catch (err) {
|
|
@@ -600,54 +880,31 @@ See docs/git.md for full output reference.`
|
|
|
600
880
|
process.exit(1);
|
|
601
881
|
}
|
|
602
882
|
if (options.waitFinishChecks) {
|
|
603
|
-
let pr2;
|
|
604
883
|
while (true) {
|
|
605
884
|
try {
|
|
606
|
-
|
|
885
|
+
pr = await getPrInfo2(branch);
|
|
607
886
|
} catch (err) {
|
|
608
887
|
process.stderr.write(`Error: ${err.message}
|
|
609
888
|
`);
|
|
610
889
|
process.exit(1);
|
|
611
890
|
}
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
`);
|
|
615
|
-
process.exit(1);
|
|
891
|
+
if (pr === null) {
|
|
892
|
+
break;
|
|
616
893
|
}
|
|
617
|
-
const running =
|
|
894
|
+
const running = pr.checks.filter((c) => c.status !== "COMPLETED");
|
|
618
895
|
if (running.length === 0) break;
|
|
619
896
|
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
620
897
|
`);
|
|
621
898
|
await sleep(POLL_INTERVAL_MS);
|
|
622
899
|
}
|
|
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:
|
|
900
|
+
} else {
|
|
901
|
+
try {
|
|
902
|
+
pr = await getPrInfo2(branch);
|
|
903
|
+
} catch (err) {
|
|
904
|
+
process.stderr.write(`Error: ${err.message}
|
|
637
905
|
`);
|
|
638
|
-
process.stdout.write(formatFailedChecks(failed));
|
|
639
|
-
}
|
|
640
906
|
process.exit(1);
|
|
641
907
|
}
|
|
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
908
|
}
|
|
652
909
|
if (pr === null) {
|
|
653
910
|
process.stdout.write(`No pull request found for branch: ${branch}
|
|
@@ -657,13 +914,24 @@ See docs/git.md for full output reference.`
|
|
|
657
914
|
if (options.json) {
|
|
658
915
|
process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
|
|
659
916
|
} else {
|
|
917
|
+
const failed = pr.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
660
918
|
process.stdout.write(`PR: #${pr.number}
|
|
661
919
|
Title: ${pr.title}
|
|
662
920
|
State: ${pr.state}
|
|
663
921
|
URL: ${pr.url}
|
|
664
922
|
`);
|
|
923
|
+
if (pr.sonarcloudUrl !== void 0) {
|
|
924
|
+
process.stdout.write(`Sonar: ${pr.sonarcloudUrl}
|
|
925
|
+
`);
|
|
926
|
+
const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
|
|
927
|
+
process.stdout.write(`Sonar New Issues: ${issueStr}
|
|
928
|
+
`);
|
|
929
|
+
}
|
|
665
930
|
process.stdout.write(formatCheckSummary(pr.checks));
|
|
666
931
|
process.stdout.write(formatChecks(pr.checks));
|
|
932
|
+
if (failed.length > 0) {
|
|
933
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
934
|
+
}
|
|
667
935
|
}
|
|
668
936
|
});
|
|
669
937
|
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
@@ -724,7 +992,7 @@ ${safeBody}`);
|
|
|
724
992
|
}
|
|
725
993
|
process.stdout.write(lines.join("\n\n") + "\n");
|
|
726
994
|
});
|
|
727
|
-
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
995
|
+
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(async () => {
|
|
728
996
|
let branch;
|
|
729
997
|
try {
|
|
730
998
|
branch = getCurrentBranch();
|
|
@@ -745,7 +1013,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
745
1013
|
}
|
|
746
1014
|
let pr;
|
|
747
1015
|
try {
|
|
748
|
-
pr = getPrInfo2(branch);
|
|
1016
|
+
pr = await getPrInfo2(branch);
|
|
749
1017
|
} catch (err) {
|
|
750
1018
|
process.stderr.write(`Error: ${err.message}
|
|
751
1019
|
`);
|
|
@@ -876,9 +1144,6 @@ var gitCommand = new Command2("git").description("Git workflow commands (some re
|
|
|
876
1144
|
|
|
877
1145
|
// src/commands/getReady.ts
|
|
878
1146
|
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
1147
|
|
|
883
1148
|
// src/config/githubService.ts
|
|
884
1149
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -903,10 +1168,6 @@ function listIssues(technique, value) {
|
|
|
903
1168
|
"list",
|
|
904
1169
|
"--state",
|
|
905
1170
|
"open",
|
|
906
|
-
"--sort",
|
|
907
|
-
"created",
|
|
908
|
-
"--order",
|
|
909
|
-
"asc",
|
|
910
1171
|
"--limit",
|
|
911
1172
|
"1",
|
|
912
1173
|
"--json",
|
|
@@ -941,42 +1202,195 @@ function postComment(issueNumber, body) {
|
|
|
941
1202
|
}
|
|
942
1203
|
}
|
|
943
1204
|
|
|
944
|
-
// src/
|
|
1205
|
+
// src/claude/claudeService.ts
|
|
1206
|
+
import { spawn, spawnSync as spawnSync4 } from "child_process";
|
|
1207
|
+
import { createInterface } from "readline";
|
|
1208
|
+
import { existsSync } from "fs";
|
|
1209
|
+
import { delimiter, join as join3 } from "path";
|
|
1210
|
+
|
|
1211
|
+
// src/cli/spawnUtils.ts
|
|
1212
|
+
function truncate(str, max) {
|
|
1213
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
1214
|
+
}
|
|
1215
|
+
function handleSpawnError(error, toolName) {
|
|
1216
|
+
if (!error) return;
|
|
1217
|
+
const err = error;
|
|
1218
|
+
if (err.code === "ENOENT") {
|
|
1219
|
+
process.stderr.write(`Error: \`${toolName}\` CLI is not installed or not on PATH.
|
|
1220
|
+
`);
|
|
1221
|
+
process.exit(1);
|
|
1222
|
+
}
|
|
1223
|
+
process.stderr.write(`Error: ${err.message}
|
|
1224
|
+
`);
|
|
1225
|
+
process.exit(1);
|
|
1226
|
+
}
|
|
1227
|
+
function handleExitCode(status, toolName) {
|
|
1228
|
+
if (status === null) {
|
|
1229
|
+
process.stderr.write(`Error: ${toolName} terminated abnormally (exit code is null, likely due to a signal).
|
|
1230
|
+
`);
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
}
|
|
1233
|
+
if (status !== 0) {
|
|
1234
|
+
process.stderr.write(`Error: ${toolName} exited with code ${status}.
|
|
1235
|
+
`);
|
|
1236
|
+
process.exit(status);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/claude/claudeService.ts
|
|
945
1241
|
function resolveCommand(name) {
|
|
946
1242
|
const pathDirs = (process.env["PATH"] ?? "").split(delimiter);
|
|
947
1243
|
for (const dir of pathDirs) {
|
|
948
|
-
const candidate =
|
|
1244
|
+
const candidate = join3(dir, name);
|
|
949
1245
|
if (existsSync(candidate)) return candidate;
|
|
950
1246
|
}
|
|
951
1247
|
return name;
|
|
952
1248
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
process.stderr.write("Error: `claude` CLI is not installed or not on PATH.\n");
|
|
963
|
-
process.exit(1);
|
|
964
|
-
}
|
|
965
|
-
process.stderr.write(`Error: ${err.message}
|
|
1249
|
+
var MODEL_IDS = {
|
|
1250
|
+
opus: "claude-opus-4-6",
|
|
1251
|
+
sonnet: "claude-sonnet-4-6",
|
|
1252
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
1253
|
+
};
|
|
1254
|
+
function resolveModelOption(opts) {
|
|
1255
|
+
const selected = ["opus", "sonnet", "haiku"].filter((m) => opts[m]);
|
|
1256
|
+
if (selected.length > 1) {
|
|
1257
|
+
process.stderr.write(`Error: --${selected[0]} and --${selected[1]} are mutually exclusive.
|
|
966
1258
|
`);
|
|
967
1259
|
process.exit(1);
|
|
968
1260
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1261
|
+
return selected.length === 1 ? MODEL_IDS[selected[0]] : void 0;
|
|
1262
|
+
}
|
|
1263
|
+
function invokeClaudeCode(prompt, options = {}) {
|
|
1264
|
+
if (options.verbose) {
|
|
1265
|
+
return invokeClaudeCodeVerbose(prompt, options.yolo ?? false, options.model);
|
|
1266
|
+
}
|
|
1267
|
+
invokeClaudeCodeSync(prompt, options.yolo ?? false, options.model);
|
|
1268
|
+
}
|
|
1269
|
+
function invokeClaudeCodeSync(prompt, yolo, model) {
|
|
1270
|
+
const claudeBin = resolveCommand("claude");
|
|
1271
|
+
const args = [];
|
|
1272
|
+
if (yolo) args.push("--dangerously-skip-permissions");
|
|
1273
|
+
if (model) args.push("--model", model);
|
|
1274
|
+
args.push("-p", prompt);
|
|
1275
|
+
const result = spawnSync4(claudeBin, args, { encoding: "utf8", stdio: "inherit" });
|
|
1276
|
+
handleSpawnError(result.error, "claude");
|
|
1277
|
+
handleExitCode(result.status, "Claude Code");
|
|
1278
|
+
}
|
|
1279
|
+
function invokeClaudeCodeVerbose(prompt, yolo, model) {
|
|
1280
|
+
return new Promise((resolve3) => {
|
|
1281
|
+
const claudeBin = resolveCommand("claude");
|
|
1282
|
+
const args = [];
|
|
1283
|
+
if (yolo) args.push("--dangerously-skip-permissions");
|
|
1284
|
+
if (model) args.push("--model", model);
|
|
1285
|
+
args.push("--verbose", "--output-format", "stream-json", "-p", prompt);
|
|
1286
|
+
const child = spawn(claudeBin, args, { stdio: ["inherit", "pipe", "inherit"] });
|
|
1287
|
+
const rl = createInterface({ input: child.stdout });
|
|
1288
|
+
let turnCount = 0;
|
|
1289
|
+
child.on("error", (err) => {
|
|
1290
|
+
handleSpawnError(err, "claude");
|
|
1291
|
+
});
|
|
1292
|
+
rl.on("line", (line) => {
|
|
1293
|
+
try {
|
|
1294
|
+
const event = JSON.parse(line);
|
|
1295
|
+
formatEvent(event, turnCount);
|
|
1296
|
+
if (event["type"] === "assistant") turnCount++;
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
child.on("close", (code) => {
|
|
1301
|
+
handleExitCode(code, "Claude Code");
|
|
1302
|
+
resolve3();
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function formatEvent(event, turnCount) {
|
|
1307
|
+
const type = event["type"];
|
|
1308
|
+
if (type === "assistant") {
|
|
1309
|
+
const message = event["message"];
|
|
1310
|
+
const content = message?.["content"];
|
|
1311
|
+
if (!content) return;
|
|
1312
|
+
for (const block of content) {
|
|
1313
|
+
if (block["type"] === "tool_use") {
|
|
1314
|
+
const toolName = block["name"];
|
|
1315
|
+
const input = block["input"];
|
|
1316
|
+
const summary = summarizeTool(toolName, input);
|
|
1317
|
+
process.stderr.write(` [step ${turnCount + 1}] ${summary}
|
|
1318
|
+
`);
|
|
1319
|
+
} else if (block["type"] === "text") {
|
|
1320
|
+
const text = block["text"] ?? "";
|
|
1321
|
+
if (text.length > 0) {
|
|
1322
|
+
const preview = text.length > 120 ? text.slice(0, 120) + "..." : text;
|
|
1323
|
+
const firstLine = preview.split("\n")[0];
|
|
1324
|
+
process.stderr.write(` [step ${turnCount + 1}] ${firstLine}
|
|
1325
|
+
`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
} else if (type === "result") {
|
|
1330
|
+
const result = event["result"];
|
|
1331
|
+
const cost = event["cost_usd"];
|
|
1332
|
+
const duration = event["duration_ms"];
|
|
1333
|
+
const turns = event["num_turns"];
|
|
1334
|
+
process.stderr.write("\n--- Result ---\n");
|
|
1335
|
+
if (cost !== void 0 || duration !== void 0 || turns !== void 0) {
|
|
1336
|
+
const parts = [];
|
|
1337
|
+
if (turns !== void 0) parts.push(`${turns} turns`);
|
|
1338
|
+
if (duration !== void 0) parts.push(`${(duration / 1e3).toFixed(1)}s`);
|
|
1339
|
+
if (cost !== void 0) parts.push(`$${cost.toFixed(4)}`);
|
|
1340
|
+
process.stderr.write(` [info] ${parts.join(" | ")}
|
|
971
1341
|
`);
|
|
972
|
-
|
|
1342
|
+
}
|
|
1343
|
+
if (result) {
|
|
1344
|
+
process.stdout.write(result + "\n");
|
|
1345
|
+
}
|
|
973
1346
|
}
|
|
974
1347
|
}
|
|
975
|
-
|
|
1348
|
+
function summarizeTool(name, input) {
|
|
1349
|
+
if (!input) return `tool: ${name}`;
|
|
1350
|
+
switch (name) {
|
|
1351
|
+
case "Read":
|
|
1352
|
+
return `reading ${input["file_path"] ?? "file"}`;
|
|
1353
|
+
case "Write":
|
|
1354
|
+
return `writing ${input["file_path"] ?? "file"}`;
|
|
1355
|
+
case "Edit":
|
|
1356
|
+
return `editing ${input["file_path"] ?? "file"}`;
|
|
1357
|
+
case "Bash":
|
|
1358
|
+
return `running: ${truncate(String(input["command"] ?? ""), 80)}`;
|
|
1359
|
+
case "Glob":
|
|
1360
|
+
return `searching files: ${input["pattern"] ?? ""}`;
|
|
1361
|
+
case "Grep":
|
|
1362
|
+
return `searching content: ${truncate(String(input["pattern"] ?? ""), 60)}`;
|
|
1363
|
+
case "Agent":
|
|
1364
|
+
return `spawning agent: ${input["description"] ?? name}`;
|
|
1365
|
+
default:
|
|
1366
|
+
return `tool: ${name}`;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/codex/codexService.ts
|
|
1371
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
1372
|
+
function invokeCodexCode(prompt, options = {}) {
|
|
1373
|
+
if (options.verbose) {
|
|
1374
|
+
process.stderr.write("Warning: --verbose is not supported for Codex and will be ignored.\n");
|
|
1375
|
+
}
|
|
1376
|
+
invokeCodexCodeSync(prompt, options.yolo ?? false);
|
|
1377
|
+
}
|
|
1378
|
+
function invokeCodexCodeSync(prompt, yolo) {
|
|
1379
|
+
const codexBin = resolveCommand("codex");
|
|
1380
|
+
const args = ["exec"];
|
|
1381
|
+
if (yolo) args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
1382
|
+
args.push(prompt);
|
|
1383
|
+
const result = spawnSync5(codexBin, args, { encoding: "utf8", stdio: "inherit" });
|
|
1384
|
+
handleSpawnError(result.error, "codex");
|
|
1385
|
+
handleExitCode(result.status, "Codex");
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/commands/getReady.ts
|
|
1389
|
+
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").action(async (options) => {
|
|
976
1390
|
const config = readConfig();
|
|
977
1391
|
if (config.remoteType !== "gh") {
|
|
978
1392
|
process.stderr.write(
|
|
979
|
-
"Error:
|
|
1393
|
+
"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"
|
|
980
1394
|
);
|
|
981
1395
|
process.exit(1);
|
|
982
1396
|
}
|
|
@@ -1014,6 +1428,9 @@ URL: ${issue.url}
|
|
|
1014
1428
|
${issue.body}
|
|
1015
1429
|
`);
|
|
1016
1430
|
}
|
|
1431
|
+
if (options.queryOnly) {
|
|
1432
|
+
process.exit(0);
|
|
1433
|
+
}
|
|
1017
1434
|
try {
|
|
1018
1435
|
postComment(issue.number, "working");
|
|
1019
1436
|
} catch (err) {
|
|
@@ -1022,16 +1439,154 @@ ${issue.body}
|
|
|
1022
1439
|
process.exit(1);
|
|
1023
1440
|
}
|
|
1024
1441
|
if (options.claude !== false) {
|
|
1025
|
-
|
|
1442
|
+
const systemPrompt = config.claudeSystemPrompt ?? DEFAULT_CLAUDE_SYSTEM_PROMPT;
|
|
1443
|
+
const prompt = `${systemPrompt}
|
|
1444
|
+
|
|
1445
|
+
${issue.body}`;
|
|
1446
|
+
if (options.codex) {
|
|
1447
|
+
await invokeCodexCode(prompt, { yolo: options.yolo, verbose: options.verbose });
|
|
1448
|
+
} else {
|
|
1449
|
+
const model = resolveModelOption(options);
|
|
1450
|
+
await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: options.verbose, model });
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// src/commands/test.ts
|
|
1456
|
+
import { Command as Command4 } from "commander";
|
|
1457
|
+
var testClaudeCmd = new Command4("claude").description("Test Claude Code invocation with a user-supplied prompt").requiredOption("--prompt <string>", "Prompt to send to Claude Code").option("--yolo", "Launch Claude Code with --dangerously-skip-permissions").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").action(async (options) => {
|
|
1458
|
+
const model = resolveModelOption(options);
|
|
1459
|
+
await invokeClaudeCode(options.prompt, { yolo: options.yolo, verbose: options.verbose, model });
|
|
1460
|
+
});
|
|
1461
|
+
var testCodexCmd = new Command4("codex").description("Test Codex CLI invocation with a user-supplied prompt").requiredOption("--prompt <string>", "Prompt to send to Codex CLI").option("--yolo", "Launch Codex with --dangerously-bypass-approvals-and-sandbox").option("--verbose", "Not supported for Codex; prints a warning and is otherwise ignored").action((options) => {
|
|
1462
|
+
invokeCodexCode(options.prompt, { yolo: options.yolo, verbose: options.verbose });
|
|
1463
|
+
});
|
|
1464
|
+
var testCommand = new Command4("test").description("Test commands for verifying automata integrations").addCommand(testClaudeCmd).addCommand(testCodexCmd);
|
|
1465
|
+
|
|
1466
|
+
// src/commands/executePrompt.ts
|
|
1467
|
+
import { Command as Command5 } from "commander";
|
|
1468
|
+
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.";
|
|
1469
|
+
function withPush(prompt, push) {
|
|
1470
|
+
return push ? `${prompt}
|
|
1471
|
+
|
|
1472
|
+
${PUSH_INSTRUCTION}` : prompt;
|
|
1473
|
+
}
|
|
1474
|
+
function addAiOptions(cmd) {
|
|
1475
|
+
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)");
|
|
1476
|
+
}
|
|
1477
|
+
var executeSonarCmd = addAiOptions(
|
|
1478
|
+
new Command5("sonar").description(
|
|
1479
|
+
"Check the current branch for a SonarCloud analysis and invoke the AI with the Sonar prompt and analysis URL"
|
|
1480
|
+
)
|
|
1481
|
+
).action(async (options) => {
|
|
1482
|
+
let branch;
|
|
1483
|
+
try {
|
|
1484
|
+
branch = getCurrentBranch();
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
process.stderr.write(`Error: ${err.message}
|
|
1487
|
+
`);
|
|
1488
|
+
process.exit(1);
|
|
1489
|
+
}
|
|
1490
|
+
let pr;
|
|
1491
|
+
try {
|
|
1492
|
+
pr = await getPrInfo2(branch);
|
|
1493
|
+
} catch (err) {
|
|
1494
|
+
process.stderr.write(`Error: ${err.message}
|
|
1495
|
+
`);
|
|
1496
|
+
process.exit(1);
|
|
1497
|
+
}
|
|
1498
|
+
if (pr === null) {
|
|
1499
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
1500
|
+
`);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
}
|
|
1503
|
+
if (!pr.sonarcloudUrl) {
|
|
1504
|
+
process.stderr.write(
|
|
1505
|
+
`Error: No SonarCloud analysis found for PR #${pr.number}. Ensure a SonarCloud check is configured on this repository.
|
|
1506
|
+
`
|
|
1507
|
+
);
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
const config = readConfig();
|
|
1511
|
+
const sonarPromptText = config.prompts?.sonar ?? DEFAULT_SONAR_PROMPT;
|
|
1512
|
+
const fullPrompt = withPush(
|
|
1513
|
+
`${sonarPromptText}
|
|
1514
|
+
|
|
1515
|
+
SonarCloud analysis URL: ${pr.sonarcloudUrl}`,
|
|
1516
|
+
options.push
|
|
1517
|
+
);
|
|
1518
|
+
if (options.codex) {
|
|
1519
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
1520
|
+
} else {
|
|
1521
|
+
const model = resolveModelOption(options);
|
|
1522
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
function formatComments(comments) {
|
|
1526
|
+
return comments.map((c) => {
|
|
1527
|
+
const loc = c.line === null ? `${c.path}:(file)` : `${c.path}:${String(c.line)}`;
|
|
1528
|
+
return `[${c.author}] on ${loc}
|
|
1529
|
+
${c.body}`;
|
|
1530
|
+
}).join("\n\n");
|
|
1531
|
+
}
|
|
1532
|
+
var executeFixCommentsCmd = addAiOptions(
|
|
1533
|
+
new Command5("fix-comments").description(
|
|
1534
|
+
"Fetch open review comments on the current PR and invoke the AI with the Fix-Comments prompt"
|
|
1535
|
+
)
|
|
1536
|
+
).action(async (options) => {
|
|
1537
|
+
const result = resolveCurrentBranchComments();
|
|
1538
|
+
if (!result.ok) {
|
|
1539
|
+
if (result.kind === "error") {
|
|
1540
|
+
process.stderr.write(`Error: ${result.message}
|
|
1541
|
+
`);
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
1544
|
+
if (result.kind === "unsupported") {
|
|
1545
|
+
process.stderr.write(
|
|
1546
|
+
`Error: fix-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
|
|
1547
|
+
`
|
|
1548
|
+
);
|
|
1549
|
+
process.exit(1);
|
|
1550
|
+
}
|
|
1551
|
+
process.stderr.write(`Error: No pull request found for branch: ${result.branch}
|
|
1552
|
+
`);
|
|
1553
|
+
process.exit(1);
|
|
1554
|
+
}
|
|
1555
|
+
const { comments } = result;
|
|
1556
|
+
if (comments.length === 0) {
|
|
1557
|
+
process.stderr.write(`Error: No open review comments found on the pull request.
|
|
1558
|
+
`);
|
|
1559
|
+
process.exit(1);
|
|
1560
|
+
}
|
|
1561
|
+
process.stdout.write(`Found ${String(comments.length)} open review comment${comments.length === 1 ? "" : "s"} on PR. Invoking AI\u2026
|
|
1562
|
+
`);
|
|
1563
|
+
const config = readConfig();
|
|
1564
|
+
const promptText = config.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT;
|
|
1565
|
+
const fullPrompt = withPush(
|
|
1566
|
+
`${promptText}
|
|
1567
|
+
|
|
1568
|
+
Open review comments:
|
|
1569
|
+
|
|
1570
|
+
${formatComments(comments)}`,
|
|
1571
|
+
options.push
|
|
1572
|
+
);
|
|
1573
|
+
if (options.codex) {
|
|
1574
|
+
invokeCodexCode(fullPrompt, { yolo: true });
|
|
1575
|
+
} else {
|
|
1576
|
+
const model = resolveModelOption(options);
|
|
1577
|
+
await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
|
|
1026
1578
|
}
|
|
1027
1579
|
});
|
|
1580
|
+
var executePromptCommand = new Command5("execute-prompt").description("Execute a configured custom prompt using an AI assistant").addCommand(executeSonarCmd).addCommand(executeFixCommentsCmd);
|
|
1028
1581
|
|
|
1029
1582
|
// src/index.ts
|
|
1030
|
-
var program = new
|
|
1583
|
+
var program = new Command6();
|
|
1031
1584
|
program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
|
|
1032
1585
|
program.addCommand(configCommand);
|
|
1033
1586
|
program.addCommand(gitCommand);
|
|
1034
|
-
program.addCommand(
|
|
1587
|
+
program.addCommand(implementNextCommand);
|
|
1588
|
+
program.addCommand(testCommand);
|
|
1589
|
+
program.addCommand(executePromptCommand);
|
|
1035
1590
|
program.showHelpAfterError();
|
|
1036
1591
|
program.parse();
|
|
1037
1592
|
if (process.argv.length <= 2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automata-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-develop.112",
|
|
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",
|