@rotorsoft/gent 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UWYWIOYZ.js → chunk-KLHUMY5L.js} +21 -2
- package/dist/{chunk-UWYWIOYZ.js.map → chunk-KLHUMY5L.js.map} +1 -1
- package/dist/index.js +346 -164
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-5EHRPUPF.js → setup-labels-3ANC76NF.js} +2 -2
- package/package.json +1 -1
- /package/dist/{setup-labels-5EHRPUPF.js.map → setup-labels-3ANC76NF.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
createIssue,
|
|
15
15
|
createPullRequest,
|
|
16
16
|
createSpinner,
|
|
17
|
-
extractPriorityFromLabels,
|
|
18
17
|
extractTypeFromLabels,
|
|
19
18
|
generateDefaultConfig,
|
|
20
19
|
getConfigPath,
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
getWorkflowLabels,
|
|
27
26
|
isValidIssueNumber,
|
|
28
27
|
listIssues,
|
|
28
|
+
listOpenPrs,
|
|
29
29
|
loadAgentInstructions,
|
|
30
30
|
loadConfig,
|
|
31
31
|
logger,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
sortByPriority,
|
|
36
36
|
updateIssueLabels,
|
|
37
37
|
withSpinner
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-KLHUMY5L.js";
|
|
39
39
|
|
|
40
40
|
// src/index.ts
|
|
41
41
|
import { Command } from "commander";
|
|
@@ -196,7 +196,7 @@ async function initCommand(options) {
|
|
|
196
196
|
}
|
|
197
197
|
]);
|
|
198
198
|
if (setupLabels) {
|
|
199
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
199
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-3ANC76NF.js");
|
|
200
200
|
await setupLabelsCommand2();
|
|
201
201
|
}
|
|
202
202
|
}
|
|
@@ -808,105 +808,6 @@ Next steps:
|
|
|
808
808
|
|
|
809
809
|
// src/commands/list.ts
|
|
810
810
|
import chalk2 from "chalk";
|
|
811
|
-
async function listCommand(options) {
|
|
812
|
-
const isAuthed = await checkGhAuth();
|
|
813
|
-
if (!isAuthed) {
|
|
814
|
-
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
815
|
-
process.exit(1);
|
|
816
|
-
}
|
|
817
|
-
const config = loadConfig();
|
|
818
|
-
const workflowLabels = getWorkflowLabels(config);
|
|
819
|
-
const labels = [];
|
|
820
|
-
if (options.label) {
|
|
821
|
-
labels.push(options.label);
|
|
822
|
-
}
|
|
823
|
-
if (options.status && options.status !== "all") {
|
|
824
|
-
switch (options.status) {
|
|
825
|
-
case "ready":
|
|
826
|
-
labels.push(workflowLabels.ready);
|
|
827
|
-
break;
|
|
828
|
-
case "in-progress":
|
|
829
|
-
labels.push(workflowLabels.inProgress);
|
|
830
|
-
break;
|
|
831
|
-
case "completed":
|
|
832
|
-
labels.push(workflowLabels.completed);
|
|
833
|
-
break;
|
|
834
|
-
case "blocked":
|
|
835
|
-
labels.push(workflowLabels.blocked);
|
|
836
|
-
break;
|
|
837
|
-
}
|
|
838
|
-
} else if (!options.status) {
|
|
839
|
-
labels.push(workflowLabels.ready);
|
|
840
|
-
}
|
|
841
|
-
let issues;
|
|
842
|
-
try {
|
|
843
|
-
issues = await listIssues({
|
|
844
|
-
labels: labels.length > 0 ? labels : void 0,
|
|
845
|
-
state: "open",
|
|
846
|
-
limit: options.limit || 20
|
|
847
|
-
});
|
|
848
|
-
} catch (error) {
|
|
849
|
-
logger.error(`Failed to fetch issues: ${error}`);
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (issues.length === 0) {
|
|
853
|
-
logger.info("No issues found matching the criteria.");
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
sortByPriority(issues);
|
|
857
|
-
logger.bold(`Found ${issues.length} issue(s):`);
|
|
858
|
-
logger.newline();
|
|
859
|
-
for (const issue of issues) {
|
|
860
|
-
const type = extractTypeFromLabels(issue.labels);
|
|
861
|
-
const priority = extractPriorityFromLabels(issue.labels);
|
|
862
|
-
const status = getIssueStatus(issue.labels, workflowLabels);
|
|
863
|
-
const priorityColor = getPriorityColor(priority);
|
|
864
|
-
const statusColor = getStatusColor(status);
|
|
865
|
-
console.log(
|
|
866
|
-
` ${colors.issue(`#${issue.number.toString().padStart(4)}`)} ${priorityColor(`[${priority}]`.padEnd(10))} ${statusColor(`[${status}]`.padEnd(14))} ${colors.label(`[${type}]`.padEnd(10))} ` + issue.title.slice(0, 50) + (issue.title.length > 50 ? "..." : "")
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
logger.newline();
|
|
870
|
-
logger.dim(`Run ${colors.command("gent run <issue-number>")} to implement an issue`);
|
|
871
|
-
logger.dim(`Run ${colors.command("gent run --auto")} to auto-select highest priority`);
|
|
872
|
-
}
|
|
873
|
-
function getIssueStatus(labels, workflowLabels) {
|
|
874
|
-
if (labels.includes(workflowLabels.ready)) return "ready";
|
|
875
|
-
if (labels.includes(workflowLabels.inProgress)) return "in-progress";
|
|
876
|
-
if (labels.includes(workflowLabels.completed)) return "completed";
|
|
877
|
-
if (labels.includes(workflowLabels.blocked)) return "blocked";
|
|
878
|
-
return "unknown";
|
|
879
|
-
}
|
|
880
|
-
function getPriorityColor(priority) {
|
|
881
|
-
switch (priority) {
|
|
882
|
-
case "critical":
|
|
883
|
-
return chalk2.red;
|
|
884
|
-
case "high":
|
|
885
|
-
return chalk2.yellow;
|
|
886
|
-
case "medium":
|
|
887
|
-
return chalk2.blue;
|
|
888
|
-
case "low":
|
|
889
|
-
return chalk2.green;
|
|
890
|
-
default:
|
|
891
|
-
return chalk2.gray;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
function getStatusColor(status) {
|
|
895
|
-
switch (status) {
|
|
896
|
-
case "ready":
|
|
897
|
-
return chalk2.green;
|
|
898
|
-
case "in-progress":
|
|
899
|
-
return chalk2.yellow;
|
|
900
|
-
case "completed":
|
|
901
|
-
return chalk2.blue;
|
|
902
|
-
case "blocked":
|
|
903
|
-
return chalk2.red;
|
|
904
|
-
default:
|
|
905
|
-
return chalk2.gray;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// src/commands/run.ts
|
|
910
811
|
import inquirer3 from "inquirer";
|
|
911
812
|
|
|
912
813
|
// src/lib/git.ts
|
|
@@ -1028,6 +929,22 @@ async function getLastCommitTimestamp() {
|
|
|
1028
929
|
const { stdout } = await execa2("git", ["log", "-1", "--format=%cI"]);
|
|
1029
930
|
return stdout.trim();
|
|
1030
931
|
}
|
|
932
|
+
async function listLocalBranches() {
|
|
933
|
+
const { stdout } = await execa2("git", ["branch", "--format=%(refname:short)"]);
|
|
934
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
935
|
+
}
|
|
936
|
+
async function remoteBranchExists(name) {
|
|
937
|
+
try {
|
|
938
|
+
await execa2("git", ["ls-remote", "--exit-code", "--heads", "origin", name]);
|
|
939
|
+
return true;
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
async function fetchAndCheckout(name) {
|
|
945
|
+
await execa2("git", ["fetch", "origin", `${name}:${name}`]);
|
|
946
|
+
await execa2("git", ["checkout", name]);
|
|
947
|
+
}
|
|
1031
948
|
|
|
1032
949
|
// src/lib/branch.ts
|
|
1033
950
|
async function generateBranchName(config, issueNumber, issueTitle, type) {
|
|
@@ -1100,7 +1017,308 @@ function extractIssueNumber(branchName) {
|
|
|
1100
1017
|
return info?.issueNumber ?? null;
|
|
1101
1018
|
}
|
|
1102
1019
|
|
|
1020
|
+
// src/commands/list.ts
|
|
1021
|
+
function findBranchForIssue(issueNumber, branches) {
|
|
1022
|
+
for (const branch of branches) {
|
|
1023
|
+
const info = parseBranchName(branch);
|
|
1024
|
+
if (info && info.issueNumber === issueNumber) {
|
|
1025
|
+
return branch;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
function buildTicketChoices(inProgressIssues, readyIssues, openPrs, localBranches) {
|
|
1031
|
+
const choices = [];
|
|
1032
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1033
|
+
const prByIssue = /* @__PURE__ */ new Map();
|
|
1034
|
+
for (const pr of openPrs) {
|
|
1035
|
+
const info = parseBranchName(pr.headRefName);
|
|
1036
|
+
if (info) {
|
|
1037
|
+
prByIssue.set(info.issueNumber, pr);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
for (const issue of inProgressIssues) {
|
|
1041
|
+
if (seen.has(issue.number)) continue;
|
|
1042
|
+
seen.add(issue.number);
|
|
1043
|
+
const branch = findBranchForIssue(issue.number, localBranches);
|
|
1044
|
+
const pr = prByIssue.get(issue.number);
|
|
1045
|
+
choices.push({
|
|
1046
|
+
issueNumber: issue.number,
|
|
1047
|
+
title: issue.title,
|
|
1048
|
+
branch: branch || pr?.headRefName || null,
|
|
1049
|
+
category: pr ? "open-pr" : "in-progress"
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
for (const [issueNumber, pr] of prByIssue) {
|
|
1053
|
+
if (seen.has(issueNumber)) continue;
|
|
1054
|
+
seen.add(issueNumber);
|
|
1055
|
+
const issue = [...inProgressIssues, ...readyIssues].find(
|
|
1056
|
+
(i) => i.number === issueNumber
|
|
1057
|
+
);
|
|
1058
|
+
choices.push({
|
|
1059
|
+
issueNumber,
|
|
1060
|
+
title: issue?.title || pr.title,
|
|
1061
|
+
branch: pr.headRefName,
|
|
1062
|
+
category: "open-pr"
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
for (const issue of readyIssues) {
|
|
1066
|
+
if (seen.has(issue.number)) continue;
|
|
1067
|
+
seen.add(issue.number);
|
|
1068
|
+
const branch = findBranchForIssue(issue.number, localBranches);
|
|
1069
|
+
choices.push({
|
|
1070
|
+
issueNumber: issue.number,
|
|
1071
|
+
title: issue.title,
|
|
1072
|
+
branch,
|
|
1073
|
+
category: "ready"
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return choices;
|
|
1077
|
+
}
|
|
1078
|
+
function categoryLabel(category) {
|
|
1079
|
+
switch (category) {
|
|
1080
|
+
case "in-progress":
|
|
1081
|
+
return chalk2.yellow("[in progress]");
|
|
1082
|
+
case "open-pr":
|
|
1083
|
+
return chalk2.blue("[open PR]");
|
|
1084
|
+
case "ready":
|
|
1085
|
+
return chalk2.green("[ready]");
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function formatChoice(choice) {
|
|
1089
|
+
const num = colors.issue(`#${choice.issueNumber}`);
|
|
1090
|
+
const cat = categoryLabel(choice.category);
|
|
1091
|
+
const title = choice.title.length > 50 ? choice.title.slice(0, 50) + "..." : choice.title;
|
|
1092
|
+
return `${num} ${cat} ${title}`;
|
|
1093
|
+
}
|
|
1094
|
+
async function listCommand(options) {
|
|
1095
|
+
const isAuthed = await checkGhAuth();
|
|
1096
|
+
if (!isAuthed) {
|
|
1097
|
+
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const config = loadConfig();
|
|
1101
|
+
const workflowLabels = getWorkflowLabels(config);
|
|
1102
|
+
const currentBranch = await getCurrentBranch();
|
|
1103
|
+
const defaultBranch = await getDefaultBranch();
|
|
1104
|
+
const statusFilter = options.status;
|
|
1105
|
+
const limit = options.limit || 20;
|
|
1106
|
+
let inProgressIssues = [];
|
|
1107
|
+
let readyIssues = [];
|
|
1108
|
+
if (statusFilter && statusFilter !== "all") {
|
|
1109
|
+
const labels = [];
|
|
1110
|
+
if (options.label) labels.push(options.label);
|
|
1111
|
+
switch (statusFilter) {
|
|
1112
|
+
case "ready":
|
|
1113
|
+
labels.push(workflowLabels.ready);
|
|
1114
|
+
break;
|
|
1115
|
+
case "in-progress":
|
|
1116
|
+
labels.push(workflowLabels.inProgress);
|
|
1117
|
+
break;
|
|
1118
|
+
case "completed":
|
|
1119
|
+
labels.push(workflowLabels.completed);
|
|
1120
|
+
break;
|
|
1121
|
+
case "blocked":
|
|
1122
|
+
labels.push(workflowLabels.blocked);
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
const [issues, localBranches2] = await withSpinner(
|
|
1126
|
+
"Fetching tickets...",
|
|
1127
|
+
() => Promise.all([
|
|
1128
|
+
listIssues({ labels, state: "open", limit }),
|
|
1129
|
+
listLocalBranches()
|
|
1130
|
+
])
|
|
1131
|
+
);
|
|
1132
|
+
sortByPriority(issues);
|
|
1133
|
+
if (statusFilter === "in-progress") {
|
|
1134
|
+
inProgressIssues = issues;
|
|
1135
|
+
} else {
|
|
1136
|
+
readyIssues = issues;
|
|
1137
|
+
}
|
|
1138
|
+
const choices2 = buildTicketChoices(
|
|
1139
|
+
inProgressIssues,
|
|
1140
|
+
readyIssues,
|
|
1141
|
+
[],
|
|
1142
|
+
localBranches2
|
|
1143
|
+
);
|
|
1144
|
+
if (choices2.length === 0) {
|
|
1145
|
+
logger.info("No issues found matching the criteria.");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
await presentSelector(
|
|
1149
|
+
choices2,
|
|
1150
|
+
currentBranch,
|
|
1151
|
+
defaultBranch,
|
|
1152
|
+
config
|
|
1153
|
+
);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const labelFilter = options.label ? [options.label] : [];
|
|
1157
|
+
const [inProgress, ready, prs, localBranches] = await withSpinner(
|
|
1158
|
+
"Fetching tickets...",
|
|
1159
|
+
() => Promise.all([
|
|
1160
|
+
listIssues({
|
|
1161
|
+
labels: [workflowLabels.inProgress, ...labelFilter],
|
|
1162
|
+
state: "open",
|
|
1163
|
+
limit
|
|
1164
|
+
}),
|
|
1165
|
+
listIssues({
|
|
1166
|
+
labels: [workflowLabels.ready, ...labelFilter],
|
|
1167
|
+
state: "open",
|
|
1168
|
+
limit
|
|
1169
|
+
}),
|
|
1170
|
+
listOpenPrs(30),
|
|
1171
|
+
listLocalBranches()
|
|
1172
|
+
])
|
|
1173
|
+
);
|
|
1174
|
+
sortByPriority(inProgress);
|
|
1175
|
+
sortByPriority(ready);
|
|
1176
|
+
const choices = buildTicketChoices(inProgress, ready, prs, localBranches);
|
|
1177
|
+
if (choices.length === 0) {
|
|
1178
|
+
logger.info("No tickets found.");
|
|
1179
|
+
logger.dim(
|
|
1180
|
+
`Create a ticket with ${colors.command("gent create")} or add the '${workflowLabels.ready}' label to an issue.`
|
|
1181
|
+
);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
await presentSelector(choices, currentBranch, defaultBranch, config);
|
|
1185
|
+
}
|
|
1186
|
+
async function presentSelector(choices, currentBranch, defaultBranch, config) {
|
|
1187
|
+
const dirty = await hasUncommittedChanges();
|
|
1188
|
+
const inquirerChoices = [];
|
|
1189
|
+
inquirerChoices.push({
|
|
1190
|
+
name: `${chalk2.magenta(defaultBranch)}${currentBranch === defaultBranch ? chalk2.dim(" (current)") : ""}`,
|
|
1191
|
+
value: "__main__"
|
|
1192
|
+
});
|
|
1193
|
+
inquirerChoices.push(new inquirer3.Separator("\u2500"));
|
|
1194
|
+
const inProgress = choices.filter((c) => c.category === "in-progress");
|
|
1195
|
+
const openPrChoices = choices.filter((c) => c.category === "open-pr");
|
|
1196
|
+
const ready = choices.filter((c) => c.category === "ready");
|
|
1197
|
+
if (inProgress.length > 0) {
|
|
1198
|
+
inquirerChoices.push(new inquirer3.Separator(chalk2.yellow(" In Progress")));
|
|
1199
|
+
for (const c of inProgress) {
|
|
1200
|
+
inquirerChoices.push({
|
|
1201
|
+
name: formatChoice(c),
|
|
1202
|
+
value: String(c.issueNumber)
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (openPrChoices.length > 0) {
|
|
1207
|
+
inquirerChoices.push(new inquirer3.Separator(chalk2.blue(" Open PRs")));
|
|
1208
|
+
for (const c of openPrChoices) {
|
|
1209
|
+
inquirerChoices.push({
|
|
1210
|
+
name: formatChoice(c),
|
|
1211
|
+
value: String(c.issueNumber)
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (ready.length > 0) {
|
|
1216
|
+
inquirerChoices.push(new inquirer3.Separator(chalk2.green(" Ready")));
|
|
1217
|
+
for (const c of ready) {
|
|
1218
|
+
inquirerChoices.push({
|
|
1219
|
+
name: formatChoice(c),
|
|
1220
|
+
value: String(c.issueNumber)
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const { selected } = await inquirer3.prompt([
|
|
1225
|
+
{
|
|
1226
|
+
type: "list",
|
|
1227
|
+
name: "selected",
|
|
1228
|
+
message: "Select a ticket to switch to:",
|
|
1229
|
+
choices: inquirerChoices,
|
|
1230
|
+
pageSize: 20
|
|
1231
|
+
}
|
|
1232
|
+
]);
|
|
1233
|
+
if (selected === "__main__") {
|
|
1234
|
+
if (currentBranch === defaultBranch) {
|
|
1235
|
+
logger.info(`Already on ${colors.branch(defaultBranch)}`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (dirty) {
|
|
1239
|
+
const ok = await confirmDirty();
|
|
1240
|
+
if (!ok) return;
|
|
1241
|
+
}
|
|
1242
|
+
await withSpinner(`Switching to ${defaultBranch}...`, async () => {
|
|
1243
|
+
await checkoutBranch(defaultBranch);
|
|
1244
|
+
});
|
|
1245
|
+
logger.success(`Switched to ${colors.branch(defaultBranch)}`);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const issueNumber = parseInt(selected, 10);
|
|
1249
|
+
const ticket = choices.find((c) => c.issueNumber === issueNumber);
|
|
1250
|
+
if (!ticket) return;
|
|
1251
|
+
if (dirty) {
|
|
1252
|
+
const ok = await confirmDirty();
|
|
1253
|
+
if (!ok) return;
|
|
1254
|
+
}
|
|
1255
|
+
const targetBranch = ticket.branch;
|
|
1256
|
+
if (targetBranch) {
|
|
1257
|
+
if (await branchExists(targetBranch)) {
|
|
1258
|
+
await withSpinner(`Switching to ${targetBranch}...`, async () => {
|
|
1259
|
+
await checkoutBranch(targetBranch);
|
|
1260
|
+
});
|
|
1261
|
+
logger.success(`Switched to ${colors.branch(targetBranch)}`);
|
|
1262
|
+
} else if (await remoteBranchExists(targetBranch)) {
|
|
1263
|
+
await withSpinner(
|
|
1264
|
+
`Fetching ${targetBranch} from remote...`,
|
|
1265
|
+
async () => {
|
|
1266
|
+
await fetchAndCheckout(targetBranch);
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
logger.success(
|
|
1270
|
+
`Fetched and switched to ${colors.branch(targetBranch)}`
|
|
1271
|
+
);
|
|
1272
|
+
} else {
|
|
1273
|
+
logger.warning(
|
|
1274
|
+
`Branch ${colors.branch(targetBranch)} not found locally or on remote.`
|
|
1275
|
+
);
|
|
1276
|
+
await offerCreateBranch(config, issueNumber, ticket.title);
|
|
1277
|
+
}
|
|
1278
|
+
} else {
|
|
1279
|
+
await offerCreateBranch(config, issueNumber, ticket.title);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async function confirmDirty() {
|
|
1283
|
+
logger.warning("You have uncommitted changes.");
|
|
1284
|
+
const { proceed } = await inquirer3.prompt([
|
|
1285
|
+
{
|
|
1286
|
+
type: "confirm",
|
|
1287
|
+
name: "proceed",
|
|
1288
|
+
message: "Continue anyway? (changes will carry over to the new branch)",
|
|
1289
|
+
default: false
|
|
1290
|
+
}
|
|
1291
|
+
]);
|
|
1292
|
+
if (!proceed) {
|
|
1293
|
+
logger.info("Aborting. Please commit or stash your changes first.");
|
|
1294
|
+
}
|
|
1295
|
+
return proceed;
|
|
1296
|
+
}
|
|
1297
|
+
async function offerCreateBranch(config, issueNumber, title) {
|
|
1298
|
+
const branchName = await generateBranchName(
|
|
1299
|
+
config,
|
|
1300
|
+
issueNumber,
|
|
1301
|
+
title,
|
|
1302
|
+
"feature"
|
|
1303
|
+
);
|
|
1304
|
+
const { create } = await inquirer3.prompt([
|
|
1305
|
+
{
|
|
1306
|
+
type: "confirm",
|
|
1307
|
+
name: "create",
|
|
1308
|
+
message: `No branch exists. Create ${colors.branch(branchName)}?`,
|
|
1309
|
+
default: true
|
|
1310
|
+
}
|
|
1311
|
+
]);
|
|
1312
|
+
if (!create) return;
|
|
1313
|
+
const defaultBranch = await getDefaultBranch();
|
|
1314
|
+
await withSpinner(`Creating branch ${branchName}...`, async () => {
|
|
1315
|
+
await createBranch(branchName, defaultBranch);
|
|
1316
|
+
});
|
|
1317
|
+
logger.success(`Created and switched to ${colors.branch(branchName)}`);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1103
1320
|
// src/commands/run.ts
|
|
1321
|
+
import inquirer4 from "inquirer";
|
|
1104
1322
|
async function runCommand(issueNumberArg, options) {
|
|
1105
1323
|
logger.bold("Running AI implementation workflow...");
|
|
1106
1324
|
logger.newline();
|
|
@@ -1122,7 +1340,7 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1122
1340
|
const hasChanges = await hasUncommittedChanges();
|
|
1123
1341
|
if (hasChanges) {
|
|
1124
1342
|
logger.warning("You have uncommitted changes.");
|
|
1125
|
-
const { proceed } = await
|
|
1343
|
+
const { proceed } = await inquirer4.prompt([
|
|
1126
1344
|
{
|
|
1127
1345
|
type: "confirm",
|
|
1128
1346
|
name: "proceed",
|
|
@@ -1137,22 +1355,14 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1137
1355
|
}
|
|
1138
1356
|
const workflowLabels = getWorkflowLabels(config);
|
|
1139
1357
|
let issueNumber;
|
|
1140
|
-
if (
|
|
1141
|
-
const autoIssue = await autoSelectIssue(workflowLabels.ready);
|
|
1142
|
-
if (!autoIssue) {
|
|
1143
|
-
logger.error("No ai-ready issues found.");
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
issueNumber = autoIssue.number;
|
|
1147
|
-
logger.info(`Auto-selected: ${colors.issue(`#${issueNumber}`)} - ${autoIssue.title}`);
|
|
1148
|
-
} else if (issueNumberArg) {
|
|
1358
|
+
if (issueNumberArg) {
|
|
1149
1359
|
if (!isValidIssueNumber(issueNumberArg)) {
|
|
1150
1360
|
logger.error("Invalid issue number.");
|
|
1151
1361
|
return;
|
|
1152
1362
|
}
|
|
1153
1363
|
issueNumber = parseInt(issueNumberArg, 10);
|
|
1154
1364
|
} else {
|
|
1155
|
-
logger.error("Please provide an issue number
|
|
1365
|
+
logger.error("Please provide an issue number. Use 'gent switch' to browse tickets.");
|
|
1156
1366
|
return;
|
|
1157
1367
|
}
|
|
1158
1368
|
let issue;
|
|
@@ -1166,7 +1376,7 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1166
1376
|
}
|
|
1167
1377
|
if (!issue.labels.includes(workflowLabels.ready)) {
|
|
1168
1378
|
logger.warning(`Issue #${issueNumber} does not have the '${workflowLabels.ready}' label.`);
|
|
1169
|
-
const { proceed } = await
|
|
1379
|
+
const { proceed } = await inquirer4.prompt([
|
|
1170
1380
|
{
|
|
1171
1381
|
type: "confirm",
|
|
1172
1382
|
name: "proceed",
|
|
@@ -1188,7 +1398,7 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1188
1398
|
const onMain = await isOnMainBranch();
|
|
1189
1399
|
if (await branchExists(branchName)) {
|
|
1190
1400
|
logger.info(`Branch ${colors.branch(branchName)} already exists.`);
|
|
1191
|
-
const { action } = await
|
|
1401
|
+
const { action } = await inquirer4.prompt([
|
|
1192
1402
|
{
|
|
1193
1403
|
type: "list",
|
|
1194
1404
|
name: "action",
|
|
@@ -1210,7 +1420,7 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1210
1420
|
} else {
|
|
1211
1421
|
if (!onMain) {
|
|
1212
1422
|
logger.warning(`Not on main branch (currently on ${colors.branch(currentBranch)}).`);
|
|
1213
|
-
const { fromMain } = await
|
|
1423
|
+
const { fromMain } = await inquirer4.prompt([
|
|
1214
1424
|
{
|
|
1215
1425
|
type: "confirm",
|
|
1216
1426
|
name: "fromMain",
|
|
@@ -1331,31 +1541,9 @@ No commits were created. Please retry later.`
|
|
|
1331
1541
|
3. Push branch: ${colors.command("git push -u origin " + branchName)}
|
|
1332
1542
|
4. Create PR: ${colors.command("gent pr")}`);
|
|
1333
1543
|
}
|
|
1334
|
-
async function autoSelectIssue(readyLabel) {
|
|
1335
|
-
let issues = await listIssues({
|
|
1336
|
-
labels: [readyLabel, "priority:critical"],
|
|
1337
|
-
state: "open",
|
|
1338
|
-
limit: 1
|
|
1339
|
-
});
|
|
1340
|
-
if (issues.length > 0) return issues[0];
|
|
1341
|
-
issues = await listIssues({
|
|
1342
|
-
labels: [readyLabel, "priority:high"],
|
|
1343
|
-
state: "open",
|
|
1344
|
-
limit: 1
|
|
1345
|
-
});
|
|
1346
|
-
if (issues.length > 0) return issues[0];
|
|
1347
|
-
issues = await listIssues({
|
|
1348
|
-
labels: [readyLabel],
|
|
1349
|
-
state: "open",
|
|
1350
|
-
limit: 10
|
|
1351
|
-
});
|
|
1352
|
-
if (issues.length === 0) return null;
|
|
1353
|
-
sortByPriority(issues);
|
|
1354
|
-
return issues[0];
|
|
1355
|
-
}
|
|
1356
1544
|
|
|
1357
1545
|
// src/commands/pr.ts
|
|
1358
|
-
import
|
|
1546
|
+
import inquirer5 from "inquirer";
|
|
1359
1547
|
|
|
1360
1548
|
// src/lib/playwright.ts
|
|
1361
1549
|
import { execa as execa3 } from "execa";
|
|
@@ -1436,7 +1624,7 @@ async function prCommand(options) {
|
|
|
1436
1624
|
const hasUnpushed = await getUnpushedCommits();
|
|
1437
1625
|
if (hasUnpushed) {
|
|
1438
1626
|
logger.warning("Branch has unpushed commits.");
|
|
1439
|
-
const { push } = await
|
|
1627
|
+
const { push } = await inquirer5.prompt([
|
|
1440
1628
|
{
|
|
1441
1629
|
type: "confirm",
|
|
1442
1630
|
name: "push",
|
|
@@ -1573,7 +1761,7 @@ function generateFallbackBody(issue, commits) {
|
|
|
1573
1761
|
}
|
|
1574
1762
|
|
|
1575
1763
|
// src/commands/fix.ts
|
|
1576
|
-
import
|
|
1764
|
+
import inquirer6 from "inquirer";
|
|
1577
1765
|
|
|
1578
1766
|
// src/lib/review-feedback.ts
|
|
1579
1767
|
var ACTIONABLE_KEYWORDS = [
|
|
@@ -1764,7 +1952,7 @@ async function fixCommand(options) {
|
|
|
1764
1952
|
const hasChanges = await hasUncommittedChanges();
|
|
1765
1953
|
if (hasChanges) {
|
|
1766
1954
|
logger.warning("You have uncommitted changes.");
|
|
1767
|
-
const { proceed } = await
|
|
1955
|
+
const { proceed } = await inquirer6.prompt([
|
|
1768
1956
|
{
|
|
1769
1957
|
type: "confirm",
|
|
1770
1958
|
name: "proceed",
|
|
@@ -1893,7 +2081,7 @@ import { homedir } from "os";
|
|
|
1893
2081
|
// package.json
|
|
1894
2082
|
var package_default = {
|
|
1895
2083
|
name: "@rotorsoft/gent",
|
|
1896
|
-
version: "1.
|
|
2084
|
+
version: "1.13.0",
|
|
1897
2085
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
1898
2086
|
keywords: [
|
|
1899
2087
|
"cli",
|
|
@@ -2303,7 +2491,7 @@ async function statusCommand() {
|
|
|
2303
2491
|
}
|
|
2304
2492
|
|
|
2305
2493
|
// src/commands/tui.ts
|
|
2306
|
-
import
|
|
2494
|
+
import inquirer7 from "inquirer";
|
|
2307
2495
|
import { execa as execa4 } from "execa";
|
|
2308
2496
|
|
|
2309
2497
|
// src/tui/state.ts
|
|
@@ -2432,7 +2620,6 @@ function getAvailableActions(state) {
|
|
|
2432
2620
|
}
|
|
2433
2621
|
if (state.isOnMain) {
|
|
2434
2622
|
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2435
|
-
actions.push({ id: "run-auto", label: "run next", shortcut: "r" });
|
|
2436
2623
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2437
2624
|
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2438
2625
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
@@ -2458,6 +2645,7 @@ function getAvailableActions(state) {
|
|
|
2458
2645
|
if (state.pr && (state.pr.state === "merged" || state.pr.state === "closed")) {
|
|
2459
2646
|
actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
|
|
2460
2647
|
}
|
|
2648
|
+
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2461
2649
|
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2462
2650
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2463
2651
|
return actions;
|
|
@@ -2739,7 +2927,7 @@ function clearScreen() {
|
|
|
2739
2927
|
// src/commands/tui.ts
|
|
2740
2928
|
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2741
2929
|
async function confirm(message) {
|
|
2742
|
-
const { ok } = await
|
|
2930
|
+
const { ok } = await inquirer7.prompt([
|
|
2743
2931
|
{
|
|
2744
2932
|
type: "confirm",
|
|
2745
2933
|
name: "ok",
|
|
@@ -2784,25 +2972,19 @@ async function executeAction(actionId, state, providerSetter) {
|
|
|
2784
2972
|
switch (actionId) {
|
|
2785
2973
|
case "quit":
|
|
2786
2974
|
return false;
|
|
2787
|
-
case "list":
|
|
2788
|
-
clearScreen();
|
|
2789
|
-
await listCommand({ status: "ready", limit: 20 });
|
|
2790
|
-
await promptContinue();
|
|
2791
|
-
return true;
|
|
2792
|
-
case "run-auto": {
|
|
2975
|
+
case "list": {
|
|
2793
2976
|
clearScreen();
|
|
2794
|
-
if (!await confirm("Start AI agent to implement next ticket?")) return true;
|
|
2795
2977
|
try {
|
|
2796
|
-
await
|
|
2978
|
+
await listCommand({});
|
|
2797
2979
|
} catch (error) {
|
|
2798
|
-
logger.error(`
|
|
2799
|
-
await promptContinue();
|
|
2980
|
+
logger.error(`List failed: ${error}`);
|
|
2800
2981
|
}
|
|
2801
|
-
|
|
2982
|
+
await promptContinue();
|
|
2983
|
+
return true;
|
|
2802
2984
|
}
|
|
2803
2985
|
case "create": {
|
|
2804
2986
|
clearScreen();
|
|
2805
|
-
const { description } = await
|
|
2987
|
+
const { description } = await inquirer7.prompt([
|
|
2806
2988
|
{
|
|
2807
2989
|
type: "input",
|
|
2808
2990
|
name: "description",
|
|
@@ -2936,7 +3118,7 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
|
|
|
2936
3118
|
} catch {
|
|
2937
3119
|
logger.warning("AI commit message generation failed");
|
|
2938
3120
|
console.log();
|
|
2939
|
-
const { message } = await
|
|
3121
|
+
const { message } = await inquirer7.prompt([
|
|
2940
3122
|
{
|
|
2941
3123
|
type: "input",
|
|
2942
3124
|
name: "message",
|
|
@@ -3005,7 +3187,7 @@ async function handlePush() {
|
|
|
3005
3187
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3006
3188
|
async function handleSwitchProvider(state, setProvider) {
|
|
3007
3189
|
const current = state.config.ai.provider;
|
|
3008
|
-
const { provider } = await
|
|
3190
|
+
const { provider } = await inquirer7.prompt([
|
|
3009
3191
|
{
|
|
3010
3192
|
type: "list",
|
|
3011
3193
|
name: "provider",
|
|
@@ -3060,7 +3242,7 @@ async function handleVideoCapture(state) {
|
|
|
3060
3242
|
}
|
|
3061
3243
|
async function promptContinue() {
|
|
3062
3244
|
console.log();
|
|
3063
|
-
await
|
|
3245
|
+
await inquirer7.prompt([
|
|
3064
3246
|
{
|
|
3065
3247
|
type: "input",
|
|
3066
3248
|
name: "continue",
|
|
@@ -3126,15 +3308,15 @@ program.command("setup-labels").description("Setup GitHub labels for AI workflow
|
|
|
3126
3308
|
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
|
|
3127
3309
|
await createCommand(description, { yes: options.yes, provider: options.provider, title: options.title });
|
|
3128
3310
|
});
|
|
3129
|
-
program.command("list").description("List GitHub issues
|
|
3311
|
+
program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option("-s, --status <status>", "Filter by workflow status (ready, in-progress, completed, blocked, all)").option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
|
|
3130
3312
|
await listCommand({
|
|
3131
3313
|
label: options.label,
|
|
3132
3314
|
status: options.status,
|
|
3133
3315
|
limit: parseInt(options.limit, 10)
|
|
3134
3316
|
});
|
|
3135
3317
|
});
|
|
3136
|
-
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-
|
|
3137
|
-
await runCommand(issueNumber, {
|
|
3318
|
+
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (issueNumber, options) => {
|
|
3319
|
+
await runCommand(issueNumber, { provider: options.provider });
|
|
3138
3320
|
});
|
|
3139
3321
|
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("--no-video", "Disable video capture for UI changes").action(async (options) => {
|
|
3140
3322
|
await prCommand({
|