@rotorsoft/gent 1.12.0 → 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-TLGYE7J2.js → chunk-KLHUMY5L.js} +21 -16
- package/dist/{chunk-TLGYE7J2.js.map → chunk-KLHUMY5L.js.map} +1 -1
- package/dist/index.js +452 -190
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-FLCGVPFY.js → setup-labels-3ANC76NF.js} +2 -2
- package/package.json +1 -1
- /package/dist/{setup-labels-FLCGVPFY.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,
|
|
@@ -33,10 +33,9 @@ import {
|
|
|
33
33
|
sanitizeSlug,
|
|
34
34
|
setupLabelsCommand,
|
|
35
35
|
sortByPriority,
|
|
36
|
-
updateConfigProvider,
|
|
37
36
|
updateIssueLabels,
|
|
38
37
|
withSpinner
|
|
39
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-KLHUMY5L.js";
|
|
40
39
|
|
|
41
40
|
// src/index.ts
|
|
42
41
|
import { Command } from "commander";
|
|
@@ -197,7 +196,7 @@ async function initCommand(options) {
|
|
|
197
196
|
}
|
|
198
197
|
]);
|
|
199
198
|
if (setupLabels) {
|
|
200
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
199
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-3ANC76NF.js");
|
|
201
200
|
await setupLabelsCommand2();
|
|
202
201
|
}
|
|
203
202
|
}
|
|
@@ -473,7 +472,7 @@ META:type=<type>,priority=<priority>,risk=<risk>,area=<area>
|
|
|
473
472
|
Example: META:type=feature,priority=high,risk=low,area=ui`;
|
|
474
473
|
return basePrompt;
|
|
475
474
|
}
|
|
476
|
-
function buildImplementationPrompt(issue, agentInstructions, progressContent, config,
|
|
475
|
+
function buildImplementationPrompt(issue, agentInstructions, progressContent, config, extraContext = null) {
|
|
477
476
|
const providerName = getProviderDisplayName(config.ai.provider);
|
|
478
477
|
const providerEmail = getProviderEmail(config.ai.provider);
|
|
479
478
|
return `GitHub Issue #${issue.number}: ${issue.title}
|
|
@@ -488,8 +487,7 @@ ${progressContent ? `## Previous Progress
|
|
|
488
487
|
${progressContent}
|
|
489
488
|
|
|
490
489
|
` : ""}
|
|
491
|
-
${
|
|
492
|
-
${reviewFeedback}
|
|
490
|
+
${extraContext ? `${extraContext}
|
|
493
491
|
|
|
494
492
|
` : ""}
|
|
495
493
|
|
|
@@ -664,11 +662,11 @@ async function createCommand(description, options) {
|
|
|
664
662
|
]);
|
|
665
663
|
if (!ghAuth) {
|
|
666
664
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
667
|
-
|
|
665
|
+
return;
|
|
668
666
|
}
|
|
669
667
|
if (!aiOk) {
|
|
670
668
|
logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
|
|
671
|
-
|
|
669
|
+
return;
|
|
672
670
|
}
|
|
673
671
|
const agentInstructions = loadAgentInstructions();
|
|
674
672
|
let aiOutput;
|
|
@@ -810,105 +808,6 @@ Next steps:
|
|
|
810
808
|
|
|
811
809
|
// src/commands/list.ts
|
|
812
810
|
import chalk2 from "chalk";
|
|
813
|
-
async function listCommand(options) {
|
|
814
|
-
const isAuthed = await checkGhAuth();
|
|
815
|
-
if (!isAuthed) {
|
|
816
|
-
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
817
|
-
process.exit(1);
|
|
818
|
-
}
|
|
819
|
-
const config = loadConfig();
|
|
820
|
-
const workflowLabels = getWorkflowLabels(config);
|
|
821
|
-
const labels = [];
|
|
822
|
-
if (options.label) {
|
|
823
|
-
labels.push(options.label);
|
|
824
|
-
}
|
|
825
|
-
if (options.status && options.status !== "all") {
|
|
826
|
-
switch (options.status) {
|
|
827
|
-
case "ready":
|
|
828
|
-
labels.push(workflowLabels.ready);
|
|
829
|
-
break;
|
|
830
|
-
case "in-progress":
|
|
831
|
-
labels.push(workflowLabels.inProgress);
|
|
832
|
-
break;
|
|
833
|
-
case "completed":
|
|
834
|
-
labels.push(workflowLabels.completed);
|
|
835
|
-
break;
|
|
836
|
-
case "blocked":
|
|
837
|
-
labels.push(workflowLabels.blocked);
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
} else if (!options.status) {
|
|
841
|
-
labels.push(workflowLabels.ready);
|
|
842
|
-
}
|
|
843
|
-
let issues;
|
|
844
|
-
try {
|
|
845
|
-
issues = await listIssues({
|
|
846
|
-
labels: labels.length > 0 ? labels : void 0,
|
|
847
|
-
state: "open",
|
|
848
|
-
limit: options.limit || 20
|
|
849
|
-
});
|
|
850
|
-
} catch (error) {
|
|
851
|
-
logger.error(`Failed to fetch issues: ${error}`);
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
if (issues.length === 0) {
|
|
855
|
-
logger.info("No issues found matching the criteria.");
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
sortByPriority(issues);
|
|
859
|
-
logger.bold(`Found ${issues.length} issue(s):`);
|
|
860
|
-
logger.newline();
|
|
861
|
-
for (const issue of issues) {
|
|
862
|
-
const type = extractTypeFromLabels(issue.labels);
|
|
863
|
-
const priority = extractPriorityFromLabels(issue.labels);
|
|
864
|
-
const status = getIssueStatus(issue.labels, workflowLabels);
|
|
865
|
-
const priorityColor = getPriorityColor(priority);
|
|
866
|
-
const statusColor = getStatusColor(status);
|
|
867
|
-
console.log(
|
|
868
|
-
` ${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 ? "..." : "")
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
logger.newline();
|
|
872
|
-
logger.dim(`Run ${colors.command("gent run <issue-number>")} to implement an issue`);
|
|
873
|
-
logger.dim(`Run ${colors.command("gent run --auto")} to auto-select highest priority`);
|
|
874
|
-
}
|
|
875
|
-
function getIssueStatus(labels, workflowLabels) {
|
|
876
|
-
if (labels.includes(workflowLabels.ready)) return "ready";
|
|
877
|
-
if (labels.includes(workflowLabels.inProgress)) return "in-progress";
|
|
878
|
-
if (labels.includes(workflowLabels.completed)) return "completed";
|
|
879
|
-
if (labels.includes(workflowLabels.blocked)) return "blocked";
|
|
880
|
-
return "unknown";
|
|
881
|
-
}
|
|
882
|
-
function getPriorityColor(priority) {
|
|
883
|
-
switch (priority) {
|
|
884
|
-
case "critical":
|
|
885
|
-
return chalk2.red;
|
|
886
|
-
case "high":
|
|
887
|
-
return chalk2.yellow;
|
|
888
|
-
case "medium":
|
|
889
|
-
return chalk2.blue;
|
|
890
|
-
case "low":
|
|
891
|
-
return chalk2.green;
|
|
892
|
-
default:
|
|
893
|
-
return chalk2.gray;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
function getStatusColor(status) {
|
|
897
|
-
switch (status) {
|
|
898
|
-
case "ready":
|
|
899
|
-
return chalk2.green;
|
|
900
|
-
case "in-progress":
|
|
901
|
-
return chalk2.yellow;
|
|
902
|
-
case "completed":
|
|
903
|
-
return chalk2.blue;
|
|
904
|
-
case "blocked":
|
|
905
|
-
return chalk2.red;
|
|
906
|
-
default:
|
|
907
|
-
return chalk2.gray;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// src/commands/run.ts
|
|
912
811
|
import inquirer3 from "inquirer";
|
|
913
812
|
|
|
914
813
|
// src/lib/git.ts
|
|
@@ -1030,6 +929,22 @@ async function getLastCommitTimestamp() {
|
|
|
1030
929
|
const { stdout } = await execa2("git", ["log", "-1", "--format=%cI"]);
|
|
1031
930
|
return stdout.trim();
|
|
1032
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
|
+
}
|
|
1033
948
|
|
|
1034
949
|
// src/lib/branch.ts
|
|
1035
950
|
async function generateBranchName(config, issueNumber, issueTitle, type) {
|
|
@@ -1102,7 +1017,308 @@ function extractIssueNumber(branchName) {
|
|
|
1102
1017
|
return info?.issueNumber ?? null;
|
|
1103
1018
|
}
|
|
1104
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
|
+
|
|
1105
1320
|
// src/commands/run.ts
|
|
1321
|
+
import inquirer4 from "inquirer";
|
|
1106
1322
|
async function runCommand(issueNumberArg, options) {
|
|
1107
1323
|
logger.bold("Running AI implementation workflow...");
|
|
1108
1324
|
logger.newline();
|
|
@@ -1115,16 +1331,16 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1115
1331
|
]);
|
|
1116
1332
|
if (!ghAuth) {
|
|
1117
1333
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
1118
|
-
|
|
1334
|
+
return;
|
|
1119
1335
|
}
|
|
1120
1336
|
if (!aiOk) {
|
|
1121
1337
|
logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
|
|
1122
|
-
|
|
1338
|
+
return;
|
|
1123
1339
|
}
|
|
1124
1340
|
const hasChanges = await hasUncommittedChanges();
|
|
1125
1341
|
if (hasChanges) {
|
|
1126
1342
|
logger.warning("You have uncommitted changes.");
|
|
1127
|
-
const { proceed } = await
|
|
1343
|
+
const { proceed } = await inquirer4.prompt([
|
|
1128
1344
|
{
|
|
1129
1345
|
type: "confirm",
|
|
1130
1346
|
name: "proceed",
|
|
@@ -1134,28 +1350,20 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1134
1350
|
]);
|
|
1135
1351
|
if (!proceed) {
|
|
1136
1352
|
logger.info("Aborting. Please commit or stash your changes first.");
|
|
1137
|
-
|
|
1353
|
+
return;
|
|
1138
1354
|
}
|
|
1139
1355
|
}
|
|
1140
1356
|
const workflowLabels = getWorkflowLabels(config);
|
|
1141
1357
|
let issueNumber;
|
|
1142
|
-
if (
|
|
1143
|
-
const autoIssue = await autoSelectIssue(workflowLabels.ready);
|
|
1144
|
-
if (!autoIssue) {
|
|
1145
|
-
logger.error("No ai-ready issues found.");
|
|
1146
|
-
process.exit(1);
|
|
1147
|
-
}
|
|
1148
|
-
issueNumber = autoIssue.number;
|
|
1149
|
-
logger.info(`Auto-selected: ${colors.issue(`#${issueNumber}`)} - ${autoIssue.title}`);
|
|
1150
|
-
} else if (issueNumberArg) {
|
|
1358
|
+
if (issueNumberArg) {
|
|
1151
1359
|
if (!isValidIssueNumber(issueNumberArg)) {
|
|
1152
1360
|
logger.error("Invalid issue number.");
|
|
1153
|
-
|
|
1361
|
+
return;
|
|
1154
1362
|
}
|
|
1155
1363
|
issueNumber = parseInt(issueNumberArg, 10);
|
|
1156
1364
|
} else {
|
|
1157
|
-
logger.error("Please provide an issue number
|
|
1158
|
-
|
|
1365
|
+
logger.error("Please provide an issue number. Use 'gent switch' to browse tickets.");
|
|
1366
|
+
return;
|
|
1159
1367
|
}
|
|
1160
1368
|
let issue;
|
|
1161
1369
|
try {
|
|
@@ -1168,7 +1376,7 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1168
1376
|
}
|
|
1169
1377
|
if (!issue.labels.includes(workflowLabels.ready)) {
|
|
1170
1378
|
logger.warning(`Issue #${issueNumber} does not have the '${workflowLabels.ready}' label.`);
|
|
1171
|
-
const { proceed } = await
|
|
1379
|
+
const { proceed } = await inquirer4.prompt([
|
|
1172
1380
|
{
|
|
1173
1381
|
type: "confirm",
|
|
1174
1382
|
name: "proceed",
|
|
@@ -1190,7 +1398,7 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1190
1398
|
const onMain = await isOnMainBranch();
|
|
1191
1399
|
if (await branchExists(branchName)) {
|
|
1192
1400
|
logger.info(`Branch ${colors.branch(branchName)} already exists.`);
|
|
1193
|
-
const { action } = await
|
|
1401
|
+
const { action } = await inquirer4.prompt([
|
|
1194
1402
|
{
|
|
1195
1403
|
type: "list",
|
|
1196
1404
|
name: "action",
|
|
@@ -1212,7 +1420,7 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1212
1420
|
} else {
|
|
1213
1421
|
if (!onMain) {
|
|
1214
1422
|
logger.warning(`Not on main branch (currently on ${colors.branch(currentBranch)}).`);
|
|
1215
|
-
const { fromMain } = await
|
|
1423
|
+
const { fromMain } = await inquirer4.prompt([
|
|
1216
1424
|
{
|
|
1217
1425
|
type: "confirm",
|
|
1218
1426
|
name: "fromMain",
|
|
@@ -1333,31 +1541,9 @@ No commits were created. Please retry later.`
|
|
|
1333
1541
|
3. Push branch: ${colors.command("git push -u origin " + branchName)}
|
|
1334
1542
|
4. Create PR: ${colors.command("gent pr")}`);
|
|
1335
1543
|
}
|
|
1336
|
-
async function autoSelectIssue(readyLabel) {
|
|
1337
|
-
let issues = await listIssues({
|
|
1338
|
-
labels: [readyLabel, "priority:critical"],
|
|
1339
|
-
state: "open",
|
|
1340
|
-
limit: 1
|
|
1341
|
-
});
|
|
1342
|
-
if (issues.length > 0) return issues[0];
|
|
1343
|
-
issues = await listIssues({
|
|
1344
|
-
labels: [readyLabel, "priority:high"],
|
|
1345
|
-
state: "open",
|
|
1346
|
-
limit: 1
|
|
1347
|
-
});
|
|
1348
|
-
if (issues.length > 0) return issues[0];
|
|
1349
|
-
issues = await listIssues({
|
|
1350
|
-
labels: [readyLabel],
|
|
1351
|
-
state: "open",
|
|
1352
|
-
limit: 10
|
|
1353
|
-
});
|
|
1354
|
-
if (issues.length === 0) return null;
|
|
1355
|
-
sortByPriority(issues);
|
|
1356
|
-
return issues[0];
|
|
1357
|
-
}
|
|
1358
1544
|
|
|
1359
1545
|
// src/commands/pr.ts
|
|
1360
|
-
import
|
|
1546
|
+
import inquirer5 from "inquirer";
|
|
1361
1547
|
|
|
1362
1548
|
// src/lib/playwright.ts
|
|
1363
1549
|
import { execa as execa3 } from "execa";
|
|
@@ -1438,7 +1624,7 @@ async function prCommand(options) {
|
|
|
1438
1624
|
const hasUnpushed = await getUnpushedCommits();
|
|
1439
1625
|
if (hasUnpushed) {
|
|
1440
1626
|
logger.warning("Branch has unpushed commits.");
|
|
1441
|
-
const { push } = await
|
|
1627
|
+
const { push } = await inquirer5.prompt([
|
|
1442
1628
|
{
|
|
1443
1629
|
type: "confirm",
|
|
1444
1630
|
name: "push",
|
|
@@ -1575,7 +1761,7 @@ function generateFallbackBody(issue, commits) {
|
|
|
1575
1761
|
}
|
|
1576
1762
|
|
|
1577
1763
|
// src/commands/fix.ts
|
|
1578
|
-
import
|
|
1764
|
+
import inquirer6 from "inquirer";
|
|
1579
1765
|
|
|
1580
1766
|
// src/lib/review-feedback.ts
|
|
1581
1767
|
var ACTIONABLE_KEYWORDS = [
|
|
@@ -1766,7 +1952,7 @@ async function fixCommand(options) {
|
|
|
1766
1952
|
const hasChanges = await hasUncommittedChanges();
|
|
1767
1953
|
if (hasChanges) {
|
|
1768
1954
|
logger.warning("You have uncommitted changes.");
|
|
1769
|
-
const { proceed } = await
|
|
1955
|
+
const { proceed } = await inquirer6.prompt([
|
|
1770
1956
|
{
|
|
1771
1957
|
type: "confirm",
|
|
1772
1958
|
name: "proceed",
|
|
@@ -1814,7 +2000,8 @@ async function fixCommand(options) {
|
|
|
1814
2000
|
});
|
|
1815
2001
|
const agentInstructions = loadAgentInstructions();
|
|
1816
2002
|
const progressContent = readProgress(config);
|
|
1817
|
-
const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config,
|
|
2003
|
+
const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config, `## Review Feedback
|
|
2004
|
+
${summary}`);
|
|
1818
2005
|
logger.newline();
|
|
1819
2006
|
logger.info(`Starting ${colors.provider(providerName)} fix session...`);
|
|
1820
2007
|
logger.dim("Review feedback will be appended to the implementation prompt.");
|
|
@@ -1894,7 +2081,7 @@ import { homedir } from "os";
|
|
|
1894
2081
|
// package.json
|
|
1895
2082
|
var package_default = {
|
|
1896
2083
|
name: "@rotorsoft/gent",
|
|
1897
|
-
version: "1.
|
|
2084
|
+
version: "1.13.0",
|
|
1898
2085
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
1899
2086
|
keywords: [
|
|
1900
2087
|
"cli",
|
|
@@ -2304,7 +2491,7 @@ async function statusCommand() {
|
|
|
2304
2491
|
}
|
|
2305
2492
|
|
|
2306
2493
|
// src/commands/tui.ts
|
|
2307
|
-
import
|
|
2494
|
+
import inquirer7 from "inquirer";
|
|
2308
2495
|
import { execa as execa4 } from "execa";
|
|
2309
2496
|
|
|
2310
2497
|
// src/tui/state.ts
|
|
@@ -2433,7 +2620,6 @@ function getAvailableActions(state) {
|
|
|
2433
2620
|
}
|
|
2434
2621
|
if (state.isOnMain) {
|
|
2435
2622
|
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2436
|
-
actions.push({ id: "run-auto", label: "run next", shortcut: "r" });
|
|
2437
2623
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2438
2624
|
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2439
2625
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
@@ -2442,16 +2628,16 @@ function getAvailableActions(state) {
|
|
|
2442
2628
|
if (state.hasUncommittedChanges) {
|
|
2443
2629
|
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2444
2630
|
}
|
|
2445
|
-
if (state.hasUnpushedCommits) {
|
|
2631
|
+
if (state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2446
2632
|
actions.push({ id: "push", label: "Push", shortcut: "P" });
|
|
2447
2633
|
}
|
|
2448
2634
|
if (!state.pr && state.commits.length > 0) {
|
|
2449
2635
|
actions.push({ id: "pr", label: "Create pr", shortcut: "C" });
|
|
2450
2636
|
}
|
|
2637
|
+
if (state.issue) {
|
|
2638
|
+
actions.push({ id: "implement", label: "implement", shortcut: "i" });
|
|
2639
|
+
}
|
|
2451
2640
|
if (state.pr && state.pr.state === "open") {
|
|
2452
|
-
if (state.hasActionableFeedback) {
|
|
2453
|
-
actions.push({ id: "fix", label: "fix feedback", shortcut: "f" });
|
|
2454
|
-
}
|
|
2455
2641
|
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled) {
|
|
2456
2642
|
actions.push({ id: "video", label: "video", shortcut: "v" });
|
|
2457
2643
|
}
|
|
@@ -2459,6 +2645,7 @@ function getAvailableActions(state) {
|
|
|
2459
2645
|
if (state.pr && (state.pr.state === "merged" || state.pr.state === "closed")) {
|
|
2460
2646
|
actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
|
|
2461
2647
|
}
|
|
2648
|
+
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2462
2649
|
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2463
2650
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2464
2651
|
return actions;
|
|
@@ -2602,6 +2789,14 @@ function renderModal(message) {
|
|
|
2602
2789
|
for (let i = 0; i < padY; i++) console.log();
|
|
2603
2790
|
for (const line of lines) console.log(line);
|
|
2604
2791
|
}
|
|
2792
|
+
function renderActionPanel(title, content) {
|
|
2793
|
+
const w = termWidth();
|
|
2794
|
+
console.log(topRow(title, w));
|
|
2795
|
+
for (const line of content) {
|
|
2796
|
+
console.log(row(line, w));
|
|
2797
|
+
}
|
|
2798
|
+
console.log(botRow(w));
|
|
2799
|
+
}
|
|
2605
2800
|
function renderSettings(state, w) {
|
|
2606
2801
|
const provider = getProviderDisplayName(state.config.ai.provider);
|
|
2607
2802
|
const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
|
|
@@ -2732,7 +2927,7 @@ function clearScreen() {
|
|
|
2732
2927
|
// src/commands/tui.ts
|
|
2733
2928
|
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2734
2929
|
async function confirm(message) {
|
|
2735
|
-
const { ok } = await
|
|
2930
|
+
const { ok } = await inquirer7.prompt([
|
|
2736
2931
|
{
|
|
2737
2932
|
type: "confirm",
|
|
2738
2933
|
name: "ok",
|
|
@@ -2773,24 +2968,23 @@ async function waitForKey(validKeys) {
|
|
|
2773
2968
|
stdin.on("data", onData);
|
|
2774
2969
|
});
|
|
2775
2970
|
}
|
|
2776
|
-
async function executeAction(actionId, state) {
|
|
2971
|
+
async function executeAction(actionId, state, providerSetter) {
|
|
2777
2972
|
switch (actionId) {
|
|
2778
2973
|
case "quit":
|
|
2779
2974
|
return false;
|
|
2780
|
-
case "list":
|
|
2975
|
+
case "list": {
|
|
2781
2976
|
clearScreen();
|
|
2782
|
-
|
|
2977
|
+
try {
|
|
2978
|
+
await listCommand({});
|
|
2979
|
+
} catch (error) {
|
|
2980
|
+
logger.error(`List failed: ${error}`);
|
|
2981
|
+
}
|
|
2783
2982
|
await promptContinue();
|
|
2784
2983
|
return true;
|
|
2785
|
-
case "run-auto": {
|
|
2786
|
-
clearScreen();
|
|
2787
|
-
if (!await confirm("Start AI agent to implement next ticket?")) return true;
|
|
2788
|
-
await runCommand(void 0, { auto: true });
|
|
2789
|
-
return false;
|
|
2790
2984
|
}
|
|
2791
2985
|
case "create": {
|
|
2792
2986
|
clearScreen();
|
|
2793
|
-
const { description } = await
|
|
2987
|
+
const { description } = await inquirer7.prompt([
|
|
2794
2988
|
{
|
|
2795
2989
|
type: "input",
|
|
2796
2990
|
name: "description",
|
|
@@ -2801,7 +2995,11 @@ async function executeAction(actionId, state) {
|
|
|
2801
2995
|
logger.info("Cancelled");
|
|
2802
2996
|
return true;
|
|
2803
2997
|
}
|
|
2804
|
-
|
|
2998
|
+
try {
|
|
2999
|
+
await createCommand(description, {});
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
logger.error(`Create failed: ${error}`);
|
|
3002
|
+
}
|
|
2805
3003
|
await promptContinue();
|
|
2806
3004
|
return true;
|
|
2807
3005
|
}
|
|
@@ -2822,10 +3020,20 @@ async function executeAction(actionId, state) {
|
|
|
2822
3020
|
await promptContinue();
|
|
2823
3021
|
return true;
|
|
2824
3022
|
}
|
|
2825
|
-
case "
|
|
3023
|
+
case "implement": {
|
|
2826
3024
|
clearScreen();
|
|
2827
|
-
|
|
2828
|
-
|
|
3025
|
+
const hasCommits = state.commits.length > 0;
|
|
3026
|
+
const hasFeedback = state.hasActionableFeedback;
|
|
3027
|
+
let msg;
|
|
3028
|
+
if (hasFeedback && hasCommits) {
|
|
3029
|
+
msg = "Start AI agent to address review feedback?";
|
|
3030
|
+
} else if (hasCommits) {
|
|
3031
|
+
msg = "Start AI agent to continue implementation from existing commits?";
|
|
3032
|
+
} else {
|
|
3033
|
+
msg = "Start AI agent to implement this ticket from scratch?";
|
|
3034
|
+
}
|
|
3035
|
+
if (!await confirm(msg)) return true;
|
|
3036
|
+
await handleImplement(state);
|
|
2829
3037
|
return false;
|
|
2830
3038
|
}
|
|
2831
3039
|
case "video": {
|
|
@@ -2837,7 +3045,7 @@ async function executeAction(actionId, state) {
|
|
|
2837
3045
|
}
|
|
2838
3046
|
case "switch-provider":
|
|
2839
3047
|
clearScreen();
|
|
2840
|
-
await handleSwitchProvider(state);
|
|
3048
|
+
await handleSwitchProvider(state, providerSetter);
|
|
2841
3049
|
return true;
|
|
2842
3050
|
case "checkout-main": {
|
|
2843
3051
|
clearScreen();
|
|
@@ -2910,7 +3118,7 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
|
|
|
2910
3118
|
} catch {
|
|
2911
3119
|
logger.warning("AI commit message generation failed");
|
|
2912
3120
|
console.log();
|
|
2913
|
-
const { message } = await
|
|
3121
|
+
const { message } = await inquirer7.prompt([
|
|
2914
3122
|
{
|
|
2915
3123
|
type: "input",
|
|
2916
3124
|
name: "message",
|
|
@@ -2920,6 +3128,50 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
|
|
|
2920
3128
|
return message.trim() || CANCEL;
|
|
2921
3129
|
}
|
|
2922
3130
|
}
|
|
3131
|
+
async function handleImplement(state) {
|
|
3132
|
+
if (!state.issue) {
|
|
3133
|
+
logger.error("No linked issue found");
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
const agentInstructions = loadAgentInstructions();
|
|
3137
|
+
const progressContent = readProgress(state.config);
|
|
3138
|
+
const contextParts = [];
|
|
3139
|
+
if (state.commits.length > 0) {
|
|
3140
|
+
contextParts.push(
|
|
3141
|
+
`## Current Progress
|
|
3142
|
+
There are ${state.commits.length} existing commit(s) on this branch:
|
|
3143
|
+
${state.commits.map((c) => `- ${c}`).join("\n")}
|
|
3144
|
+
|
|
3145
|
+
Continue the implementation from where it left off. Review the existing work and complete any remaining tasks.`
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
if (state.hasActionableFeedback && state.reviewFeedback.length > 0) {
|
|
3149
|
+
const feedbackLines = state.reviewFeedback.map((f) => `- [${f.source}] ${f.body.slice(0, 200)}`).join("\n");
|
|
3150
|
+
contextParts.push(`## Review Feedback
|
|
3151
|
+
${feedbackLines}`);
|
|
3152
|
+
}
|
|
3153
|
+
const extraContext = contextParts.length > 0 ? contextParts.join("\n\n") : null;
|
|
3154
|
+
const prompt = buildImplementationPrompt(
|
|
3155
|
+
state.issue,
|
|
3156
|
+
agentInstructions,
|
|
3157
|
+
progressContent,
|
|
3158
|
+
state.config,
|
|
3159
|
+
extraContext
|
|
3160
|
+
);
|
|
3161
|
+
const providerName = getProviderDisplayName(state.config.ai.provider);
|
|
3162
|
+
clearScreen();
|
|
3163
|
+
renderActionPanel(`${providerName} Session`, [
|
|
3164
|
+
`Implementing: #${state.issue.number} ${state.issue.title}`,
|
|
3165
|
+
state.commits.length > 0 ? `Continuing from ${state.commits.length} existing commit(s)` : "Starting fresh implementation",
|
|
3166
|
+
...state.hasActionableFeedback ? [`Includes ${state.reviewFeedback.length} review feedback item(s)`] : []
|
|
3167
|
+
]);
|
|
3168
|
+
console.log();
|
|
3169
|
+
try {
|
|
3170
|
+
await invokeAIInteractive(prompt, state.config);
|
|
3171
|
+
} catch (error) {
|
|
3172
|
+
logger.error(`${providerName} session failed: ${error}`);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
2923
3175
|
async function handlePush() {
|
|
2924
3176
|
try {
|
|
2925
3177
|
const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
|
|
@@ -2933,9 +3185,9 @@ async function handlePush() {
|
|
|
2933
3185
|
}
|
|
2934
3186
|
}
|
|
2935
3187
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
2936
|
-
async function handleSwitchProvider(state) {
|
|
3188
|
+
async function handleSwitchProvider(state, setProvider) {
|
|
2937
3189
|
const current = state.config.ai.provider;
|
|
2938
|
-
const { provider } = await
|
|
3190
|
+
const { provider } = await inquirer7.prompt([
|
|
2939
3191
|
{
|
|
2940
3192
|
type: "list",
|
|
2941
3193
|
name: "provider",
|
|
@@ -2948,12 +3200,8 @@ async function handleSwitchProvider(state) {
|
|
|
2948
3200
|
}
|
|
2949
3201
|
]);
|
|
2950
3202
|
if (provider === current) return;
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
logger.success(`Provider switched to ${provider}`);
|
|
2954
|
-
} catch (error) {
|
|
2955
|
-
logger.error(`Failed to switch provider: ${error}`);
|
|
2956
|
-
}
|
|
3203
|
+
setProvider(provider);
|
|
3204
|
+
logger.success(`Provider switched to ${provider} (session only)`);
|
|
2957
3205
|
}
|
|
2958
3206
|
async function handleCheckoutMain() {
|
|
2959
3207
|
try {
|
|
@@ -2971,6 +3219,13 @@ async function handleVideoCapture(state) {
|
|
|
2971
3219
|
logger.error("No linked issue found");
|
|
2972
3220
|
return;
|
|
2973
3221
|
}
|
|
3222
|
+
const providerName = getProviderDisplayName(state.config.ai.provider);
|
|
3223
|
+
clearScreen();
|
|
3224
|
+
renderActionPanel("Video Capture", [
|
|
3225
|
+
`Recording: #${state.issue.number} ${state.issue.title}`,
|
|
3226
|
+
`Provider: ${providerName}`
|
|
3227
|
+
]);
|
|
3228
|
+
console.log();
|
|
2974
3229
|
try {
|
|
2975
3230
|
const agentInstructions = loadAgentInstructions();
|
|
2976
3231
|
const videoPrompt = buildVideoPrompt(
|
|
@@ -2987,7 +3242,7 @@ async function handleVideoCapture(state) {
|
|
|
2987
3242
|
}
|
|
2988
3243
|
async function promptContinue() {
|
|
2989
3244
|
console.log();
|
|
2990
|
-
await
|
|
3245
|
+
await inquirer7.prompt([
|
|
2991
3246
|
{
|
|
2992
3247
|
type: "input",
|
|
2993
3248
|
name: "continue",
|
|
@@ -2997,8 +3252,15 @@ async function promptContinue() {
|
|
|
2997
3252
|
}
|
|
2998
3253
|
async function tuiCommand() {
|
|
2999
3254
|
let running = true;
|
|
3255
|
+
let providerOverride = null;
|
|
3256
|
+
const setProvider = (p) => {
|
|
3257
|
+
providerOverride = p;
|
|
3258
|
+
};
|
|
3000
3259
|
while (running) {
|
|
3001
3260
|
const state = await loadState();
|
|
3261
|
+
if (providerOverride) {
|
|
3262
|
+
state.config.ai.provider = providerOverride;
|
|
3263
|
+
}
|
|
3002
3264
|
clearScreen();
|
|
3003
3265
|
const actions = getAvailableActions(state);
|
|
3004
3266
|
let hint;
|
|
@@ -3014,7 +3276,7 @@ async function tuiCommand() {
|
|
|
3014
3276
|
const key = await waitForKey(validKeys);
|
|
3015
3277
|
const action = actions.find((a) => a.shortcut === key);
|
|
3016
3278
|
if (action) {
|
|
3017
|
-
running = await executeAction(action.id, state);
|
|
3279
|
+
running = await executeAction(action.id, state, setProvider);
|
|
3018
3280
|
}
|
|
3019
3281
|
}
|
|
3020
3282
|
}
|
|
@@ -3046,15 +3308,15 @@ program.command("setup-labels").description("Setup GitHub labels for AI workflow
|
|
|
3046
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) => {
|
|
3047
3309
|
await createCommand(description, { yes: options.yes, provider: options.provider, title: options.title });
|
|
3048
3310
|
});
|
|
3049
|
-
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) => {
|
|
3050
3312
|
await listCommand({
|
|
3051
3313
|
label: options.label,
|
|
3052
3314
|
status: options.status,
|
|
3053
3315
|
limit: parseInt(options.limit, 10)
|
|
3054
3316
|
});
|
|
3055
3317
|
});
|
|
3056
|
-
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-
|
|
3057
|
-
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 });
|
|
3058
3320
|
});
|
|
3059
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) => {
|
|
3060
3322
|
await prCommand({
|