automata-cli 0.3.0-develop.78 → 0.3.0-develop.96

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