automata-cli 0.6.0-develop.225 → 0.6.0-develop.230
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.
|
@@ -130,8 +130,8 @@ function ConfigWizard() {
|
|
|
130
130
|
const initialTechIndex = TECHNIQUE_OPTIONS.findIndex((o) => o.value === existing.issueDiscoveryTechnique);
|
|
131
131
|
const [screen, setScreen] = useState("main");
|
|
132
132
|
const [mainMenuIndex, setMainMenuIndex] = useState(0);
|
|
133
|
-
const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(initialRemoteIndex
|
|
134
|
-
const [selectedTechIndex, setSelectedTechIndex] = useState(initialTechIndex
|
|
133
|
+
const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(Math.max(initialRemoteIndex, 0));
|
|
134
|
+
const [selectedTechIndex, setSelectedTechIndex] = useState(Math.max(initialTechIndex, 0));
|
|
135
135
|
const [discoveryValue, setDiscoveryValue] = useState(existing.issueDiscoveryValue ?? "");
|
|
136
136
|
const [systemPrompt, setSystemPrompt] = useState(existing.claudeSystemPrompt ?? "");
|
|
137
137
|
const [promptsMenuIndex, setPromptsMenuIndex] = useState(0);
|
package/dist/index.js
CHANGED
|
@@ -194,7 +194,7 @@ var configCommand = new Command("config").description("Configure automata settin
|
|
|
194
194
|
const [{ render }, React, { ConfigWizard }] = await Promise.all([
|
|
195
195
|
import("ink"),
|
|
196
196
|
import("react"),
|
|
197
|
-
import("./ConfigWizard-
|
|
197
|
+
import("./ConfigWizard-J67TY22Z.js")
|
|
198
198
|
]);
|
|
199
199
|
const { waitUntilExit } = render(React.createElement(ConfigWizard));
|
|
200
200
|
await waitUntilExit();
|
|
@@ -276,15 +276,15 @@ function parseOwnerRepo() {
|
|
|
276
276
|
const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
|
|
277
277
|
if (status !== 0) return null;
|
|
278
278
|
const url = stdout.trim();
|
|
279
|
-
const https =
|
|
279
|
+
const https = /github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/.exec(url);
|
|
280
280
|
if (https) return https[1];
|
|
281
|
-
const ssh =
|
|
281
|
+
const ssh = /github\.com:([^/]+\/[^/]+?)(?:\.git)?$/.exec(url);
|
|
282
282
|
if (ssh) return ssh[1];
|
|
283
283
|
return null;
|
|
284
284
|
}
|
|
285
285
|
function extractLastMarkdownUrl(markdown) {
|
|
286
286
|
const matches = [...markdown.matchAll(/\]\((https?:\/\/[^)]+)\)/g)];
|
|
287
|
-
return matches.
|
|
287
|
+
return matches.at(-1)?.[1] ?? null;
|
|
288
288
|
}
|
|
289
289
|
function fetchCheckRunOutputs(ownerRepo, sha) {
|
|
290
290
|
const { stdout, status } = run2("gh", [
|
|
@@ -366,12 +366,14 @@ function resolveSonarPath(componentKey, components) {
|
|
|
366
366
|
const path = componentKey.slice(separatorIndex + 1).trim();
|
|
367
367
|
return path || void 0;
|
|
368
368
|
}
|
|
369
|
-
function
|
|
369
|
+
function collectSonarComponents(data, components) {
|
|
370
370
|
for (const component of data.components ?? []) {
|
|
371
371
|
if (component.key && component.path) {
|
|
372
372
|
components.set(component.key, component.path);
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
+
}
|
|
376
|
+
function collectSonarRules(data, rules) {
|
|
375
377
|
for (const rule of data.rules ?? []) {
|
|
376
378
|
if (!rule.key) continue;
|
|
377
379
|
const explanation = normalizeText(rule.htmlDesc) ?? normalizeText(rule.htmlNote) ?? normalizeText(rule.name);
|
|
@@ -379,6 +381,10 @@ function mapSonarIssuesPage(data, targetIssues, components, rules) {
|
|
|
379
381
|
rules.set(rule.key, explanation);
|
|
380
382
|
}
|
|
381
383
|
}
|
|
384
|
+
}
|
|
385
|
+
function mapSonarIssuesPage(data, targetIssues, components, rules) {
|
|
386
|
+
collectSonarComponents(data, components);
|
|
387
|
+
collectSonarRules(data, rules);
|
|
382
388
|
for (const issue of data.issues ?? []) {
|
|
383
389
|
if (!issue.key || !issue.message) continue;
|
|
384
390
|
const ruleKey = issue.rule;
|
|
@@ -396,11 +402,7 @@ function mapSonarIssuesPage(data, targetIssues, components, rules) {
|
|
|
396
402
|
}
|
|
397
403
|
}
|
|
398
404
|
function mapSonarHotspotsPage(data, targetHotspots, components) {
|
|
399
|
-
|
|
400
|
-
if (component.key && component.path) {
|
|
401
|
-
components.set(component.key, component.path);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
405
|
+
collectSonarComponents(data, components);
|
|
404
406
|
targetHotspots.push(...data.hotspots ?? []);
|
|
405
407
|
}
|
|
406
408
|
function mapSonarHotspot(hotspot, components, detail) {
|
|
@@ -625,6 +627,32 @@ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
|
|
|
625
627
|
issueNote
|
|
626
628
|
};
|
|
627
629
|
}
|
|
630
|
+
async function describeSonarCheck(sonarCheck, prNumber) {
|
|
631
|
+
const sonarcloudUrl = resolveSonarPullRequestUrl(sonarCheck.detailsUrl, prNumber);
|
|
632
|
+
const projectKey = extractSonarProjectKey(sonarcloudUrl);
|
|
633
|
+
if (!projectKey) {
|
|
634
|
+
return {
|
|
635
|
+
sonarcloudUrl,
|
|
636
|
+
sonarNewIssues: null,
|
|
637
|
+
sonarNewIssuesNote: "Could not determine the SonarCloud project key from the check URL."
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
|
|
641
|
+
const sonarSummary = await fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl);
|
|
642
|
+
return {
|
|
643
|
+
sonarcloudUrl,
|
|
644
|
+
sonarNewIssues: sonarSummary.issueTotal,
|
|
645
|
+
sonarNewIssuesNote: sonarSummary.issueNote,
|
|
646
|
+
sonarFailures: sonarSummary.summary
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const sonarNewIssuesResult = await fetchSonarNewIssues(projectKey, prNumber, sonarcloudUrl);
|
|
650
|
+
return {
|
|
651
|
+
sonarcloudUrl,
|
|
652
|
+
sonarNewIssues: sonarNewIssuesResult.total,
|
|
653
|
+
sonarNewIssuesNote: sonarNewIssuesResult.note
|
|
654
|
+
};
|
|
655
|
+
}
|
|
628
656
|
async function getPrInfoGh(branch) {
|
|
629
657
|
const { stdout, stderr, status } = run2("gh", [
|
|
630
658
|
"pr",
|
|
@@ -660,37 +688,19 @@ async function getPrInfoGh(branch) {
|
|
|
660
688
|
};
|
|
661
689
|
});
|
|
662
690
|
const sonarCheck = checks.find((c) => isSonarUrl(c.detailsUrl));
|
|
663
|
-
|
|
664
|
-
let sonarNewIssues;
|
|
665
|
-
let sonarNewIssuesNote;
|
|
666
|
-
let sonarFailures;
|
|
667
|
-
if (sonarCheck) {
|
|
668
|
-
sonarcloudUrl = resolveSonarPullRequestUrl(sonarCheck.detailsUrl, raw.number);
|
|
669
|
-
const projectKey = extractSonarProjectKey(sonarcloudUrl);
|
|
670
|
-
if (projectKey) {
|
|
671
|
-
if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
|
|
672
|
-
const sonarSummary = await fetchSonarFailureSummary(projectKey, raw.number, sonarcloudUrl);
|
|
673
|
-
sonarFailures = sonarSummary.summary;
|
|
674
|
-
sonarNewIssues = sonarSummary.issueTotal;
|
|
675
|
-
sonarNewIssuesNote = sonarSummary.issueNote;
|
|
676
|
-
} else {
|
|
677
|
-
const sonarNewIssuesResult = await fetchSonarNewIssues(projectKey, raw.number, sonarcloudUrl);
|
|
678
|
-
sonarNewIssues = sonarNewIssuesResult.total;
|
|
679
|
-
sonarNewIssuesNote = sonarNewIssuesResult.note;
|
|
680
|
-
}
|
|
681
|
-
} else {
|
|
682
|
-
sonarNewIssues = null;
|
|
683
|
-
sonarNewIssuesNote = "Could not determine the SonarCloud project key from the check URL.";
|
|
684
|
-
}
|
|
685
|
-
}
|
|
691
|
+
const sonar = sonarCheck === void 0 ? void 0 : await describeSonarCheck(sonarCheck, raw.number);
|
|
686
692
|
return {
|
|
687
693
|
number: raw.number,
|
|
688
694
|
title: raw.title,
|
|
689
695
|
state: raw.state,
|
|
690
696
|
url: raw.url,
|
|
691
697
|
checks,
|
|
692
|
-
...
|
|
693
|
-
|
|
698
|
+
...sonar === void 0 ? {} : {
|
|
699
|
+
sonarcloudUrl: sonar.sonarcloudUrl,
|
|
700
|
+
sonarNewIssues: sonar.sonarNewIssues,
|
|
701
|
+
sonarNewIssuesNote: sonar.sonarNewIssuesNote
|
|
702
|
+
},
|
|
703
|
+
...sonar?.sonarFailures === void 0 ? {} : { sonarFailures: sonar.sonarFailures }
|
|
694
704
|
};
|
|
695
705
|
}
|
|
696
706
|
async function getPrInfo2(branch) {
|
|
@@ -1059,7 +1069,6 @@ When checks fail, details are printed in a trailing FailedChecks section.
|
|
|
1059
1069
|
See docs/git.md for full output reference.`
|
|
1060
1070
|
).action(async (options) => {
|
|
1061
1071
|
let branch;
|
|
1062
|
-
let pr = null;
|
|
1063
1072
|
try {
|
|
1064
1073
|
branch = getCurrentBranch();
|
|
1065
1074
|
} catch (err) {
|
|
@@ -1067,33 +1076,7 @@ See docs/git.md for full output reference.`
|
|
|
1067
1076
|
`);
|
|
1068
1077
|
process.exit(1);
|
|
1069
1078
|
}
|
|
1070
|
-
|
|
1071
|
-
while (true) {
|
|
1072
|
-
try {
|
|
1073
|
-
pr = await getPrInfo2(branch);
|
|
1074
|
-
} catch (err) {
|
|
1075
|
-
process.stderr.write(`Error: ${err.message}
|
|
1076
|
-
`);
|
|
1077
|
-
process.exit(1);
|
|
1078
|
-
}
|
|
1079
|
-
if (pr === null) {
|
|
1080
|
-
break;
|
|
1081
|
-
}
|
|
1082
|
-
const running = pr.checks.filter((c) => c.status !== "COMPLETED");
|
|
1083
|
-
if (running.length === 0) break;
|
|
1084
|
-
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
1085
|
-
`);
|
|
1086
|
-
await sleep(POLL_INTERVAL_MS);
|
|
1087
|
-
}
|
|
1088
|
-
} else {
|
|
1089
|
-
try {
|
|
1090
|
-
pr = await getPrInfo2(branch);
|
|
1091
|
-
} catch (err) {
|
|
1092
|
-
process.stderr.write(`Error: ${err.message}
|
|
1093
|
-
`);
|
|
1094
|
-
process.exit(1);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1079
|
+
const pr = options.waitFinishChecks ? await pollUntilChecksComplete(branch) : await readPrInfoOrExit(branch);
|
|
1097
1080
|
if (pr === null) {
|
|
1098
1081
|
process.stdout.write(`No pull request found for branch: ${branch}
|
|
1099
1082
|
`);
|
|
@@ -1102,36 +1085,61 @@ See docs/git.md for full output reference.`
|
|
|
1102
1085
|
if (options.json) {
|
|
1103
1086
|
process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
|
|
1104
1087
|
} else {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1088
|
+
printPrStatus(pr);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
async function readPrInfoOrExit(branch) {
|
|
1092
|
+
try {
|
|
1093
|
+
return await getPrInfo2(branch);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
process.stderr.write(`Error: ${err.message}
|
|
1110
1096
|
`);
|
|
1111
|
-
|
|
1112
|
-
|
|
1097
|
+
process.exit(1);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
async function pollUntilChecksComplete(branch) {
|
|
1101
|
+
for (; ; ) {
|
|
1102
|
+
const pr = await readPrInfoOrExit(branch);
|
|
1103
|
+
if (pr === null) return null;
|
|
1104
|
+
const running = pr.checks.filter((c) => c.status !== "COMPLETED");
|
|
1105
|
+
if (running.length === 0) return pr;
|
|
1106
|
+
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
1113
1107
|
`);
|
|
1114
|
-
|
|
1115
|
-
|
|
1108
|
+
await sleep(POLL_INTERVAL_MS);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function printSonarStatus(pr) {
|
|
1112
|
+
if (pr.sonarcloudUrl === void 0) return;
|
|
1113
|
+
process.stdout.write(`Sonar: ${pr.sonarcloudUrl}
|
|
1116
1114
|
`);
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1115
|
+
const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
|
|
1116
|
+
process.stdout.write(`Sonar New Issues: ${issueStr}
|
|
1117
|
+
`);
|
|
1118
|
+
const failureNote = sonarFailureNote(pr.sonarFailures);
|
|
1119
|
+
if (pr.sonarNewIssuesNote && pr.sonarNewIssuesNote !== failureNote) {
|
|
1120
|
+
process.stdout.write(`Sonar Note: ${sanitizeText(pr.sonarNewIssuesNote)}
|
|
1120
1121
|
`);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
process.stdout.write(formatCheckSummary(pr.checks));
|
|
1124
|
-
process.stdout.write(formatChecks(pr.checks));
|
|
1125
|
-
if (failed.length > 0) {
|
|
1126
|
-
process.stdout.write(formatFailedChecks(failed));
|
|
1127
|
-
}
|
|
1128
|
-
if (pr.sonarFailures !== void 0) {
|
|
1129
|
-
process.stdout.write(formatSonarFailures(pr.sonarFailures, pr.sonarcloudUrl));
|
|
1130
|
-
}
|
|
1131
1122
|
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1123
|
+
}
|
|
1124
|
+
function printPrStatus(pr) {
|
|
1125
|
+
const failed = pr.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
1126
|
+
process.stdout.write(`PR: #${pr.number}
|
|
1127
|
+
Title: ${pr.title}
|
|
1128
|
+
State: ${pr.state}
|
|
1129
|
+
URL: ${pr.url}
|
|
1130
|
+
`);
|
|
1131
|
+
printSonarStatus(pr);
|
|
1132
|
+
process.stdout.write(formatCheckSummary(pr.checks));
|
|
1133
|
+
process.stdout.write(formatChecks(pr.checks));
|
|
1134
|
+
if (failed.length > 0) {
|
|
1135
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
1136
|
+
}
|
|
1137
|
+
if (pr.sonarFailures !== void 0) {
|
|
1138
|
+
process.stdout.write(formatSonarFailures(pr.sonarFailures, pr.sonarcloudUrl));
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
var ANSI_ESCAPE_RE = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
1142
|
+
var CONTROL_CHARS_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
1135
1143
|
function sanitizeText(text) {
|
|
1136
1144
|
return text.replace(ANSI_ESCAPE_RE, "").replace(CONTROL_CHARS_RE, "");
|
|
1137
1145
|
}
|
|
@@ -1666,65 +1674,78 @@ function invokeClaudeCodeVerbose(prompt, yolo, model) {
|
|
|
1666
1674
|
});
|
|
1667
1675
|
});
|
|
1668
1676
|
}
|
|
1677
|
+
function formatAssistantEvent(event, turnCount) {
|
|
1678
|
+
const message = event["message"];
|
|
1679
|
+
const content = message?.["content"];
|
|
1680
|
+
if (!content) return;
|
|
1681
|
+
for (const block of content) {
|
|
1682
|
+
const line = formatContentBlock(block);
|
|
1683
|
+
if (line !== null) {
|
|
1684
|
+
process.stderr.write(` [step ${turnCount + 1}] ${line}
|
|
1685
|
+
`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
function formatContentBlock(block) {
|
|
1690
|
+
if (block["type"] === "tool_use") {
|
|
1691
|
+
const toolName = block["name"];
|
|
1692
|
+
const input = block["input"];
|
|
1693
|
+
return summarizeTool(toolName, input);
|
|
1694
|
+
}
|
|
1695
|
+
if (block["type"] === "text") {
|
|
1696
|
+
const text = block["text"] ?? "";
|
|
1697
|
+
if (text.length === 0) return null;
|
|
1698
|
+
const preview = text.length > 120 ? text.slice(0, 120) + "..." : text;
|
|
1699
|
+
return preview.split("\n")[0] ?? null;
|
|
1700
|
+
}
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
function formatResultEvent(event) {
|
|
1704
|
+
const result = event["result"];
|
|
1705
|
+
const cost = event["cost_usd"];
|
|
1706
|
+
const duration = event["duration_ms"];
|
|
1707
|
+
const turns = event["num_turns"];
|
|
1708
|
+
process.stderr.write("\n--- Result ---\n");
|
|
1709
|
+
const parts = [];
|
|
1710
|
+
if (turns !== void 0) parts.push(`${turns} turns`);
|
|
1711
|
+
if (duration !== void 0) parts.push(`${(duration / 1e3).toFixed(1)}s`);
|
|
1712
|
+
if (cost !== void 0) parts.push(`$${cost.toFixed(4)}`);
|
|
1713
|
+
if (parts.length > 0) {
|
|
1714
|
+
process.stderr.write(` [info] ${parts.join(" | ")}
|
|
1715
|
+
`);
|
|
1716
|
+
}
|
|
1717
|
+
if (result) {
|
|
1718
|
+
process.stdout.write(result + "\n");
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1669
1721
|
function formatEvent(event, turnCount) {
|
|
1670
1722
|
const type = event["type"];
|
|
1671
1723
|
if (type === "assistant") {
|
|
1672
|
-
|
|
1673
|
-
const content = message?.["content"];
|
|
1674
|
-
if (!content) return;
|
|
1675
|
-
for (const block of content) {
|
|
1676
|
-
if (block["type"] === "tool_use") {
|
|
1677
|
-
const toolName = block["name"];
|
|
1678
|
-
const input = block["input"];
|
|
1679
|
-
const summary = summarizeTool(toolName, input);
|
|
1680
|
-
process.stderr.write(` [step ${turnCount + 1}] ${summary}
|
|
1681
|
-
`);
|
|
1682
|
-
} else if (block["type"] === "text") {
|
|
1683
|
-
const text = block["text"] ?? "";
|
|
1684
|
-
if (text.length > 0) {
|
|
1685
|
-
const preview = text.length > 120 ? text.slice(0, 120) + "..." : text;
|
|
1686
|
-
const firstLine = preview.split("\n")[0];
|
|
1687
|
-
process.stderr.write(` [step ${turnCount + 1}] ${firstLine}
|
|
1688
|
-
`);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1724
|
+
formatAssistantEvent(event, turnCount);
|
|
1692
1725
|
} else if (type === "result") {
|
|
1693
|
-
|
|
1694
|
-
const cost = event["cost_usd"];
|
|
1695
|
-
const duration = event["duration_ms"];
|
|
1696
|
-
const turns = event["num_turns"];
|
|
1697
|
-
process.stderr.write("\n--- Result ---\n");
|
|
1698
|
-
if (cost !== void 0 || duration !== void 0 || turns !== void 0) {
|
|
1699
|
-
const parts = [];
|
|
1700
|
-
if (turns !== void 0) parts.push(`${turns} turns`);
|
|
1701
|
-
if (duration !== void 0) parts.push(`${(duration / 1e3).toFixed(1)}s`);
|
|
1702
|
-
if (cost !== void 0) parts.push(`$${cost.toFixed(4)}`);
|
|
1703
|
-
process.stderr.write(` [info] ${parts.join(" | ")}
|
|
1704
|
-
`);
|
|
1705
|
-
}
|
|
1706
|
-
if (result) {
|
|
1707
|
-
process.stdout.write(result + "\n");
|
|
1708
|
-
}
|
|
1726
|
+
formatResultEvent(event);
|
|
1709
1727
|
}
|
|
1710
1728
|
}
|
|
1729
|
+
function asText(value, fallback) {
|
|
1730
|
+
return typeof value === "string" ? value : fallback;
|
|
1731
|
+
}
|
|
1711
1732
|
function summarizeTool(name, input) {
|
|
1712
1733
|
if (!input) return `tool: ${name}`;
|
|
1713
1734
|
switch (name) {
|
|
1714
1735
|
case "Read":
|
|
1715
|
-
return `reading ${input["file_path"]
|
|
1736
|
+
return `reading ${asText(input["file_path"], "file")}`;
|
|
1716
1737
|
case "Write":
|
|
1717
|
-
return `writing ${input["file_path"]
|
|
1738
|
+
return `writing ${asText(input["file_path"], "file")}`;
|
|
1718
1739
|
case "Edit":
|
|
1719
|
-
return `editing ${input["file_path"]
|
|
1740
|
+
return `editing ${asText(input["file_path"], "file")}`;
|
|
1720
1741
|
case "Bash":
|
|
1721
|
-
return `running: ${truncate(
|
|
1742
|
+
return `running: ${truncate(asText(input["command"], ""), 80)}`;
|
|
1722
1743
|
case "Glob":
|
|
1723
|
-
return `searching files: ${input["pattern"]
|
|
1744
|
+
return `searching files: ${asText(input["pattern"], "")}`;
|
|
1724
1745
|
case "Grep":
|
|
1725
|
-
return `searching content: ${truncate(
|
|
1746
|
+
return `searching content: ${truncate(asText(input["pattern"], ""), 60)}`;
|
|
1726
1747
|
case "Agent":
|
|
1727
|
-
return `spawning agent: ${input["description"]
|
|
1748
|
+
return `spawning agent: ${asText(input["description"], name)}`;
|
|
1728
1749
|
default:
|
|
1729
1750
|
return `tool: ${name}`;
|
|
1730
1751
|
}
|
|
@@ -1889,16 +1910,7 @@ ${issue.body}
|
|
|
1889
1910
|
if (options.queryOnly) {
|
|
1890
1911
|
process.exit(0);
|
|
1891
1912
|
}
|
|
1892
|
-
|
|
1893
|
-
if (options.claude !== false) {
|
|
1894
|
-
const requestedExecutor = options.with.toLowerCase();
|
|
1895
|
-
if (requestedExecutor !== "claude" && requestedExecutor !== "codex") {
|
|
1896
|
-
process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
|
|
1897
|
-
`);
|
|
1898
|
-
process.exit(1);
|
|
1899
|
-
}
|
|
1900
|
-
executor = requestedExecutor;
|
|
1901
|
-
}
|
|
1913
|
+
const executor = options.claude === false ? void 0 : resolveExecutor(options.with);
|
|
1902
1914
|
let commentUrl;
|
|
1903
1915
|
try {
|
|
1904
1916
|
commentUrl = postComment(issue.number, "working");
|
|
@@ -1923,37 +1935,49 @@ ${issue.body}`;
|
|
|
1923
1935
|
await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: !options.silent, model: options.model });
|
|
1924
1936
|
}
|
|
1925
1937
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
} catch (err) {
|
|
1933
|
-
process.stderr.write(`Warning: could not update issue comment: ${err.message}
|
|
1934
|
-
`);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
try {
|
|
1938
|
-
addClosesRefToPr(pr.number, issue.number);
|
|
1939
|
-
} catch (err) {
|
|
1940
|
-
process.stderr.write(`Warning: could not add Closes #${issue.number} to PR: ${err.message}
|
|
1938
|
+
linkPrToIssue(issue.number, commentUrl, options.askCopilotReview === true);
|
|
1939
|
+
});
|
|
1940
|
+
function resolveExecutor(requested) {
|
|
1941
|
+
const executor = requested.toLowerCase();
|
|
1942
|
+
if (executor !== "claude" && executor !== "codex") {
|
|
1943
|
+
process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${requested}'.
|
|
1941
1944
|
`);
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1945
|
+
process.exit(1);
|
|
1946
|
+
}
|
|
1947
|
+
return executor;
|
|
1948
|
+
}
|
|
1949
|
+
function warnOnFailure(what, action) {
|
|
1950
|
+
try {
|
|
1951
|
+
action();
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
process.stderr.write(`Warning: could not ${what}: ${err.message}
|
|
1948
1954
|
`);
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function linkPrToIssue(issueNumber, commentUrl, askCopilotReview) {
|
|
1958
|
+
let pr;
|
|
1959
|
+
try {
|
|
1960
|
+
pr = getCurrentBranchPr();
|
|
1952
1961
|
} catch (err) {
|
|
1953
1962
|
process.stderr.write(`Warning: could not detect current branch PR: ${err.message}
|
|
1954
1963
|
`);
|
|
1964
|
+
return;
|
|
1955
1965
|
}
|
|
1956
|
-
|
|
1966
|
+
if (!pr) return;
|
|
1967
|
+
if (commentUrl) {
|
|
1968
|
+
warnOnFailure("update issue comment", () => {
|
|
1969
|
+
editComment(commentUrl, `Working on this in PR #${pr.number} \u2014 ${pr.url}`);
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
warnOnFailure(`add Closes #${String(issueNumber)} to PR`, () => {
|
|
1973
|
+
addClosesRefToPr(pr.number, issueNumber);
|
|
1974
|
+
});
|
|
1975
|
+
if (askCopilotReview) {
|
|
1976
|
+
warnOnFailure("request Copilot review", () => {
|
|
1977
|
+
addCopilotReviewer(pr.number);
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1957
1981
|
|
|
1958
1982
|
// src/commands/execute.ts
|
|
1959
1983
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -2132,7 +2156,7 @@ function pluralSuffix(count) {
|
|
|
2132
2156
|
function addAiOptions(cmd) {
|
|
2133
2157
|
return cmd.requiredOption("--with <executor>", "Executor to use: claude or codex").option("--model <string>", "Model identifier to pass to the executor").option("--silent", "Suppress step-by-step Claude output; show only the final summary").option("--push", "Append instruction to commit and push changes after the AI finishes");
|
|
2134
2158
|
}
|
|
2135
|
-
function
|
|
2159
|
+
function resolveExecutor2(withOption) {
|
|
2136
2160
|
const executor = withOption.toLowerCase();
|
|
2137
2161
|
if (executor !== "claude" && executor !== "codex") {
|
|
2138
2162
|
process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${withOption}'.
|
|
@@ -2153,7 +2177,7 @@ var executeSonarCmd = addAiOptions(
|
|
|
2153
2177
|
"Check the current branch for a SonarCloud analysis and invoke the AI with the Sonar prompt and analysis URL"
|
|
2154
2178
|
)
|
|
2155
2179
|
).action(async (options) => {
|
|
2156
|
-
const executor =
|
|
2180
|
+
const executor = resolveExecutor2(options.with);
|
|
2157
2181
|
let branch;
|
|
2158
2182
|
try {
|
|
2159
2183
|
branch = getCurrentBranch();
|
|
@@ -2207,7 +2231,7 @@ var executeFixCommentsCmd = addAiOptions(
|
|
|
2207
2231
|
"Fetch open review comments on the current PR and invoke the AI with the Fix-Comments prompt"
|
|
2208
2232
|
)
|
|
2209
2233
|
).action(async (options) => {
|
|
2210
|
-
const executor =
|
|
2234
|
+
const executor = resolveExecutor2(options.with);
|
|
2211
2235
|
const result = resolveCurrentBranchComments();
|
|
2212
2236
|
if (!result.ok) {
|
|
2213
2237
|
if (result.kind === "error") {
|
|
@@ -2251,7 +2275,7 @@ var executeCheckIssueCmd = addAiOptions(
|
|
|
2251
2275
|
"Check a GitHub issue for a new message from an allowed user since the last agent run and invoke the AI with the issue conversation"
|
|
2252
2276
|
).argument("<issue-number>", "GitHub issue number to check")
|
|
2253
2277
|
).option("--force", "Skip the new-message check and invoke the AI directly").action(async (issueNumberArg, options) => {
|
|
2254
|
-
const executor =
|
|
2278
|
+
const executor = resolveExecutor2(options.with);
|
|
2255
2279
|
const issueNumber = Number.parseInt(issueNumberArg, 10);
|
|
2256
2280
|
if (Number.isNaN(issueNumber) || issueNumber <= 0) {
|
|
2257
2281
|
process.stderr.write(`Error: <issue-number> must be a positive integer (got '${issueNumberArg}').
|
|
@@ -3401,26 +3425,28 @@ function validateDoWorkConfig(section) {
|
|
|
3401
3425
|
validateOptionalString(section, "baseBranch", "doWork.baseBranch");
|
|
3402
3426
|
validateOptionalInt(section, "maxRunsPerTick", "doWork.maxRunsPerTick", 0, "a non-negative integer (0 = unlimited)");
|
|
3403
3427
|
validateOptionalInt(section, "lockStaleMinutes", "doWork.lockStaleMinutes", 1, "a positive integer");
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3428
|
+
validateProtectedBranches(section["protectedBranches"]);
|
|
3429
|
+
validateSettingContainer(section["models"], "models", ["claude", "codex"]);
|
|
3430
|
+
validateSettingContainer(section["prompts"], "prompts", ["issueDiscuss", "prWork"]);
|
|
3431
|
+
}
|
|
3432
|
+
function validateProtectedBranches(value) {
|
|
3433
|
+
if (value === void 0 || value === null) return;
|
|
3434
|
+
const isNonEmptyString = (b) => typeof b === "string" && b.trim().length > 0;
|
|
3435
|
+
if (!Array.isArray(value) || !value.every(isNonEmptyString)) {
|
|
3436
|
+
fail("doWork.protectedBranches must be an array of non-empty strings.");
|
|
3409
3437
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
fail(`doWork.${container}.${key} is not a recognised setting; expected one of: ${keys.join(", ")}.`);
|
|
3423
|
-
}
|
|
3438
|
+
}
|
|
3439
|
+
function validateSettingContainer(value, container, keys) {
|
|
3440
|
+
if (value === void 0 || value === null) return;
|
|
3441
|
+
if (!isPlainObject(value)) {
|
|
3442
|
+
fail(`doWork.${container} must be an object, got ${JSON.stringify(value)}.`);
|
|
3443
|
+
}
|
|
3444
|
+
for (const key of keys) {
|
|
3445
|
+
validateOptionalString(value, key, `doWork.${container}.${key}`);
|
|
3446
|
+
}
|
|
3447
|
+
for (const key of Object.keys(value)) {
|
|
3448
|
+
if (!keys.includes(key)) {
|
|
3449
|
+
fail(`doWork.${container}.${key} is not a recognised setting; expected one of: ${keys.join(", ")}.`);
|
|
3424
3450
|
}
|
|
3425
3451
|
}
|
|
3426
3452
|
}
|
|
@@ -3736,10 +3762,9 @@ async function processItem(planned, settings, silent) {
|
|
|
3736
3762
|
baseBranch: settings.baseBranch,
|
|
3737
3763
|
frame: settings.prompts[item.turn]
|
|
3738
3764
|
});
|
|
3739
|
-
const
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
const detail = `the composed prompt is ${String(Math.round(promptBytes / 1024))} KiB, over the ${String(MAX_PROMPT_BYTES / 1024)} KiB limit for a single command-line argument. The conversation is too long to hand to the executor this way.`;
|
|
3765
|
+
const oversized = describeOversizedPrompt(prompt);
|
|
3766
|
+
if (oversized !== null) {
|
|
3767
|
+
const detail = oversized;
|
|
3743
3768
|
progress(` failed: ${detail}
|
|
3744
3769
|
`);
|
|
3745
3770
|
inFlightMarker = null;
|
|
@@ -3765,6 +3790,16 @@ async function processItem(planned, settings, silent) {
|
|
|
3765
3790
|
const reconciled = reconcileMarker(item, marker, settings.participants, watermark, runError);
|
|
3766
3791
|
progress(` ${reconciled.detail}
|
|
3767
3792
|
`);
|
|
3793
|
+
const outcome = adjustOutcome(reconciled, item, settings, buriedByNote);
|
|
3794
|
+
return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor };
|
|
3795
|
+
}
|
|
3796
|
+
function describeOversizedPrompt(prompt) {
|
|
3797
|
+
const MAX_PROMPT_BYTES = 96 * 1024;
|
|
3798
|
+
const promptBytes = Buffer.byteLength(prompt, "utf8");
|
|
3799
|
+
if (promptBytes <= MAX_PROMPT_BYTES) return null;
|
|
3800
|
+
return `the composed prompt is ${String(Math.round(promptBytes / 1024))} KiB, over the ${String(MAX_PROMPT_BYTES / 1024)} KiB limit for a single command-line argument. The conversation is too long to hand to the executor this way.`;
|
|
3801
|
+
}
|
|
3802
|
+
function adjustOutcome(reconciled, item, settings, buriedByNote) {
|
|
3768
3803
|
let outcome = reconciled;
|
|
3769
3804
|
if (buriedByNote > 0 && outcome.outcome === "answered") {
|
|
3770
3805
|
outcome = {
|
|
@@ -3773,18 +3808,17 @@ async function processItem(planned, settings, silent) {
|
|
|
3773
3808
|
reason: "flagged"
|
|
3774
3809
|
};
|
|
3775
3810
|
}
|
|
3776
|
-
if (item.turn
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
}
|
|
3811
|
+
if (item.turn !== "issue-discuss") return outcome;
|
|
3812
|
+
const linked = repairIssueLink(item, settings.baseBranch);
|
|
3813
|
+
if (linked && outcome.reason === "no-answer") {
|
|
3814
|
+
progress(" a pull request was opened, so the turn is counted as answered.\n");
|
|
3815
|
+
return {
|
|
3816
|
+
outcome: "answered",
|
|
3817
|
+
detail: "opened a pull request (no issue comment)",
|
|
3818
|
+
reason: "answered"
|
|
3819
|
+
};
|
|
3786
3820
|
}
|
|
3787
|
-
return
|
|
3821
|
+
return outcome;
|
|
3788
3822
|
}
|
|
3789
3823
|
function summarize(reports) {
|
|
3790
3824
|
out("\nTick summary:\n");
|
|
@@ -3811,25 +3845,7 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3811
3845
|
}
|
|
3812
3846
|
const lock = acquireRunLock("do-work", settings.lockStaleMinutes);
|
|
3813
3847
|
if (!lock.ok) {
|
|
3814
|
-
const
|
|
3815
|
-
const sentence = `Another automata instance is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}, command ${held.command}). Doing nothing.
|
|
3816
|
-
`;
|
|
3817
|
-
const suspectSentence = lock.suspect ? `Warning: that lock has been held longer than ${String(settings.lockStaleMinutes)} minutes. If no tick is really running, its process id was probably reused; remove ${RUN_LOCK_RELATIVE_PATH} once you have confirmed that.
|
|
3818
|
-
` : "";
|
|
3819
|
-
const exitCode2 = lock.suspect ? 2 : 0;
|
|
3820
|
-
if (options.json === true) {
|
|
3821
|
-
progress(sentence + suspectSentence);
|
|
3822
|
-
out(
|
|
3823
|
-
JSON.stringify(
|
|
3824
|
-
{ lockHeld: true, suspect: lock.suspect, heldBy: held, plan: [], items: [], exitCode: exitCode2 },
|
|
3825
|
-
null,
|
|
3826
|
-
2
|
|
3827
|
-
) + "\n"
|
|
3828
|
-
);
|
|
3829
|
-
} else {
|
|
3830
|
-
out(sentence);
|
|
3831
|
-
if (suspectSentence) progress(suspectSentence);
|
|
3832
|
-
}
|
|
3848
|
+
const exitCode2 = reportLockHeld(lock, settings, options);
|
|
3833
3849
|
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
3834
3850
|
return;
|
|
3835
3851
|
}
|
|
@@ -3839,18 +3855,7 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3839
3855
|
if (shuttingDown) return;
|
|
3840
3856
|
shuttingDown = true;
|
|
3841
3857
|
progress("\nInterrupted: stopping the executor before releasing the run lock\u2026\n");
|
|
3842
|
-
|
|
3843
|
-
if (pending !== null) {
|
|
3844
|
-
try {
|
|
3845
|
-
updateMarker(
|
|
3846
|
-
pending.marker,
|
|
3847
|
-
`automata do-work: this run was interrupted before it finished, so no answer was produced. The branch \`${pending.item.branch}\` may have been changed. Reply here to have another attempt made.`
|
|
3848
|
-
);
|
|
3849
|
-
} catch (err) {
|
|
3850
|
-
progress(`Warning: could not update the in-flight marker: ${err.message}
|
|
3851
|
-
`);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3858
|
+
explainInterruptedMarker();
|
|
3854
3859
|
void terminateTrackedChildren().then((allExited) => {
|
|
3855
3860
|
if (allExited) {
|
|
3856
3861
|
handle.release();
|
|
@@ -3878,6 +3883,37 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3878
3883
|
}
|
|
3879
3884
|
if (exitCode !== 0) process.exit(exitCode);
|
|
3880
3885
|
});
|
|
3886
|
+
function reportLockHeld(lock, settings, options) {
|
|
3887
|
+
const held = lock.heldBy;
|
|
3888
|
+
const sentence = `Another automata instance is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}, command ${held.command}). Doing nothing.
|
|
3889
|
+
`;
|
|
3890
|
+
const suspectSentence = lock.suspect ? `Warning: that lock has been held longer than ${String(settings.lockStaleMinutes)} minutes. If no tick is really running, its process id was probably reused; remove ${RUN_LOCK_RELATIVE_PATH} once you have confirmed that.
|
|
3891
|
+
` : "";
|
|
3892
|
+
const exitCode = lock.suspect ? 2 : 0;
|
|
3893
|
+
if (options.json === true) {
|
|
3894
|
+
progress(sentence + suspectSentence);
|
|
3895
|
+
out(
|
|
3896
|
+
JSON.stringify({ lockHeld: true, suspect: lock.suspect, heldBy: held, plan: [], items: [], exitCode }, null, 2) + "\n"
|
|
3897
|
+
);
|
|
3898
|
+
} else {
|
|
3899
|
+
out(sentence);
|
|
3900
|
+
if (suspectSentence) progress(suspectSentence);
|
|
3901
|
+
}
|
|
3902
|
+
return exitCode;
|
|
3903
|
+
}
|
|
3904
|
+
function explainInterruptedMarker() {
|
|
3905
|
+
const pending = inFlightMarker;
|
|
3906
|
+
if (pending === null) return;
|
|
3907
|
+
try {
|
|
3908
|
+
updateMarker(
|
|
3909
|
+
pending.marker,
|
|
3910
|
+
`automata do-work: this run was interrupted before it finished, so no answer was produced. The branch \`${pending.item.branch}\` may have been changed. Reply here to have another attempt made.`
|
|
3911
|
+
);
|
|
3912
|
+
} catch (err) {
|
|
3913
|
+
progress(`Warning: could not update the in-flight marker: ${err.message}
|
|
3914
|
+
`);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3881
3917
|
async function runTick(settings, options) {
|
|
3882
3918
|
const issues = discoverIssues(settings);
|
|
3883
3919
|
const linkMap = getOpenPrLinkMap();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automata-cli",
|
|
3
|
-
"version": "0.6.0-develop.
|
|
3
|
+
"version": "0.6.0-develop.230",
|
|
4
4
|
"description": "Automata CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"format": "prettier --check src/",
|
|
18
18
|
"test": "npm run build && vitest run tests/unit",
|
|
19
|
+
"test:unit": "vitest run tests/unit",
|
|
19
20
|
"test:integration": "npm run build && vitest run tests/integration"
|
|
20
21
|
},
|
|
21
22
|
"repository": {
|