automata-cli 0.3.0-develop.77 → 0.3.0-develop.93

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