@rotorsoft/gent 1.12.1 → 1.13.1
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 +379 -197
- 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.1",
|
|
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" });
|
|
@@ -2447,7 +2634,7 @@ function getAvailableActions(state) {
|
|
|
2447
2634
|
if (!state.pr && state.commits.length > 0) {
|
|
2448
2635
|
actions.push({ id: "pr", label: "Create pr", shortcut: "C" });
|
|
2449
2636
|
}
|
|
2450
|
-
if (state.issue) {
|
|
2637
|
+
if (state.issue && state.pr?.state !== "merged") {
|
|
2451
2638
|
actions.push({ id: "implement", label: "implement", shortcut: "i" });
|
|
2452
2639
|
}
|
|
2453
2640
|
if (state.pr && state.pr.state === "open") {
|
|
@@ -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;
|
|
@@ -2470,9 +2658,6 @@ var visibleLen = (str) => stripAnsi(str).length;
|
|
|
2470
2658
|
function termWidth() {
|
|
2471
2659
|
return Math.min(process.stdout.columns || 80, 90);
|
|
2472
2660
|
}
|
|
2473
|
-
function termHeight() {
|
|
2474
|
-
return process.stdout.rows || 24;
|
|
2475
|
-
}
|
|
2476
2661
|
function truncate(text, max) {
|
|
2477
2662
|
if (text.length <= max) return text;
|
|
2478
2663
|
return text.slice(0, max - 1) + "\u2026";
|
|
@@ -2582,25 +2767,6 @@ function formatCommandBar(actions, w) {
|
|
|
2582
2767
|
if (cur.length > 0) lines.push(cur);
|
|
2583
2768
|
return lines;
|
|
2584
2769
|
}
|
|
2585
|
-
function renderModal(message) {
|
|
2586
|
-
const w = termWidth();
|
|
2587
|
-
const h = termHeight();
|
|
2588
|
-
const modalW = Math.min(w - 4, Math.max(30, visibleLen(message) + 8));
|
|
2589
|
-
const padX = Math.max(0, Math.floor((w - modalW) / 2));
|
|
2590
|
-
const padY = Math.max(0, Math.floor((h - 5) / 2));
|
|
2591
|
-
const indent = " ".repeat(padX);
|
|
2592
|
-
const inner = modalW - 4;
|
|
2593
|
-
const textPad = Math.max(0, inner - visibleLen(message));
|
|
2594
|
-
const lines = [
|
|
2595
|
-
indent + chalk3.dim("\u250C" + "\u2500".repeat(modalW - 2) + "\u2510"),
|
|
2596
|
-
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2597
|
-
indent + chalk3.dim("\u2502") + " " + chalk3.bold(message) + " ".repeat(textPad) + " " + chalk3.dim("\u2502"),
|
|
2598
|
-
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2599
|
-
indent + chalk3.dim("\u2514" + "\u2500".repeat(modalW - 2) + "\u2518")
|
|
2600
|
-
];
|
|
2601
|
-
for (let i = 0; i < padY; i++) console.log();
|
|
2602
|
-
for (const line of lines) console.log(line);
|
|
2603
|
-
}
|
|
2604
2770
|
function renderActionPanel(title, content) {
|
|
2605
2771
|
const w = termWidth();
|
|
2606
2772
|
console.log(topRow(title, w));
|
|
@@ -2618,11 +2784,11 @@ function renderSettings(state, w) {
|
|
|
2618
2784
|
console.log(row(chalk3.dim("GitHub: ") + ghTag, w));
|
|
2619
2785
|
console.log(row(chalk3.dim("Video: ") + videoTag, w));
|
|
2620
2786
|
}
|
|
2621
|
-
function renderDashboard(state, actions, hint) {
|
|
2787
|
+
function renderDashboard(state, actions, hint, refreshing) {
|
|
2622
2788
|
const w = termWidth();
|
|
2623
2789
|
const descMax = w - 8;
|
|
2624
2790
|
const version2 = getVersion();
|
|
2625
|
-
const titleLabel = `gent v${version2}`;
|
|
2791
|
+
const titleLabel = refreshing ? `gent v${version2} ${chalk3.yellow("Refreshing\u2026")}` : `gent v${version2}`;
|
|
2626
2792
|
console.log(topRow(titleLabel, w));
|
|
2627
2793
|
if (!state.isGitRepo) {
|
|
2628
2794
|
console.log(row(chalk3.red("Not a git repository"), w));
|
|
@@ -2739,7 +2905,7 @@ function clearScreen() {
|
|
|
2739
2905
|
// src/commands/tui.ts
|
|
2740
2906
|
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2741
2907
|
async function confirm(message) {
|
|
2742
|
-
const { ok } = await
|
|
2908
|
+
const { ok } = await inquirer7.prompt([
|
|
2743
2909
|
{
|
|
2744
2910
|
type: "confirm",
|
|
2745
2911
|
name: "ok",
|
|
@@ -2749,12 +2915,6 @@ async function confirm(message) {
|
|
|
2749
2915
|
]);
|
|
2750
2916
|
return ok;
|
|
2751
2917
|
}
|
|
2752
|
-
async function loadState() {
|
|
2753
|
-
clearScreen();
|
|
2754
|
-
renderModal("Loading workflow state...");
|
|
2755
|
-
const state = await aggregateState();
|
|
2756
|
-
return state;
|
|
2757
|
-
}
|
|
2758
2918
|
async function waitForKey(validKeys) {
|
|
2759
2919
|
return new Promise((resolve) => {
|
|
2760
2920
|
const { stdin } = process;
|
|
@@ -2784,25 +2944,19 @@ async function executeAction(actionId, state, providerSetter) {
|
|
|
2784
2944
|
switch (actionId) {
|
|
2785
2945
|
case "quit":
|
|
2786
2946
|
return false;
|
|
2787
|
-
case "list":
|
|
2947
|
+
case "list": {
|
|
2788
2948
|
clearScreen();
|
|
2789
|
-
await listCommand({ status: "ready", limit: 20 });
|
|
2790
|
-
await promptContinue();
|
|
2791
|
-
return true;
|
|
2792
|
-
case "run-auto": {
|
|
2793
|
-
clearScreen();
|
|
2794
|
-
if (!await confirm("Start AI agent to implement next ticket?")) return true;
|
|
2795
2949
|
try {
|
|
2796
|
-
await
|
|
2950
|
+
await listCommand({});
|
|
2797
2951
|
} catch (error) {
|
|
2798
|
-
logger.error(`
|
|
2799
|
-
await promptContinue();
|
|
2952
|
+
logger.error(`List failed: ${error}`);
|
|
2800
2953
|
}
|
|
2801
|
-
|
|
2954
|
+
await promptContinue();
|
|
2955
|
+
return true;
|
|
2802
2956
|
}
|
|
2803
2957
|
case "create": {
|
|
2804
2958
|
clearScreen();
|
|
2805
|
-
const { description } = await
|
|
2959
|
+
const { description } = await inquirer7.prompt([
|
|
2806
2960
|
{
|
|
2807
2961
|
type: "input",
|
|
2808
2962
|
name: "description",
|
|
@@ -2936,7 +3090,7 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
|
|
|
2936
3090
|
} catch {
|
|
2937
3091
|
logger.warning("AI commit message generation failed");
|
|
2938
3092
|
console.log();
|
|
2939
|
-
const { message } = await
|
|
3093
|
+
const { message } = await inquirer7.prompt([
|
|
2940
3094
|
{
|
|
2941
3095
|
type: "input",
|
|
2942
3096
|
name: "message",
|
|
@@ -3005,7 +3159,7 @@ async function handlePush() {
|
|
|
3005
3159
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3006
3160
|
async function handleSwitchProvider(state, setProvider) {
|
|
3007
3161
|
const current = state.config.ai.provider;
|
|
3008
|
-
const { provider } = await
|
|
3162
|
+
const { provider } = await inquirer7.prompt([
|
|
3009
3163
|
{
|
|
3010
3164
|
type: "list",
|
|
3011
3165
|
name: "provider",
|
|
@@ -3060,7 +3214,7 @@ async function handleVideoCapture(state) {
|
|
|
3060
3214
|
}
|
|
3061
3215
|
async function promptContinue() {
|
|
3062
3216
|
console.log();
|
|
3063
|
-
await
|
|
3217
|
+
await inquirer7.prompt([
|
|
3064
3218
|
{
|
|
3065
3219
|
type: "input",
|
|
3066
3220
|
name: "continue",
|
|
@@ -3071,16 +3225,44 @@ async function promptContinue() {
|
|
|
3071
3225
|
async function tuiCommand() {
|
|
3072
3226
|
let running = true;
|
|
3073
3227
|
let providerOverride = null;
|
|
3228
|
+
let lastActions = [];
|
|
3074
3229
|
const setProvider = (p) => {
|
|
3075
3230
|
providerOverride = p;
|
|
3076
3231
|
};
|
|
3232
|
+
const config = loadConfig();
|
|
3233
|
+
let lastState = {
|
|
3234
|
+
isGitRepo: true,
|
|
3235
|
+
isGhAuthenticated: true,
|
|
3236
|
+
isAIProviderAvailable: true,
|
|
3237
|
+
config,
|
|
3238
|
+
hasConfig: true,
|
|
3239
|
+
hasProgress: false,
|
|
3240
|
+
branch: "",
|
|
3241
|
+
branchInfo: null,
|
|
3242
|
+
isOnMain: true,
|
|
3243
|
+
hasUncommittedChanges: false,
|
|
3244
|
+
hasUnpushedCommits: false,
|
|
3245
|
+
commits: [],
|
|
3246
|
+
baseBranch: "main",
|
|
3247
|
+
issue: null,
|
|
3248
|
+
workflowStatus: "none",
|
|
3249
|
+
pr: null,
|
|
3250
|
+
reviewFeedback: [],
|
|
3251
|
+
hasActionableFeedback: false,
|
|
3252
|
+
hasUIChanges: false,
|
|
3253
|
+
isPlaywrightAvailable: false
|
|
3254
|
+
};
|
|
3077
3255
|
while (running) {
|
|
3078
|
-
|
|
3256
|
+
clearScreen();
|
|
3257
|
+
renderDashboard(lastState, lastActions, void 0, true);
|
|
3258
|
+
const state = await aggregateState();
|
|
3079
3259
|
if (providerOverride) {
|
|
3080
3260
|
state.config.ai.provider = providerOverride;
|
|
3081
3261
|
}
|
|
3082
|
-
clearScreen();
|
|
3083
3262
|
const actions = getAvailableActions(state);
|
|
3263
|
+
lastState = state;
|
|
3264
|
+
lastActions = actions;
|
|
3265
|
+
clearScreen();
|
|
3084
3266
|
let hint;
|
|
3085
3267
|
if (state.isOnMain) {
|
|
3086
3268
|
hint = "Select an action to get started";
|
|
@@ -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({
|