automata-cli 0.1.0-bugfix-nextwork.57 → 0.1.0-feature-021-execute-command.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +1152 -114
  3. 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 Command4 } from "commander";
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 readConfig() {
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("remote");
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 === "remote") {
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({ ...existing, remoteType: chosen.value });
163
+ writeConfig({ ...rawExisting, remoteType: chosen.value });
83
164
  exit();
84
165
  }
85
- } else if (key.escape || key.ctrl && input === "c") {
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 || key.ctrl && input === "c") {
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 || key.ctrl && input === "c") {
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
- ...existing,
205
+ ...rawExisting,
114
206
  remoteType: pendingRemote,
115
207
  issueDiscoveryTechnique: pendingTechnique,
116
208
  issueDiscoveryValue: discoveryValue || void 0,
117
- claudeSystemPrompt: systemPrompt || void 0
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 || key.ctrl && input === "c") {
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 === "remote") {
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: "Configure Issue Discovery Technique" }),
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: "Configure Issue Discovery Value" }),
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 confirm \xB7 Ctrl+C to cancel" })
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: "Configure Claude System Prompt" }),
385
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Prompts \u2014 Fix-Comments" }),
175
386
  /* @__PURE__ */ jsx(Text, { children: " " }),
176
387
  /* @__PURE__ */ jsxs(Text, { children: [
177
- "System prompt (optional):",
388
+ "Fix-Comments prompt:",
178
389
  " ",
179
390
  /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
180
- systemPrompt,
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 and exit \xB7 Ctrl+C to cancel" })
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 = readConfig();
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 = readConfig();
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 = readConfig();
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 = readConfig();
432
+ const current = readRawConfig();
222
433
  writeConfig({ ...current, claudeSystemPrompt: value });
223
434
  process.stdout.write(`Claude system prompt set.
224
435
  `);
@@ -299,6 +510,8 @@ function getCurrentBranch() {
299
510
  }
300
511
  return stdout.trim();
301
512
  }
513
+ var SONAR_FETCH_TIMEOUT_MS = 5e3;
514
+ var SONAR_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
302
515
  function parseOwnerRepo() {
303
516
  const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
304
517
  if (status !== 0) return null;
@@ -335,7 +548,295 @@ function fetchCheckRunOutputs(ownerRepo, sha) {
335
548
  }
336
549
  return map;
337
550
  }
338
- function getPrInfoGh(branch) {
551
+ function extractSonarProjectKey(url) {
552
+ try {
553
+ const parsed = new URL(url);
554
+ const id = parsed.searchParams.get("id");
555
+ return id ?? null;
556
+ } catch {
557
+ return null;
558
+ }
559
+ }
560
+ function isSonarUrl(url) {
561
+ try {
562
+ const hostname = new URL(url).hostname;
563
+ return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
568
+ function stripHtml(text) {
569
+ let stripped = "";
570
+ let inTag = false;
571
+ for (const char of text) {
572
+ if (char === "<") {
573
+ inTag = true;
574
+ stripped += " ";
575
+ continue;
576
+ }
577
+ if (char === ">") {
578
+ inTag = false;
579
+ continue;
580
+ }
581
+ if (!inTag) {
582
+ stripped += char;
583
+ }
584
+ }
585
+ return stripped;
586
+ }
587
+ function normalizeText(text) {
588
+ if (!text) return void 0;
589
+ const normalized = stripHtml(text).replaceAll(/\s+/g, " ").trim();
590
+ return normalized || void 0;
591
+ }
592
+ function resolveSonarPath(componentKey, components) {
593
+ if (!componentKey) return void 0;
594
+ const mapped = components.get(componentKey);
595
+ if (mapped) return mapped;
596
+ const separatorIndex = componentKey.indexOf(":");
597
+ if (separatorIndex === -1) return void 0;
598
+ const path = componentKey.slice(separatorIndex + 1).trim();
599
+ return path || void 0;
600
+ }
601
+ function mapSonarIssuesPage(data, targetIssues, components, rules) {
602
+ for (const component of data.components ?? []) {
603
+ if (component.key && component.path) {
604
+ components.set(component.key, component.path);
605
+ }
606
+ }
607
+ for (const rule of data.rules ?? []) {
608
+ if (!rule.key) continue;
609
+ const explanation = normalizeText(rule.htmlDesc) ?? normalizeText(rule.htmlNote) ?? normalizeText(rule.name);
610
+ if (explanation) {
611
+ rules.set(rule.key, explanation);
612
+ }
613
+ }
614
+ for (const issue of data.issues ?? []) {
615
+ if (!issue.key || !issue.message) continue;
616
+ const ruleKey = issue.rule;
617
+ const mappedIssue = {
618
+ key: issue.key,
619
+ severity: issue.severity,
620
+ type: issue.type,
621
+ message: issue.message,
622
+ path: resolveSonarPath(issue.component, components),
623
+ line: issue.line ?? issue.textRange?.startLine ?? null,
624
+ ...ruleKey ? { rule: ruleKey } : {},
625
+ ...ruleKey && rules.has(ruleKey) ? { explanation: rules.get(ruleKey) } : {}
626
+ };
627
+ targetIssues.push(mappedIssue);
628
+ }
629
+ }
630
+ function mapSonarHotspotsPage(data, targetHotspots, components) {
631
+ for (const component of data.components ?? []) {
632
+ if (component.key && component.path) {
633
+ components.set(component.key, component.path);
634
+ }
635
+ }
636
+ targetHotspots.push(...data.hotspots ?? []);
637
+ }
638
+ function mapSonarHotspot(hotspot, components, detail) {
639
+ const rule = detail?.rule;
640
+ return {
641
+ key: detail?.key ?? hotspot.key ?? "",
642
+ rule: hotspot.ruleKey ?? rule?.key ?? "",
643
+ ruleName: normalizeText(rule?.name),
644
+ status: detail?.status ?? hotspot.status ?? "UNKNOWN",
645
+ message: detail?.message ?? hotspot.message ?? "",
646
+ path: detail?.component?.path ?? resolveSonarPath(hotspot.component, components),
647
+ line: detail?.line ?? detail?.textRange?.startLine ?? hotspot.line ?? hotspot.textRange?.startLine ?? null,
648
+ securityCategory: rule?.securityCategory ?? hotspot.securityCategory,
649
+ vulnerabilityProbability: rule?.vulnerabilityProbability ?? hotspot.vulnerabilityProbability,
650
+ riskDescription: normalizeText(rule?.riskDescription),
651
+ vulnerabilityDescription: normalizeText(rule?.vulnerabilityDescription),
652
+ fixRecommendations: normalizeText(rule?.fixRecommendations)
653
+ };
654
+ }
655
+ async function fetchSonarJson(apiUrl) {
656
+ const controller = new AbortController();
657
+ const timeoutId = setTimeout(() => controller.abort(), SONAR_FETCH_TIMEOUT_MS);
658
+ try {
659
+ const response = await fetch(apiUrl, { signal: controller.signal });
660
+ if (!response.ok) {
661
+ return { ok: false, status: response.status };
662
+ }
663
+ return {
664
+ ok: true,
665
+ status: response.status,
666
+ data: await response.json()
667
+ };
668
+ } catch {
669
+ return { ok: false, status: null };
670
+ } finally {
671
+ clearTimeout(timeoutId);
672
+ }
673
+ }
674
+ async function fetchSonarNewIssues(projectKey, prNumber) {
675
+ const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
676
+ const response = await fetchSonarJson(apiUrl);
677
+ if (!response.ok) {
678
+ return null;
679
+ }
680
+ return response.data.paging?.total ?? null;
681
+ }
682
+ async function fetchSonarGateViolations(projectKey, prNumber) {
683
+ const apiUrl = `https://sonarcloud.io/api/qualitygates/project_status?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}`;
684
+ const response = await fetchSonarJson(apiUrl);
685
+ if (!response.ok) {
686
+ return response;
687
+ }
688
+ const projectStatus = response.data.projectStatus;
689
+ const gateViolations = (projectStatus?.conditions ?? []).filter((condition) => condition.status !== void 0 && condition.status !== "OK").map((condition) => ({
690
+ metricKey: condition.metricKey ?? "unknown",
691
+ status: condition.status ?? "ERROR",
692
+ comparator: condition.comparator,
693
+ actualValue: condition.actualValue,
694
+ errorThreshold: condition.errorThreshold
695
+ }));
696
+ return {
697
+ ok: true,
698
+ status: response.status,
699
+ data: {
700
+ status: "available",
701
+ qualityGateStatus: projectStatus?.status,
702
+ gateViolations,
703
+ issues: [],
704
+ securityHotspots: []
705
+ }
706
+ };
707
+ }
708
+ async function fetchSonarIssues(projectKey, prNumber) {
709
+ const pageSize = 100;
710
+ const issues = [];
711
+ const components = /* @__PURE__ */ new Map();
712
+ const rules = /* @__PURE__ */ new Map();
713
+ let page = 1;
714
+ let total = null;
715
+ while (true) {
716
+ const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=${String(pageSize)}&p=${String(page)}&additionalFields=_all`;
717
+ const response = await fetchSonarJson(apiUrl);
718
+ if (!response.ok) {
719
+ return response;
720
+ }
721
+ const paging = response.data.paging;
722
+ total ??= paging?.total ?? null;
723
+ mapSonarIssuesPage(response.data, issues, components, rules);
724
+ const fetchedCount = page * (paging?.pageSize ?? pageSize);
725
+ if (paging?.total === void 0 || fetchedCount >= paging.total) {
726
+ break;
727
+ }
728
+ page += 1;
729
+ }
730
+ return {
731
+ ok: true,
732
+ status: 200,
733
+ data: {
734
+ total,
735
+ issues
736
+ }
737
+ };
738
+ }
739
+ async function fetchSonarHotspotDetail(hotspotKey) {
740
+ const apiUrl = `https://sonarcloud.io/api/hotspots/show?hotspot=${encodeURIComponent(hotspotKey)}`;
741
+ return fetchSonarJson(apiUrl);
742
+ }
743
+ async function fetchSonarHotspots(projectKey, prNumber) {
744
+ const pageSize = 100;
745
+ const rawHotspots = [];
746
+ const components = /* @__PURE__ */ new Map();
747
+ let page = 1;
748
+ while (true) {
749
+ const apiUrl = `https://sonarcloud.io/api/hotspots/search?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&onlyMine=false&sinceLeakPeriod=true&ps=${String(pageSize)}&p=${String(page)}`;
750
+ const response = await fetchSonarJson(apiUrl);
751
+ if (!response.ok) {
752
+ return response;
753
+ }
754
+ const paging = response.data.paging;
755
+ mapSonarHotspotsPage(response.data, rawHotspots, components);
756
+ const fetchedCount = page * (paging?.pageSize ?? pageSize);
757
+ if (paging?.total === void 0 || fetchedCount >= paging.total) {
758
+ break;
759
+ }
760
+ page += 1;
761
+ }
762
+ const detailResults = await Promise.all(rawHotspots.map((hotspot) => fetchSonarHotspotDetail(hotspot.key ?? "")));
763
+ const securityHotspots = [];
764
+ for (let index = 0; index < rawHotspots.length; index += 1) {
765
+ const hotspot = rawHotspots[index];
766
+ const detailResult = detailResults[index];
767
+ if (detailResult && !detailResult.ok && detailResult.status === 401) {
768
+ return detailResult;
769
+ }
770
+ securityHotspots.push(mapSonarHotspot(hotspot, components, detailResult?.ok ? detailResult.data : void 0));
771
+ }
772
+ return {
773
+ ok: true,
774
+ status: 200,
775
+ data: securityHotspots
776
+ };
777
+ }
778
+ function sonarPrivateFailureSummary(sonarcloudUrl) {
779
+ return {
780
+ status: "private",
781
+ gateViolations: [],
782
+ issues: [],
783
+ securityHotspots: [],
784
+ privateMessage: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
785
+ };
786
+ }
787
+ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
788
+ const [gateResult, issuesResult, hotspotsResult] = await Promise.all([
789
+ fetchSonarGateViolations(projectKey, prNumber),
790
+ fetchSonarIssues(projectKey, prNumber),
791
+ fetchSonarHotspots(projectKey, prNumber)
792
+ ]);
793
+ if (!gateResult.ok && gateResult.status === 401) {
794
+ return {
795
+ summary: sonarPrivateFailureSummary(sonarcloudUrl),
796
+ issueTotal: null
797
+ };
798
+ }
799
+ if (!issuesResult.ok && issuesResult.status === 401) {
800
+ return {
801
+ summary: sonarPrivateFailureSummary(sonarcloudUrl),
802
+ issueTotal: null
803
+ };
804
+ }
805
+ if (!hotspotsResult.ok && hotspotsResult.status === 401) {
806
+ return {
807
+ summary: sonarPrivateFailureSummary(sonarcloudUrl),
808
+ issueTotal: null
809
+ };
810
+ }
811
+ const gateViolations = gateResult.ok ? gateResult.data.gateViolations : [];
812
+ const issues = issuesResult.ok ? issuesResult.data.issues : [];
813
+ const securityHotspots = hotspotsResult.ok ? hotspotsResult.data : [];
814
+ const qualityGateStatus = gateResult.ok ? gateResult.data.qualityGateStatus : void 0;
815
+ const issueTotal = issuesResult.ok ? issuesResult.data.total : null;
816
+ if (gateViolations.length === 0 && issues.length === 0 && securityHotspots.length === 0 && !qualityGateStatus) {
817
+ return {
818
+ summary: {
819
+ status: "unavailable",
820
+ gateViolations: [],
821
+ issues: [],
822
+ securityHotspots: [],
823
+ unavailableMessage: "SonarCloud failure details are unavailable right now."
824
+ },
825
+ issueTotal
826
+ };
827
+ }
828
+ return {
829
+ summary: {
830
+ status: "available",
831
+ qualityGateStatus,
832
+ gateViolations,
833
+ issues,
834
+ securityHotspots
835
+ },
836
+ issueTotal
837
+ };
838
+ }
839
+ async function getPrInfoGh(branch) {
339
840
  const { stdout, stderr, status } = run2("gh", [
340
841
  "pr",
341
842
  "view",
@@ -365,9 +866,36 @@ function getPrInfoGh(branch) {
365
866
  detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
366
867
  };
367
868
  });
368
- return { number: raw.number, title: raw.title, state: raw.state, url: raw.url, checks };
869
+ const sonarCheck = checks.find((c) => isSonarUrl(c.detailsUrl));
870
+ let sonarcloudUrl;
871
+ let sonarNewIssues;
872
+ let sonarFailures;
873
+ if (sonarCheck) {
874
+ sonarcloudUrl = sonarCheck.detailsUrl;
875
+ const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
876
+ if (projectKey) {
877
+ if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
878
+ const sonarSummary = await fetchSonarFailureSummary(projectKey, raw.number, sonarcloudUrl);
879
+ sonarFailures = sonarSummary.summary;
880
+ sonarNewIssues = sonarSummary.issueTotal;
881
+ } else {
882
+ sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
883
+ }
884
+ } else {
885
+ sonarNewIssues = null;
886
+ }
887
+ }
888
+ return {
889
+ number: raw.number,
890
+ title: raw.title,
891
+ state: raw.state,
892
+ url: raw.url,
893
+ checks,
894
+ ...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues },
895
+ ...sonarFailures === void 0 ? {} : { sonarFailures }
896
+ };
369
897
  }
370
- function getPrInfo2(branch) {
898
+ async function getPrInfo2(branch) {
371
899
  const config = readConfig();
372
900
  if (config.remoteType === "azdo") {
373
901
  return getPrInfo();
@@ -471,6 +999,23 @@ function getPrComments(branch) {
471
999
  }
472
1000
  return getPrCommentsGh(branch);
473
1001
  }
1002
+ function resolveCurrentBranchComments() {
1003
+ let branch;
1004
+ try {
1005
+ branch = getCurrentBranch();
1006
+ } catch (err) {
1007
+ return { ok: false, kind: "error", message: err.message };
1008
+ }
1009
+ let raw;
1010
+ try {
1011
+ raw = getPrComments(branch);
1012
+ } catch (err) {
1013
+ return { ok: false, kind: "error", message: err.message };
1014
+ }
1015
+ if (raw === "unsupported") return { ok: false, kind: "unsupported" };
1016
+ if (raw === null) return { ok: false, kind: "no-pr", branch };
1017
+ return { ok: true, branch, comments: raw };
1018
+ }
474
1019
  var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
475
1020
  function getLatestTagOnMaster() {
476
1021
  const { stdout, status } = run2("git", [
@@ -553,33 +1098,131 @@ function formatChecks(checks) {
553
1098
  const sym = checkSymbol(check);
554
1099
  const pending = check.status !== "COMPLETED" ? " (pending)" : "";
555
1100
  lines.push(` ${sym} ${check.name}${pending}`);
556
- if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) {
557
- const desc = check.description.trim();
558
- const url = check.detailsUrl.trim();
559
- if (desc) lines.push(` Details: ${desc}`);
560
- if (url) lines.push(` URL: ${url}`);
561
- if (!desc && !url) lines.push(` Details: (no details available)`);
562
- }
563
1101
  }
564
1102
  return lines.join("\n") + "\n";
565
1103
  }
566
1104
  function sleep(ms) {
567
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1105
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
1106
+ }
1107
+ function isSonarCheck(check) {
1108
+ try {
1109
+ const hostname = new URL(check.detailsUrl).hostname;
1110
+ return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
1111
+ } catch {
1112
+ return false;
1113
+ }
568
1114
  }
569
1115
  function formatFailedChecks(failed) {
570
- const lines = [];
1116
+ const lines = ["FailedChecks:"];
571
1117
  for (const check of failed) {
572
1118
  lines.push(` \u2717 ${check.name}`);
573
1119
  const desc = check.description.trim();
574
1120
  const url = check.detailsUrl.trim();
575
1121
  if (desc) lines.push(` Details: ${desc}`);
576
- if (url) lines.push(` URL: ${url}`);
1122
+ if (url && (isSonarCheck(check) || !desc)) lines.push(` URL: ${url}`);
577
1123
  if (!desc && !url) lines.push(` Details: (no details available)`);
578
1124
  }
579
1125
  return lines.join("\n") + "\n";
580
1126
  }
1127
+ function formatGateViolation(violation) {
1128
+ const parts = [sanitizeText(violation.metricKey)];
1129
+ if (violation.actualValue) parts.push(`actual ${sanitizeText(violation.actualValue)}`);
1130
+ if (violation.comparator && violation.errorThreshold) {
1131
+ parts.push(`${sanitizeText(violation.comparator)} ${sanitizeText(violation.errorThreshold)}`);
1132
+ } else if (violation.errorThreshold) {
1133
+ parts.push(`threshold ${sanitizeText(violation.errorThreshold)}`);
1134
+ }
1135
+ return parts.join(" | ");
1136
+ }
1137
+ function formatLocation(path, line) {
1138
+ if (!path) return void 0;
1139
+ const safePath = sanitizeText(path);
1140
+ if (!line) return safePath;
1141
+ return `${safePath}:${String(line)}`;
1142
+ }
1143
+ function formatRuleWithName(rule, ruleName) {
1144
+ const safeRule = sanitizeText(rule);
1145
+ if (!ruleName) return safeRule;
1146
+ return `${safeRule} (${sanitizeText(ruleName)})`;
1147
+ }
1148
+ function formatSonarIssue(issue) {
1149
+ const lines = [` - ${sanitizeText(issue.message)}`];
1150
+ const location = formatLocation(issue.path, issue.line);
1151
+ if (location) lines.push(` Location: ${location}`);
1152
+ if (issue.severity || issue.type) {
1153
+ const labels = [issue.severity, issue.type].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
1154
+ lines.push(` Classification: ${labels}`);
1155
+ }
1156
+ if (issue.rule) lines.push(` Rule: ${sanitizeText(issue.rule)}`);
1157
+ if (issue.explanation) lines.push(` Explanation: ${sanitizeText(issue.explanation)}`);
1158
+ return lines;
1159
+ }
1160
+ function formatSonarHotspot(hotspot) {
1161
+ const lines = [` - ${sanitizeText(hotspot.message)}`];
1162
+ const location = formatLocation(hotspot.path, hotspot.line);
1163
+ if (location) lines.push(` Location: ${location}`);
1164
+ if (hotspot.status) lines.push(` Status: ${sanitizeText(hotspot.status)}`);
1165
+ if (hotspot.vulnerabilityProbability || hotspot.securityCategory) {
1166
+ const labels = [hotspot.vulnerabilityProbability, hotspot.securityCategory].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
1167
+ lines.push(` Classification: ${labels}`);
1168
+ }
1169
+ if (hotspot.rule) {
1170
+ lines.push(` Rule: ${formatRuleWithName(hotspot.rule, hotspot.ruleName)}`);
1171
+ }
1172
+ if (hotspot.riskDescription) lines.push(` Risk: ${sanitizeText(hotspot.riskDescription)}`);
1173
+ if (hotspot.vulnerabilityDescription) lines.push(` Review: ${sanitizeText(hotspot.vulnerabilityDescription)}`);
1174
+ if (hotspot.fixRecommendations) lines.push(` Fix: ${sanitizeText(hotspot.fixRecommendations)}`);
1175
+ return lines;
1176
+ }
1177
+ function appendGateViolations(lines, gateViolations) {
1178
+ if (gateViolations.length === 0) return;
1179
+ lines.push(" Gate Violations:");
1180
+ for (const violation of gateViolations) {
1181
+ lines.push(` - ${formatGateViolation(violation)}`);
1182
+ }
1183
+ }
1184
+ function appendSonarIssues(lines, issues) {
1185
+ if (issues.length === 0) return;
1186
+ lines.push(" Issues:");
1187
+ for (const issue of issues) {
1188
+ lines.push(...formatSonarIssue(issue));
1189
+ }
1190
+ }
1191
+ function appendSecurityHotspots(lines, securityHotspots) {
1192
+ if (securityHotspots.length === 0) return;
1193
+ lines.push(" Security Hotspots:");
1194
+ for (const hotspot of securityHotspots) {
1195
+ lines.push(...formatSonarHotspot(hotspot));
1196
+ }
1197
+ }
1198
+ function hasSonarFailureDetails(sonarFailures) {
1199
+ return Boolean(sonarFailures.qualityGateStatus) || sonarFailures.gateViolations.length > 0 || sonarFailures.issues.length > 0 || sonarFailures.securityHotspots.length > 0;
1200
+ }
1201
+ function formatSonarFailures(sonarFailures, sonarcloudUrl) {
1202
+ const lines = ["Sonar Failures:"];
1203
+ if (sonarFailures.status === "private") {
1204
+ lines.push(` Note: ${sanitizeText(sonarFailures.privateMessage ?? "SonarCloud project is private.")}`);
1205
+ return lines.join("\n") + "\n";
1206
+ }
1207
+ if (sonarFailures.status === "unavailable") {
1208
+ lines.push(` Note: ${sanitizeText(sonarFailures.unavailableMessage ?? "SonarCloud failure details are unavailable.")}`);
1209
+ if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
1210
+ return lines.join("\n") + "\n";
1211
+ }
1212
+ if (sonarFailures.qualityGateStatus) {
1213
+ lines.push(` Quality Gate: ${sanitizeText(sonarFailures.qualityGateStatus)}`);
1214
+ }
1215
+ appendGateViolations(lines, sonarFailures.gateViolations);
1216
+ appendSonarIssues(lines, sonarFailures.issues);
1217
+ appendSecurityHotspots(lines, sonarFailures.securityHotspots);
1218
+ if (!hasSonarFailureDetails(sonarFailures)) {
1219
+ lines.push(" Note: SonarCloud reported a failure but returned no violation, issue, or hotspot details.");
1220
+ if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
1221
+ }
1222
+ return lines.join("\n") + "\n";
1223
+ }
581
1224
  var POLL_INTERVAL_MS = 1e4;
582
- var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then report pass/fail (exit 1 on failure)").addHelpText(
1225
+ var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then print the normal get-pr-info output").addHelpText(
583
1226
  "after",
584
1227
  `
585
1228
  Check status symbols:
@@ -588,10 +1231,11 @@ Check status symbols:
588
1231
  \u25CF Pending (status: QUEUED or IN_PROGRESS)
589
1232
  \u25CB Skipped (conclusion: SKIPPED or NEUTRAL)
590
1233
 
591
- Failure details are printed beneath each \u2717 check.
1234
+ When checks fail, details are printed in a trailing FailedChecks section.
592
1235
  See docs/git.md for full output reference.`
593
1236
  ).action(async (options) => {
594
1237
  let branch;
1238
+ let pr = null;
595
1239
  try {
596
1240
  branch = getCurrentBranch();
597
1241
  } catch (err) {
@@ -600,54 +1244,31 @@ See docs/git.md for full output reference.`
600
1244
  process.exit(1);
601
1245
  }
602
1246
  if (options.waitFinishChecks) {
603
- let pr2;
604
1247
  while (true) {
605
1248
  try {
606
- pr2 = getPrInfo2(branch);
1249
+ pr = await getPrInfo2(branch);
607
1250
  } catch (err) {
608
1251
  process.stderr.write(`Error: ${err.message}
609
1252
  `);
610
1253
  process.exit(1);
611
1254
  }
612
- if (pr2 === null) {
613
- process.stderr.write(`Error: No pull request found for branch: ${branch}
614
- `);
615
- process.exit(1);
1255
+ if (pr === null) {
1256
+ break;
616
1257
  }
617
- const running = pr2.checks.filter((c) => c.status !== "COMPLETED");
1258
+ const running = pr.checks.filter((c) => c.status !== "COMPLETED");
618
1259
  if (running.length === 0) break;
619
1260
  process.stdout.write(`Waiting for ${running.length} check(s) to complete...
620
1261
  `);
621
1262
  await sleep(POLL_INTERVAL_MS);
622
1263
  }
623
- const failed = pr2.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
624
- if (failed.length === 0) {
625
- if (options.json) {
626
- process.stdout.write(JSON.stringify({ result: "passed" }, null, 2) + "\n");
627
- } else {
628
- process.stdout.write(`All checks passed. \u2713
629
- `);
630
- }
631
- process.exit(0);
632
- } else {
633
- if (options.json) {
634
- process.stdout.write(JSON.stringify({ result: "failed", failed }, null, 2) + "\n");
635
- } else {
636
- process.stdout.write(`${failed.length} check(s) failed:
1264
+ } else {
1265
+ try {
1266
+ pr = await getPrInfo2(branch);
1267
+ } catch (err) {
1268
+ process.stderr.write(`Error: ${err.message}
637
1269
  `);
638
- process.stdout.write(formatFailedChecks(failed));
639
- }
640
1270
  process.exit(1);
641
1271
  }
642
- return;
643
- }
644
- let pr;
645
- try {
646
- pr = getPrInfo2(branch);
647
- } catch (err) {
648
- process.stderr.write(`Error: ${err.message}
649
- `);
650
- process.exit(1);
651
1272
  }
652
1273
  if (pr === null) {
653
1274
  process.stdout.write(`No pull request found for branch: ${branch}
@@ -657,13 +1278,27 @@ See docs/git.md for full output reference.`
657
1278
  if (options.json) {
658
1279
  process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
659
1280
  } else {
1281
+ const failed = pr.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
660
1282
  process.stdout.write(`PR: #${pr.number}
661
1283
  Title: ${pr.title}
662
1284
  State: ${pr.state}
663
1285
  URL: ${pr.url}
664
1286
  `);
1287
+ if (pr.sonarcloudUrl !== void 0) {
1288
+ process.stdout.write(`Sonar: ${pr.sonarcloudUrl}
1289
+ `);
1290
+ const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
1291
+ process.stdout.write(`Sonar New Issues: ${issueStr}
1292
+ `);
1293
+ }
665
1294
  process.stdout.write(formatCheckSummary(pr.checks));
666
1295
  process.stdout.write(formatChecks(pr.checks));
1296
+ if (failed.length > 0) {
1297
+ process.stdout.write(formatFailedChecks(failed));
1298
+ }
1299
+ if (pr.sonarFailures !== void 0) {
1300
+ process.stdout.write(formatSonarFailures(pr.sonarFailures, pr.sonarcloudUrl));
1301
+ }
667
1302
  }
668
1303
  });
669
1304
  var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
@@ -724,7 +1359,7 @@ ${safeBody}`);
724
1359
  }
725
1360
  process.stdout.write(lines.join("\n\n") + "\n");
726
1361
  });
727
- var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
1362
+ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(async () => {
728
1363
  let branch;
729
1364
  try {
730
1365
  branch = getCurrentBranch();
@@ -745,7 +1380,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
745
1380
  }
746
1381
  let pr;
747
1382
  try {
748
- pr = getPrInfo2(branch);
1383
+ pr = await getPrInfo2(branch);
749
1384
  } catch (err) {
750
1385
  process.stderr.write(`Error: ${err.message}
751
1386
  `);
@@ -875,10 +1510,8 @@ minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
875
1510
  var gitCommand = new Command2("git").description("Git workflow commands (some require gh CLI)").addCommand(getPrInfoCmd).addCommand(getPrCommentsCmd).addCommand(finishFeatureCmd).addCommand(publishReleaseCmd);
876
1511
 
877
1512
  // src/commands/getReady.ts
1513
+ import { createInterface as createInterface2 } from "readline";
878
1514
  import { Command as Command3 } from "commander";
879
- import { spawnSync as spawnSync4 } from "child_process";
880
- import { existsSync } from "fs";
881
- import { delimiter, join as join2 } from "path";
882
1515
 
883
1516
  // src/config/githubService.ts
884
1517
  import { spawnSync as spawnSync3 } from "child_process";
@@ -897,14 +1530,14 @@ function run3(cmd, args) {
897
1530
  status: result.status ?? 1
898
1531
  };
899
1532
  }
900
- function listIssues(technique, value) {
1533
+ function listIssues(technique, value, limit = 10) {
901
1534
  const baseArgs = [
902
1535
  "issue",
903
1536
  "list",
904
1537
  "--state",
905
1538
  "open",
906
1539
  "--limit",
907
- "1",
1540
+ String(limit),
908
1541
  "--json",
909
1542
  "number,title,body,url"
910
1543
  ];
@@ -924,11 +1557,7 @@ function listIssues(technique, value) {
924
1557
  if (status !== 0) {
925
1558
  throw new Error(stderr.trim() || "Failed to query GitHub issues. Is `gh` installed and authenticated?");
926
1559
  }
927
- const issues = JSON.parse(stdout);
928
- if (issues.length === 0) {
929
- return null;
930
- }
931
- return issues[0];
1560
+ return JSON.parse(stdout);
932
1561
  }
933
1562
  function postComment(issueNumber, body) {
934
1563
  const { stderr, status } = run3("gh", ["issue", "comment", String(issueNumber), "--body", body]);
@@ -937,40 +1566,222 @@ function postComment(issueNumber, body) {
937
1566
  }
938
1567
  }
939
1568
 
940
- // src/commands/getReady.ts
1569
+ // src/claude/claudeService.ts
1570
+ import { spawn, spawnSync as spawnSync4 } from "child_process";
1571
+ import { createInterface } from "readline";
1572
+ import { existsSync } from "fs";
1573
+ import { delimiter, join as join3 } from "path";
1574
+
1575
+ // src/cli/spawnUtils.ts
1576
+ function truncate(str, max) {
1577
+ return str.length > max ? str.slice(0, max) + "..." : str;
1578
+ }
1579
+ function handleSpawnError(error, toolName) {
1580
+ if (!error) return;
1581
+ const err = error;
1582
+ if (err.code === "ENOENT") {
1583
+ process.stderr.write(`Error: \`${toolName}\` CLI is not installed or not on PATH.
1584
+ `);
1585
+ process.exit(1);
1586
+ }
1587
+ process.stderr.write(`Error: ${err.message}
1588
+ `);
1589
+ process.exit(1);
1590
+ }
1591
+ function handleExitCode(status, toolName) {
1592
+ if (status === null) {
1593
+ process.stderr.write(`Error: ${toolName} terminated abnormally (exit code is null, likely due to a signal).
1594
+ `);
1595
+ process.exit(1);
1596
+ }
1597
+ if (status !== 0) {
1598
+ process.stderr.write(`Error: ${toolName} exited with code ${status}.
1599
+ `);
1600
+ process.exit(status);
1601
+ }
1602
+ }
1603
+
1604
+ // src/claude/claudeService.ts
941
1605
  function resolveCommand(name) {
942
1606
  const pathDirs = (process.env["PATH"] ?? "").split(delimiter);
943
1607
  for (const dir of pathDirs) {
944
- const candidate = join2(dir, name);
1608
+ const candidate = join3(dir, name);
945
1609
  if (existsSync(candidate)) return candidate;
946
1610
  }
947
1611
  return name;
948
1612
  }
949
- function invokeClaudeCode(issue, systemPrompt, yolo) {
950
- const prompt = systemPrompt ? `${systemPrompt}
951
-
952
- ${issue.body}` : issue.body;
1613
+ var MODEL_IDS = {
1614
+ opus: "claude-opus-4-6",
1615
+ sonnet: "claude-sonnet-4-6",
1616
+ haiku: "claude-haiku-4-5-20251001"
1617
+ };
1618
+ function resolveModelOption(opts) {
1619
+ const selected = ["opus", "sonnet", "haiku"].filter((m) => opts[m]);
1620
+ if (selected.length > 1) {
1621
+ process.stderr.write(`Error: --${selected[0]} and --${selected[1]} are mutually exclusive.
1622
+ `);
1623
+ process.exit(1);
1624
+ }
1625
+ return selected.length === 1 ? MODEL_IDS[selected[0]] : void 0;
1626
+ }
1627
+ function invokeClaudeCode(prompt, options = {}) {
1628
+ if (options.verbose) {
1629
+ return invokeClaudeCodeVerbose(prompt, options.yolo ?? false, options.model);
1630
+ }
1631
+ invokeClaudeCodeSync(prompt, options.yolo ?? false, options.model);
1632
+ }
1633
+ function invokeClaudeCodeSync(prompt, yolo, model) {
953
1634
  const claudeBin = resolveCommand("claude");
954
- const args = yolo ? ["--dangerously-skip-permissions", "-p", prompt] : ["-p", prompt];
1635
+ const args = [];
1636
+ if (yolo) args.push("--dangerously-skip-permissions");
1637
+ if (model) args.push("--model", model);
1638
+ args.push("-p", prompt);
955
1639
  const result = spawnSync4(claudeBin, args, { encoding: "utf8", stdio: "inherit" });
956
- if (result.error) {
957
- const err = result.error;
958
- if (err.code === "ENOENT") {
959
- process.stderr.write("Error: `claude` CLI is not installed or not on PATH.\n");
960
- process.exit(1);
1640
+ handleSpawnError(result.error, "claude");
1641
+ handleExitCode(result.status, "Claude Code");
1642
+ }
1643
+ function invokeClaudeCodeVerbose(prompt, yolo, model) {
1644
+ return new Promise((resolve3) => {
1645
+ const claudeBin = resolveCommand("claude");
1646
+ const args = [];
1647
+ if (yolo) args.push("--dangerously-skip-permissions");
1648
+ if (model) args.push("--model", model);
1649
+ args.push("--verbose", "--output-format", "stream-json", "-p", prompt);
1650
+ const child = spawn(claudeBin, args, { stdio: ["inherit", "pipe", "inherit"] });
1651
+ const rl = createInterface({ input: child.stdout });
1652
+ let turnCount = 0;
1653
+ child.on("error", (err) => {
1654
+ handleSpawnError(err, "claude");
1655
+ });
1656
+ rl.on("line", (line) => {
1657
+ try {
1658
+ const event = JSON.parse(line);
1659
+ formatEvent(event, turnCount);
1660
+ if (event["type"] === "assistant") turnCount++;
1661
+ } catch {
1662
+ }
1663
+ });
1664
+ child.on("close", (code) => {
1665
+ handleExitCode(code, "Claude Code");
1666
+ resolve3();
1667
+ });
1668
+ });
1669
+ }
1670
+ function formatEvent(event, turnCount) {
1671
+ const type = event["type"];
1672
+ if (type === "assistant") {
1673
+ const message = event["message"];
1674
+ const content = message?.["content"];
1675
+ if (!content) return;
1676
+ for (const block of content) {
1677
+ if (block["type"] === "tool_use") {
1678
+ const toolName = block["name"];
1679
+ const input = block["input"];
1680
+ const summary = summarizeTool(toolName, input);
1681
+ process.stderr.write(` [step ${turnCount + 1}] ${summary}
1682
+ `);
1683
+ } else if (block["type"] === "text") {
1684
+ const text = block["text"] ?? "";
1685
+ if (text.length > 0) {
1686
+ const preview = text.length > 120 ? text.slice(0, 120) + "..." : text;
1687
+ const firstLine = preview.split("\n")[0];
1688
+ process.stderr.write(` [step ${turnCount + 1}] ${firstLine}
1689
+ `);
1690
+ }
1691
+ }
961
1692
  }
962
- process.stderr.write(`Error: ${err.message}
1693
+ } else if (type === "result") {
1694
+ const result = event["result"];
1695
+ const cost = event["cost_usd"];
1696
+ const duration = event["duration_ms"];
1697
+ const turns = event["num_turns"];
1698
+ process.stderr.write("\n--- Result ---\n");
1699
+ if (cost !== void 0 || duration !== void 0 || turns !== void 0) {
1700
+ const parts = [];
1701
+ if (turns !== void 0) parts.push(`${turns} turns`);
1702
+ if (duration !== void 0) parts.push(`${(duration / 1e3).toFixed(1)}s`);
1703
+ if (cost !== void 0) parts.push(`$${cost.toFixed(4)}`);
1704
+ process.stderr.write(` [info] ${parts.join(" | ")}
963
1705
  `);
964
- process.exit(1);
1706
+ }
1707
+ if (result) {
1708
+ process.stdout.write(result + "\n");
1709
+ }
965
1710
  }
966
- if (result.status !== 0) {
967
- process.stderr.write(`Error: Claude Code exited with code ${result.status ?? "unknown"}.
1711
+ }
1712
+ function summarizeTool(name, input) {
1713
+ if (!input) return `tool: ${name}`;
1714
+ switch (name) {
1715
+ case "Read":
1716
+ return `reading ${input["file_path"] ?? "file"}`;
1717
+ case "Write":
1718
+ return `writing ${input["file_path"] ?? "file"}`;
1719
+ case "Edit":
1720
+ return `editing ${input["file_path"] ?? "file"}`;
1721
+ case "Bash":
1722
+ return `running: ${truncate(String(input["command"] ?? ""), 80)}`;
1723
+ case "Glob":
1724
+ return `searching files: ${input["pattern"] ?? ""}`;
1725
+ case "Grep":
1726
+ return `searching content: ${truncate(String(input["pattern"] ?? ""), 60)}`;
1727
+ case "Agent":
1728
+ return `spawning agent: ${input["description"] ?? name}`;
1729
+ default:
1730
+ return `tool: ${name}`;
1731
+ }
1732
+ }
1733
+
1734
+ // src/codex/codexService.ts
1735
+ import { spawnSync as spawnSync5 } from "child_process";
1736
+ function invokeCodexCode(prompt, options = {}) {
1737
+ if (options.verbose) {
1738
+ process.stderr.write("Warning: --verbose is not supported for Codex and will be ignored.\n");
1739
+ }
1740
+ invokeCodexCodeSync(prompt, options.yolo ?? false, options.model);
1741
+ }
1742
+ function invokeCodexCodeSync(prompt, yolo, model) {
1743
+ const codexBin = resolveCommand("codex");
1744
+ const args = ["exec"];
1745
+ if (yolo) args.push("--dangerously-bypass-approvals-and-sandbox");
1746
+ if (model) args.push("--model", model);
1747
+ args.push(prompt);
1748
+ const result = spawnSync5(codexBin, args, { encoding: "utf8", stdio: "inherit" });
1749
+ handleSpawnError(result.error, "codex");
1750
+ handleExitCode(result.status, "Codex");
1751
+ }
1752
+
1753
+ // src/commands/getReady.ts
1754
+ function writeOverflowHint(output, issues, limit) {
1755
+ if (issues.length === limit) {
1756
+ output.write(`(Showing first ${limit} matching issues \u2014 there may be more. Use --limit to fetch more.)
968
1757
  `);
969
- process.exit(result.status ?? 1);
970
1758
  }
971
1759
  }
972
- var implementNextCommand = new Command3("implement-next").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke Claude Code").option("--json", "Output issue details as JSON").option("--no-claude", "Skip Claude Code invocation after claiming the issue").option("--query-only", "Print issue content and exit without claiming or invoking Claude").option("--yolo", "Launch Claude Code with --dangerously-skip-permissions").action((options) => {
973
- const config = readConfig();
1760
+ function writeIssueList(output, issues, limit) {
1761
+ output.write("\nAvailable issues:\n");
1762
+ for (let i = 0; i < issues.length; i++) {
1763
+ output.write(` [${i + 1}] #${issues[i].number} - ${issues[i].title}
1764
+ `);
1765
+ }
1766
+ writeOverflowHint(output, issues, limit);
1767
+ }
1768
+ async function promptSelection(issues, limit, output) {
1769
+ writeIssueList(output, issues, limit);
1770
+ const rl = createInterface2({ input: process.stdin, output });
1771
+ const answer = await new Promise(
1772
+ (resolve3) => rl.question(`
1773
+ Select issue (1-${issues.length}): `, resolve3)
1774
+ );
1775
+ rl.close();
1776
+ const n = Number.parseInt(answer.trim(), 10);
1777
+ if (Number.isNaN(n) || n < 1 || n > issues.length) {
1778
+ process.stderr.write(`Error: Invalid selection "${answer.trim()}". Enter a number between 1 and ${issues.length}.
1779
+ `);
1780
+ process.exit(1);
1781
+ }
1782
+ return issues[n - 1];
1783
+ }
1784
+ function validateConfig(config) {
974
1785
  if (config.remoteType !== "gh") {
975
1786
  process.stderr.write(
976
1787
  "Error: implement-next is not supported in Azure DevOps mode. Work item discovery is not available in azdo-cli. See docs/azdo-gap.md for details.\n"
@@ -989,24 +1800,58 @@ var implementNextCommand = new Command3("implement-next").description("Find the
989
1800
  );
990
1801
  process.exit(1);
991
1802
  }
1803
+ }
1804
+ async function resolveIssue(issues, options, limit) {
1805
+ const selectionOutput = options.json ? process.stderr : process.stdout;
1806
+ if (issues.length === 0) {
1807
+ process.stdout.write("No issues found matching the configured filter.\n");
1808
+ process.exit(0);
1809
+ }
1810
+ if (issues.length > 1 && options.queryOnly) {
1811
+ writeIssueList(selectionOutput, issues, limit);
1812
+ process.exit(0);
1813
+ }
992
1814
  let issue;
1815
+ if (issues.length === 1) {
1816
+ issue = issues[0];
1817
+ selectionOutput.write(`Issue: #${issue.number}
1818
+ Title: ${issue.title}
1819
+ `);
1820
+ } else if (options.takeFirst) {
1821
+ issue = issues[0];
1822
+ selectionOutput.write(`Selecting issue #${issue.number}: ${issue.title}
1823
+ `);
1824
+ } else {
1825
+ issue = await promptSelection(issues, limit, selectionOutput);
1826
+ selectionOutput.write(`
1827
+ Issue: #${issue.number}
1828
+ Title: ${issue.title}
1829
+ `);
1830
+ }
1831
+ return issue;
1832
+ }
1833
+ var implementNextCommand = new Command3("implement-next").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke the AI code assistant (Claude or Codex)").option("--json", "Output issue details as JSON").option("--no-claude", "Skip all AI invocation (Claude or Codex) after claiming the issue").option("--codex", "Use Codex CLI instead of Claude Code").option("--query-only", "Print issue content and exit without claiming or invoking any AI tools").option("--yolo", "Launch with --dangerously-skip-permissions (Claude) or --dangerously-bypass-approvals-and-sandbox (Codex)").option("--verbose", "Show step-by-step progress summary and final result").option("--opus", "Use claude-opus-4-6").option("--sonnet", "Use claude-sonnet-4-6").option("--haiku", "Use claude-haiku-4-5-20251001").option("--take-first", "When multiple issues match, pick the first without prompting").option("--limit <n>", "Max issues to fetch and display (default: 10)", "10").action(async (options) => {
1834
+ const config = readConfig();
1835
+ validateConfig(config);
1836
+ const limit = Number.parseInt(options.limit, 10);
1837
+ if (Number.isNaN(limit) || limit <= 0) {
1838
+ process.stderr.write(`Error: --limit must be a positive integer (got "${options.limit}").
1839
+ `);
1840
+ process.exit(1);
1841
+ }
1842
+ let issues;
993
1843
  try {
994
- issue = listIssues(config.issueDiscoveryTechnique, config.issueDiscoveryValue);
1844
+ issues = listIssues(config.issueDiscoveryTechnique, config.issueDiscoveryValue, limit);
995
1845
  } catch (err) {
996
1846
  process.stderr.write(`Error: ${err.message}
997
1847
  `);
998
1848
  process.exit(1);
999
1849
  }
1000
- if (issue === null) {
1001
- process.stdout.write("No issues found matching the configured filter.\n");
1002
- process.exit(0);
1003
- }
1850
+ const issue = await resolveIssue(issues, options, limit);
1004
1851
  if (options.json) {
1005
1852
  process.stdout.write(JSON.stringify({ number: issue.number, title: issue.title, body: issue.body, url: issue.url }, null, 2) + "\n");
1006
1853
  } else {
1007
- process.stdout.write(`Issue: #${issue.number}
1008
- Title: ${issue.title}
1009
- URL: ${issue.url}
1854
+ process.stdout.write(`URL: ${issue.url}
1010
1855
 
1011
1856
  ${issue.body}
1012
1857
  `);
@@ -1022,16 +1867,209 @@ ${issue.body}
1022
1867
  process.exit(1);
1023
1868
  }
1024
1869
  if (options.claude !== false) {
1025
- invokeClaudeCode(issue, config.claudeSystemPrompt, options.yolo ?? false);
1870
+ const systemPrompt = config.claudeSystemPrompt ?? DEFAULT_CLAUDE_SYSTEM_PROMPT;
1871
+ const prompt = `Resolving issue #${issue.number}:
1872
+
1873
+ ${systemPrompt}
1874
+
1875
+ ${issue.body}`;
1876
+ if (options.codex) {
1877
+ await invokeCodexCode(prompt, { yolo: options.yolo, verbose: options.verbose });
1878
+ } else {
1879
+ const model = resolveModelOption(options);
1880
+ await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: options.verbose, model });
1881
+ }
1882
+ }
1883
+ });
1884
+
1885
+ // src/commands/execute.ts
1886
+ import { readFileSync as readFileSync3 } from "fs";
1887
+ import { Command as Command4 } from "commander";
1888
+ function readStdin() {
1889
+ return new Promise((resolve3, reject) => {
1890
+ const chunks = [];
1891
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
1892
+ process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8").trim()));
1893
+ process.stdin.on("error", reject);
1894
+ });
1895
+ }
1896
+ async function resolvePrompt(options) {
1897
+ const hasCli = options.prompt !== void 0;
1898
+ const hasFile = options.filePrompt !== void 0;
1899
+ const hasStdin = !process.stdin.isTTY;
1900
+ if (hasCli && (hasFile || hasStdin)) {
1901
+ process.stderr.write("Error: --prompt is mutually exclusive with --file-prompt and stdin.\n");
1902
+ process.exit(1);
1903
+ }
1904
+ if (hasCli) {
1905
+ return options.prompt;
1906
+ }
1907
+ if (hasFile) {
1908
+ const path = options.filePrompt;
1909
+ try {
1910
+ return readFileSync3(path, "utf8");
1911
+ } catch {
1912
+ process.stderr.write(`Error: Cannot read file: ${path}
1913
+ `);
1914
+ process.exit(1);
1915
+ }
1916
+ }
1917
+ if (hasStdin) {
1918
+ const content = await readStdin();
1919
+ if (content.length === 0) {
1920
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1921
+ process.exit(1);
1922
+ }
1923
+ return content;
1924
+ }
1925
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1926
+ process.exit(1);
1927
+ }
1928
+ var executeCommand = new Command4("execute").description("Delegate work to an AI executor").requiredOption("--with <executor>", "Executor to use: claude or codex").option("--prompt <string>", "Prompt to send to the executor").option("--file-prompt <path>", "Path to a file whose content is used as the prompt").option("--silent", "Suppress step-by-step output; show only the final summary").option("--model <string>", "Model identifier to pass to the executor").action(async (options) => {
1929
+ const executor = options.with.toLowerCase();
1930
+ if (executor !== "claude" && executor !== "codex") {
1931
+ process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
1932
+ `);
1933
+ process.exit(1);
1934
+ }
1935
+ const prompt = await resolvePrompt(options);
1936
+ if (executor === "codex") {
1937
+ invokeCodexCode(prompt, { yolo: true, model: options.model });
1938
+ } else {
1939
+ await invokeClaudeCode(prompt, { yolo: true, verbose: !options.silent, model: options.model });
1940
+ }
1941
+ });
1942
+
1943
+ // src/commands/executePrompt.ts
1944
+ import { Command as Command5 } from "commander";
1945
+ var PUSH_INSTRUCTION = "Once all changes are complete, stage every modified file, create a single commit with a clear and descriptive commit message that summarises what was fixed, and push the branch to the remote.";
1946
+ function withPush(prompt, push) {
1947
+ return push ? `${prompt}
1948
+
1949
+ ${PUSH_INSTRUCTION}` : prompt;
1950
+ }
1951
+ function formatPrInfoContext(pr) {
1952
+ return JSON.stringify(pr, null, 2);
1953
+ }
1954
+ function addAiOptions(cmd) {
1955
+ return cmd.option("--codex", "Use Codex CLI instead of Claude Code").option("--verbose", "Show step-by-step progress (Claude only; ignored for Codex)").option("--push", "Append instruction to commit and push changes after the AI finishes").option("--opus", "Use claude-opus-4-6 (Claude only)").option("--sonnet", "Use claude-sonnet-4-6 (Claude only)").option("--haiku", "Use claude-haiku-4-5-20251001 (Claude only)");
1956
+ }
1957
+ var executeSonarCmd = addAiOptions(
1958
+ new Command5("sonar").description(
1959
+ "Check the current branch for a SonarCloud analysis and invoke the AI with the Sonar prompt and analysis URL"
1960
+ )
1961
+ ).action(async (options) => {
1962
+ let branch;
1963
+ try {
1964
+ branch = getCurrentBranch();
1965
+ } catch (err) {
1966
+ process.stderr.write(`Error: ${err.message}
1967
+ `);
1968
+ process.exit(1);
1969
+ }
1970
+ let pr;
1971
+ try {
1972
+ pr = await getPrInfo2(branch);
1973
+ } catch (err) {
1974
+ process.stderr.write(`Error: ${err.message}
1975
+ `);
1976
+ process.exit(1);
1977
+ }
1978
+ if (pr === null) {
1979
+ process.stderr.write(`Error: No pull request found for branch: ${branch}
1980
+ `);
1981
+ process.exit(1);
1982
+ }
1983
+ if (!pr.sonarcloudUrl) {
1984
+ process.stderr.write(
1985
+ `Error: No SonarCloud analysis found for PR #${pr.number}. Ensure a SonarCloud check is configured on this repository.
1986
+ `
1987
+ );
1988
+ process.exit(1);
1989
+ }
1990
+ const config = readConfig();
1991
+ const sonarPromptText = config.prompts?.sonar ?? DEFAULT_SONAR_PROMPT;
1992
+ const fullPrompt = withPush(
1993
+ `${sonarPromptText}
1994
+
1995
+ SonarCloud analysis URL: ${pr.sonarcloudUrl}
1996
+
1997
+ Current PR context from automata git get-pr-info --json:
1998
+ ${formatPrInfoContext(pr)}`,
1999
+ options.push
2000
+ );
2001
+ if (options.codex) {
2002
+ invokeCodexCode(fullPrompt, { yolo: true });
2003
+ } else {
2004
+ const model = resolveModelOption(options);
2005
+ await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
2006
+ }
2007
+ });
2008
+ function formatComments(comments) {
2009
+ return comments.map((c) => {
2010
+ const loc = c.line === null ? `${c.path}:(file)` : `${c.path}:${String(c.line)}`;
2011
+ return `[${c.author}] on ${loc}
2012
+ ${c.body}`;
2013
+ }).join("\n\n");
2014
+ }
2015
+ var executeFixCommentsCmd = addAiOptions(
2016
+ new Command5("fix-comments").description(
2017
+ "Fetch open review comments on the current PR and invoke the AI with the Fix-Comments prompt"
2018
+ )
2019
+ ).action(async (options) => {
2020
+ const result = resolveCurrentBranchComments();
2021
+ if (!result.ok) {
2022
+ if (result.kind === "error") {
2023
+ process.stderr.write(`Error: ${result.message}
2024
+ `);
2025
+ process.exit(1);
2026
+ }
2027
+ if (result.kind === "unsupported") {
2028
+ process.stderr.write(
2029
+ `Error: fix-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
2030
+ `
2031
+ );
2032
+ process.exit(1);
2033
+ }
2034
+ process.stderr.write(`Error: No pull request found for branch: ${result.branch}
2035
+ `);
2036
+ process.exit(1);
2037
+ }
2038
+ const { comments } = result;
2039
+ if (comments.length === 0) {
2040
+ process.stderr.write(`Error: No open review comments found on the pull request.
2041
+ `);
2042
+ process.exit(1);
2043
+ }
2044
+ process.stdout.write(`Found ${String(comments.length)} open review comment${comments.length === 1 ? "" : "s"} on PR. Invoking AI\u2026
2045
+ `);
2046
+ const config = readConfig();
2047
+ const promptText = config.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT;
2048
+ const fullPrompt = withPush(
2049
+ `${promptText}
2050
+
2051
+ Open review comments:
2052
+
2053
+ ${formatComments(comments)}`,
2054
+ options.push
2055
+ );
2056
+ if (options.codex) {
2057
+ invokeCodexCode(fullPrompt, { yolo: true });
2058
+ } else {
2059
+ const model = resolveModelOption(options);
2060
+ await invokeClaudeCode(fullPrompt, { yolo: true, verbose: options.verbose, model });
1026
2061
  }
1027
2062
  });
2063
+ var executePromptCommand = new Command5("execute-prompt").description("Execute a configured custom prompt using an AI assistant").addCommand(executeSonarCmd).addCommand(executeFixCommentsCmd);
1028
2064
 
1029
2065
  // src/index.ts
1030
- var program = new Command4();
2066
+ var program = new Command6();
1031
2067
  program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
1032
2068
  program.addCommand(configCommand);
1033
2069
  program.addCommand(gitCommand);
1034
2070
  program.addCommand(implementNextCommand);
2071
+ program.addCommand(executeCommand);
2072
+ program.addCommand(executePromptCommand);
1035
2073
  program.showHelpAfterError();
1036
2074
  program.parse();
1037
2075
  if (process.argv.length <= 2) {