@rotorsoft/gent 1.13.1 → 1.13.2
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-KLHUMY5L.js → chunk-SKWCS6Z2.js} +89 -41
- package/dist/chunk-SKWCS6Z2.js.map +1 -0
- package/dist/index.js +346 -144
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-3ANC76NF.js → setup-labels-C6YHJ637.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-KLHUMY5L.js.map +0 -1
- /package/dist/{setup-labels-3ANC76NF.js.map → setup-labels-C6YHJ637.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -30,12 +30,14 @@ import {
|
|
|
30
30
|
loadConfig,
|
|
31
31
|
logger,
|
|
32
32
|
replyToReviewComment,
|
|
33
|
+
resolveProvider,
|
|
33
34
|
sanitizeSlug,
|
|
35
|
+
setRuntimeProvider,
|
|
34
36
|
setupLabelsCommand,
|
|
35
37
|
sortByPriority,
|
|
36
38
|
updateIssueLabels,
|
|
37
39
|
withSpinner
|
|
38
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-SKWCS6Z2.js";
|
|
39
41
|
|
|
40
42
|
// src/index.ts
|
|
41
43
|
import { Command } from "commander";
|
|
@@ -182,11 +184,14 @@ async function initCommand(options) {
|
|
|
182
184
|
initializeProgress(config, cwd);
|
|
183
185
|
logger.success(`Created ${colors.file(config.progress.file)}`);
|
|
184
186
|
logger.newline();
|
|
185
|
-
logger.box(
|
|
187
|
+
logger.box(
|
|
188
|
+
"Setup Complete",
|
|
189
|
+
`Next steps:
|
|
186
190
|
1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
|
|
187
191
|
2. Edit ${colors.file(".gent.yml")} to customize settings
|
|
188
192
|
3. Run ${colors.command("gent setup-labels")} to create GitHub labels
|
|
189
|
-
4. Run ${colors.command("gent create <description>")} to create your first ticket`
|
|
193
|
+
4. Run ${colors.command("gent create <description>")} to create your first ticket`
|
|
194
|
+
);
|
|
190
195
|
const { setupLabels } = await inquirer.prompt([
|
|
191
196
|
{
|
|
192
197
|
type: "confirm",
|
|
@@ -196,7 +201,7 @@ async function initCommand(options) {
|
|
|
196
201
|
}
|
|
197
202
|
]);
|
|
198
203
|
if (setupLabels) {
|
|
199
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
204
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-C6YHJ637.js");
|
|
200
205
|
await setupLabelsCommand2();
|
|
201
206
|
}
|
|
202
207
|
}
|
|
@@ -227,7 +232,9 @@ async function invokeAI(options, config, providerOverride) {
|
|
|
227
232
|
if (isRateLimitError(error, provider)) {
|
|
228
233
|
if (config.ai.auto_fallback && config.ai.fallback_provider && !providerOverride) {
|
|
229
234
|
const fallback = config.ai.fallback_provider;
|
|
230
|
-
logger.warning(
|
|
235
|
+
logger.warning(
|
|
236
|
+
`Rate limit reached on ${getProviderDisplayName(provider)}, switching to ${getProviderDisplayName(fallback)}...`
|
|
237
|
+
);
|
|
231
238
|
const output = await invokeInternal(fallback, options);
|
|
232
239
|
return { output, provider: fallback };
|
|
233
240
|
}
|
|
@@ -654,7 +661,7 @@ async function createCommand(description, options) {
|
|
|
654
661
|
logger.bold("Creating AI-enhanced ticket...");
|
|
655
662
|
logger.newline();
|
|
656
663
|
const config = loadConfig();
|
|
657
|
-
const provider = options
|
|
664
|
+
const provider = resolveProvider(options, config);
|
|
658
665
|
const providerName = getProviderDisplayName(provider);
|
|
659
666
|
const [ghAuth, aiOk] = await Promise.all([
|
|
660
667
|
checkGhAuth(),
|
|
@@ -665,21 +672,39 @@ async function createCommand(description, options) {
|
|
|
665
672
|
return;
|
|
666
673
|
}
|
|
667
674
|
if (!aiOk) {
|
|
668
|
-
logger.error(
|
|
675
|
+
logger.error(
|
|
676
|
+
`${providerName} CLI not found. Please install ${provider} CLI first.`
|
|
677
|
+
);
|
|
669
678
|
return;
|
|
670
679
|
}
|
|
671
680
|
const agentInstructions = loadAgentInstructions();
|
|
672
681
|
let aiOutput;
|
|
673
682
|
let additionalHints = null;
|
|
674
683
|
while (true) {
|
|
675
|
-
const prompt = buildTicketPrompt(
|
|
684
|
+
const prompt = buildTicketPrompt(
|
|
685
|
+
description,
|
|
686
|
+
agentInstructions,
|
|
687
|
+
additionalHints
|
|
688
|
+
);
|
|
676
689
|
try {
|
|
677
|
-
console.log(
|
|
690
|
+
console.log(
|
|
691
|
+
chalk.dim(
|
|
692
|
+
`\u250C\u2500 Generating ticket with ${providerName}... \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`
|
|
693
|
+
)
|
|
694
|
+
);
|
|
678
695
|
logger.newline();
|
|
679
|
-
const result = await invokeAI(
|
|
696
|
+
const result = await invokeAI(
|
|
697
|
+
{ prompt, streamOutput: true },
|
|
698
|
+
config,
|
|
699
|
+
options.provider
|
|
700
|
+
);
|
|
680
701
|
aiOutput = result.output;
|
|
681
702
|
logger.newline();
|
|
682
|
-
console.log(
|
|
703
|
+
console.log(
|
|
704
|
+
chalk.dim(
|
|
705
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
706
|
+
)
|
|
707
|
+
);
|
|
683
708
|
logger.newline();
|
|
684
709
|
} catch (error) {
|
|
685
710
|
logger.error(`${providerName} invocation failed: ${error}`);
|
|
@@ -687,7 +712,9 @@ async function createCommand(description, options) {
|
|
|
687
712
|
}
|
|
688
713
|
const meta = parseTicketMeta(aiOutput);
|
|
689
714
|
if (!meta) {
|
|
690
|
-
logger.warning(
|
|
715
|
+
logger.warning(
|
|
716
|
+
"Could not parse metadata from AI output. Using defaults."
|
|
717
|
+
);
|
|
691
718
|
}
|
|
692
719
|
const finalMeta = meta || {
|
|
693
720
|
type: "feature",
|
|
@@ -860,11 +887,7 @@ async function hasUncommittedChanges() {
|
|
|
860
887
|
}
|
|
861
888
|
async function getUnpushedCommits() {
|
|
862
889
|
try {
|
|
863
|
-
const { stdout } = await execa2("git", [
|
|
864
|
-
"log",
|
|
865
|
-
"@{u}..HEAD",
|
|
866
|
-
"--oneline"
|
|
867
|
-
]);
|
|
890
|
+
const { stdout } = await execa2("git", ["log", "@{u}..HEAD", "--oneline"]);
|
|
868
891
|
return stdout.trim().length > 0;
|
|
869
892
|
} catch {
|
|
870
893
|
return true;
|
|
@@ -907,11 +930,7 @@ async function getCommitsSinceBase(base = "main") {
|
|
|
907
930
|
}
|
|
908
931
|
async function getDiffSummary(base = "main") {
|
|
909
932
|
try {
|
|
910
|
-
const { stdout } = await execa2("git", [
|
|
911
|
-
"diff",
|
|
912
|
-
`${base}...HEAD`,
|
|
913
|
-
"--stat"
|
|
914
|
-
]);
|
|
933
|
+
const { stdout } = await execa2("git", ["diff", `${base}...HEAD`, "--stat"]);
|
|
915
934
|
return stdout.trim();
|
|
916
935
|
} catch {
|
|
917
936
|
return "";
|
|
@@ -930,7 +949,10 @@ async function getLastCommitTimestamp() {
|
|
|
930
949
|
return stdout.trim();
|
|
931
950
|
}
|
|
932
951
|
async function listLocalBranches() {
|
|
933
|
-
const { stdout } = await execa2("git", [
|
|
952
|
+
const { stdout } = await execa2("git", [
|
|
953
|
+
"branch",
|
|
954
|
+
"--format=%(refname:short)"
|
|
955
|
+
]);
|
|
934
956
|
return stdout.trim().split("\n").filter(Boolean);
|
|
935
957
|
}
|
|
936
958
|
async function remoteBranchExists(name) {
|
|
@@ -1145,12 +1167,7 @@ async function listCommand(options) {
|
|
|
1145
1167
|
logger.info("No issues found matching the criteria.");
|
|
1146
1168
|
return;
|
|
1147
1169
|
}
|
|
1148
|
-
await presentSelector(
|
|
1149
|
-
choices2,
|
|
1150
|
-
currentBranch,
|
|
1151
|
-
defaultBranch,
|
|
1152
|
-
config
|
|
1153
|
-
);
|
|
1170
|
+
await presentSelector(choices2, currentBranch, defaultBranch, config);
|
|
1154
1171
|
return;
|
|
1155
1172
|
}
|
|
1156
1173
|
const labelFilter = options.label ? [options.label] : [];
|
|
@@ -1260,15 +1277,10 @@ async function presentSelector(choices, currentBranch, defaultBranch, config) {
|
|
|
1260
1277
|
});
|
|
1261
1278
|
logger.success(`Switched to ${colors.branch(targetBranch)}`);
|
|
1262
1279
|
} else if (await remoteBranchExists(targetBranch)) {
|
|
1263
|
-
await withSpinner(
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
);
|
|
1269
|
-
logger.success(
|
|
1270
|
-
`Fetched and switched to ${colors.branch(targetBranch)}`
|
|
1271
|
-
);
|
|
1280
|
+
await withSpinner(`Fetching ${targetBranch} from remote...`, async () => {
|
|
1281
|
+
await fetchAndCheckout(targetBranch);
|
|
1282
|
+
});
|
|
1283
|
+
logger.success(`Fetched and switched to ${colors.branch(targetBranch)}`);
|
|
1272
1284
|
} else {
|
|
1273
1285
|
logger.warning(
|
|
1274
1286
|
`Branch ${colors.branch(targetBranch)} not found locally or on remote.`
|
|
@@ -1334,7 +1346,9 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1334
1346
|
return;
|
|
1335
1347
|
}
|
|
1336
1348
|
if (!aiOk) {
|
|
1337
|
-
logger.error(
|
|
1349
|
+
logger.error(
|
|
1350
|
+
`${providerName} CLI not found. Please install ${provider} CLI first.`
|
|
1351
|
+
);
|
|
1338
1352
|
return;
|
|
1339
1353
|
}
|
|
1340
1354
|
const hasChanges = await hasUncommittedChanges();
|
|
@@ -1362,7 +1376,9 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1362
1376
|
}
|
|
1363
1377
|
issueNumber = parseInt(issueNumberArg, 10);
|
|
1364
1378
|
} else {
|
|
1365
|
-
logger.error(
|
|
1379
|
+
logger.error(
|
|
1380
|
+
"Please provide an issue number. Use 'gent switch' to browse tickets."
|
|
1381
|
+
);
|
|
1366
1382
|
return;
|
|
1367
1383
|
}
|
|
1368
1384
|
let issue;
|
|
@@ -1375,7 +1391,9 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1375
1391
|
return;
|
|
1376
1392
|
}
|
|
1377
1393
|
if (!issue.labels.includes(workflowLabels.ready)) {
|
|
1378
|
-
logger.warning(
|
|
1394
|
+
logger.warning(
|
|
1395
|
+
`Issue #${issueNumber} does not have the '${workflowLabels.ready}' label.`
|
|
1396
|
+
);
|
|
1379
1397
|
const { proceed } = await inquirer4.prompt([
|
|
1380
1398
|
{
|
|
1381
1399
|
type: "confirm",
|
|
@@ -1389,11 +1407,19 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1389
1407
|
}
|
|
1390
1408
|
}
|
|
1391
1409
|
logger.newline();
|
|
1392
|
-
logger.box(
|
|
1393
|
-
|
|
1410
|
+
logger.box(
|
|
1411
|
+
"Issue Details",
|
|
1412
|
+
`#${issue.number}: ${issue.title}
|
|
1413
|
+
Labels: ${issue.labels.join(", ")}`
|
|
1414
|
+
);
|
|
1394
1415
|
logger.newline();
|
|
1395
1416
|
const type = extractTypeFromLabels(issue.labels);
|
|
1396
|
-
const branchName = await generateBranchName(
|
|
1417
|
+
const branchName = await generateBranchName(
|
|
1418
|
+
config,
|
|
1419
|
+
issueNumber,
|
|
1420
|
+
issue.title,
|
|
1421
|
+
type
|
|
1422
|
+
);
|
|
1397
1423
|
const currentBranch = await getCurrentBranch();
|
|
1398
1424
|
const onMain = await isOnMainBranch();
|
|
1399
1425
|
if (await branchExists(branchName)) {
|
|
@@ -1419,7 +1445,9 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1419
1445
|
}
|
|
1420
1446
|
} else {
|
|
1421
1447
|
if (!onMain) {
|
|
1422
|
-
logger.warning(
|
|
1448
|
+
logger.warning(
|
|
1449
|
+
`Not on main branch (currently on ${colors.branch(currentBranch)}).`
|
|
1450
|
+
);
|
|
1423
1451
|
const { fromMain } = await inquirer4.prompt([
|
|
1424
1452
|
{
|
|
1425
1453
|
type: "confirm",
|
|
@@ -1443,15 +1471,24 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1443
1471
|
add: [workflowLabels.inProgress],
|
|
1444
1472
|
remove: [workflowLabels.ready]
|
|
1445
1473
|
});
|
|
1446
|
-
logger.success(
|
|
1474
|
+
logger.success(
|
|
1475
|
+
`Updated issue labels: ${colors.label(workflowLabels.ready)} \u2192 ${colors.label(workflowLabels.inProgress)}`
|
|
1476
|
+
);
|
|
1447
1477
|
} catch (error) {
|
|
1448
1478
|
logger.warning(`Failed to update labels: ${error}`);
|
|
1449
1479
|
}
|
|
1450
1480
|
const agentInstructions = loadAgentInstructions();
|
|
1451
1481
|
const progressContent = readProgress(config);
|
|
1452
|
-
const prompt = buildImplementationPrompt(
|
|
1482
|
+
const prompt = buildImplementationPrompt(
|
|
1483
|
+
issue,
|
|
1484
|
+
agentInstructions,
|
|
1485
|
+
progressContent,
|
|
1486
|
+
config
|
|
1487
|
+
);
|
|
1453
1488
|
logger.newline();
|
|
1454
|
-
logger.info(
|
|
1489
|
+
logger.info(
|
|
1490
|
+
`Starting ${colors.provider(providerName)} implementation session...`
|
|
1491
|
+
);
|
|
1455
1492
|
logger.dim(`${providerName} will implement the feature and create a commit.`);
|
|
1456
1493
|
logger.dim("Review the changes before pushing.");
|
|
1457
1494
|
logger.newline();
|
|
@@ -1465,14 +1502,20 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1465
1502
|
let aiExitCode;
|
|
1466
1503
|
let usedProvider = provider;
|
|
1467
1504
|
try {
|
|
1468
|
-
const { result, provider: actualProvider } = await invokeAIInteractive(
|
|
1505
|
+
const { result, provider: actualProvider } = await invokeAIInteractive(
|
|
1506
|
+
prompt,
|
|
1507
|
+
config,
|
|
1508
|
+
options.provider
|
|
1509
|
+
);
|
|
1469
1510
|
usedProvider = actualProvider;
|
|
1470
1511
|
aiExitCode = result.exitCode ?? void 0;
|
|
1471
1512
|
} catch (error) {
|
|
1472
1513
|
if (error && typeof error === "object" && "exitCode" in error) {
|
|
1473
1514
|
aiExitCode = error.exitCode;
|
|
1474
1515
|
}
|
|
1475
|
-
logger.error(
|
|
1516
|
+
logger.error(
|
|
1517
|
+
`${getProviderDisplayName(usedProvider)} session failed: ${error}`
|
|
1518
|
+
);
|
|
1476
1519
|
} finally {
|
|
1477
1520
|
process.off("SIGINT", handleSignal);
|
|
1478
1521
|
process.off("SIGTERM", handleSignal);
|
|
@@ -1491,7 +1534,9 @@ Labels: ${issue.labels.join(", ")}`);
|
|
|
1491
1534
|
add: [workflowLabels.completed],
|
|
1492
1535
|
remove: [workflowLabels.inProgress]
|
|
1493
1536
|
});
|
|
1494
|
-
logger.success(
|
|
1537
|
+
logger.success(
|
|
1538
|
+
`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`
|
|
1539
|
+
);
|
|
1495
1540
|
} catch (error) {
|
|
1496
1541
|
logger.warning(`Failed to update labels: ${error}`);
|
|
1497
1542
|
}
|
|
@@ -1509,13 +1554,17 @@ Please review the changes and create a PR when ready.`
|
|
|
1509
1554
|
} else {
|
|
1510
1555
|
const isRateLimited = aiExitCode === 2;
|
|
1511
1556
|
if (isRateLimited) {
|
|
1512
|
-
logger.warning(
|
|
1557
|
+
logger.warning(
|
|
1558
|
+
`${usedProviderName} session ended due to rate limits. No commits were created.`
|
|
1559
|
+
);
|
|
1513
1560
|
try {
|
|
1514
1561
|
await updateIssueLabels(issueNumber, {
|
|
1515
1562
|
add: [workflowLabels.blocked],
|
|
1516
1563
|
remove: [workflowLabels.inProgress]
|
|
1517
1564
|
});
|
|
1518
|
-
logger.info(
|
|
1565
|
+
logger.info(
|
|
1566
|
+
`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.blocked)}`
|
|
1567
|
+
);
|
|
1519
1568
|
} catch (error) {
|
|
1520
1569
|
logger.warning(`Failed to update labels: ${error}`);
|
|
1521
1570
|
}
|
|
@@ -1531,15 +1580,20 @@ No commits were created. Please retry later.`
|
|
|
1531
1580
|
logger.warning(`Failed to post comment: ${error}`);
|
|
1532
1581
|
}
|
|
1533
1582
|
} else {
|
|
1534
|
-
logger.warning(
|
|
1583
|
+
logger.warning(
|
|
1584
|
+
`${usedProviderName} session completed but no commits were created. Labels unchanged.`
|
|
1585
|
+
);
|
|
1535
1586
|
}
|
|
1536
1587
|
return;
|
|
1537
1588
|
}
|
|
1538
1589
|
logger.newline();
|
|
1539
|
-
logger.box(
|
|
1590
|
+
logger.box(
|
|
1591
|
+
"Next Steps",
|
|
1592
|
+
`1. Review changes: ${colors.command("git diff HEAD~1")}
|
|
1540
1593
|
2. Run tests: ${colors.command("npm test")}
|
|
1541
1594
|
3. Push branch: ${colors.command("git push -u origin " + branchName)}
|
|
1542
|
-
4. Create PR: ${colors.command("gent pr")}`
|
|
1595
|
+
4. Create PR: ${colors.command("gent pr")}`
|
|
1596
|
+
);
|
|
1543
1597
|
}
|
|
1544
1598
|
|
|
1545
1599
|
// src/commands/pr.ts
|
|
@@ -1605,7 +1659,9 @@ async function prCommand(options) {
|
|
|
1605
1659
|
process.exit(1);
|
|
1606
1660
|
}
|
|
1607
1661
|
if (!aiOk) {
|
|
1608
|
-
logger.error(
|
|
1662
|
+
logger.error(
|
|
1663
|
+
`${providerName} CLI not found. Please install ${provider} CLI first.`
|
|
1664
|
+
);
|
|
1609
1665
|
process.exit(1);
|
|
1610
1666
|
}
|
|
1611
1667
|
if (await isOnMainBranch()) {
|
|
@@ -1614,7 +1670,9 @@ async function prCommand(options) {
|
|
|
1614
1670
|
}
|
|
1615
1671
|
const existingPr = await getPrForBranch();
|
|
1616
1672
|
if (existingPr) {
|
|
1617
|
-
logger.warning(
|
|
1673
|
+
logger.warning(
|
|
1674
|
+
`A PR already exists for this branch: ${colors.url(existingPr.url)}`
|
|
1675
|
+
);
|
|
1618
1676
|
return;
|
|
1619
1677
|
}
|
|
1620
1678
|
const currentBranch = await getCurrentBranch();
|
|
@@ -1644,7 +1702,9 @@ async function prCommand(options) {
|
|
|
1644
1702
|
if (issueNumber) {
|
|
1645
1703
|
try {
|
|
1646
1704
|
issue = await getIssue(issueNumber);
|
|
1647
|
-
logger.info(
|
|
1705
|
+
logger.info(
|
|
1706
|
+
`Linked issue: ${colors.issue(`#${issueNumber}`)} - ${issue.title}`
|
|
1707
|
+
);
|
|
1648
1708
|
} catch {
|
|
1649
1709
|
logger.warning(`Could not fetch issue #${issueNumber}`);
|
|
1650
1710
|
}
|
|
@@ -1671,7 +1731,9 @@ async function prCommand(options) {
|
|
|
1671
1731
|
logger.warning("Playwright not available. Skipping video capture.");
|
|
1672
1732
|
logger.dim("Install Playwright with: npm install -D playwright");
|
|
1673
1733
|
} else {
|
|
1674
|
-
logger.info(
|
|
1734
|
+
logger.info(
|
|
1735
|
+
"Playwright available - AI will capture demo video via MCP"
|
|
1736
|
+
);
|
|
1675
1737
|
captureVideoInstructions = `
|
|
1676
1738
|
|
|
1677
1739
|
IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
|
|
@@ -1686,9 +1748,15 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
|
|
|
1686
1748
|
const prompt = buildPrPrompt(issue, commits, diffSummary) + captureVideoInstructions;
|
|
1687
1749
|
let prBody;
|
|
1688
1750
|
try {
|
|
1689
|
-
logger.info(
|
|
1751
|
+
logger.info(
|
|
1752
|
+
`Generating PR description with ${colors.provider(providerName)}...`
|
|
1753
|
+
);
|
|
1690
1754
|
logger.newline();
|
|
1691
|
-
const result = await invokeAI(
|
|
1755
|
+
const result = await invokeAI(
|
|
1756
|
+
{ prompt, streamOutput: true },
|
|
1757
|
+
config,
|
|
1758
|
+
options.provider
|
|
1759
|
+
);
|
|
1692
1760
|
prBody = result.output;
|
|
1693
1761
|
logger.newline();
|
|
1694
1762
|
} catch (error) {
|
|
@@ -1727,7 +1795,9 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
|
|
|
1727
1795
|
add: [workflowLabels.completed],
|
|
1728
1796
|
remove: [workflowLabels.inProgress]
|
|
1729
1797
|
});
|
|
1730
|
-
logger.success(
|
|
1798
|
+
logger.success(
|
|
1799
|
+
`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`
|
|
1800
|
+
);
|
|
1731
1801
|
} catch {
|
|
1732
1802
|
}
|
|
1733
1803
|
}
|
|
@@ -1887,7 +1957,9 @@ function isActionableThread(thread) {
|
|
|
1887
1957
|
if (thread.isResolved === false || thread.isResolved === void 0 || thread.isResolved === null) {
|
|
1888
1958
|
return true;
|
|
1889
1959
|
}
|
|
1890
|
-
return (thread.comments ?? []).some(
|
|
1960
|
+
return (thread.comments ?? []).some(
|
|
1961
|
+
(comment) => isActionableText(comment.body)
|
|
1962
|
+
);
|
|
1891
1963
|
}
|
|
1892
1964
|
function isActionableText(text) {
|
|
1893
1965
|
const normalized = text.toLowerCase();
|
|
@@ -1942,11 +2014,15 @@ async function fixCommand(options) {
|
|
|
1942
2014
|
process.exit(1);
|
|
1943
2015
|
}
|
|
1944
2016
|
if (!aiOk) {
|
|
1945
|
-
logger.error(
|
|
2017
|
+
logger.error(
|
|
2018
|
+
`${providerName} CLI not found. Please install ${provider} CLI first.`
|
|
2019
|
+
);
|
|
1946
2020
|
process.exit(1);
|
|
1947
2021
|
}
|
|
1948
2022
|
if (await isOnMainBranch()) {
|
|
1949
|
-
logger.error(
|
|
2023
|
+
logger.error(
|
|
2024
|
+
"Cannot apply fixes from main/master branch. Switch to the PR branch first."
|
|
2025
|
+
);
|
|
1950
2026
|
process.exit(1);
|
|
1951
2027
|
}
|
|
1952
2028
|
const hasChanges = await hasUncommittedChanges();
|
|
@@ -1969,21 +2045,30 @@ async function fixCommand(options) {
|
|
|
1969
2045
|
return getPrForBranch();
|
|
1970
2046
|
});
|
|
1971
2047
|
if (!pr) {
|
|
1972
|
-
logger.error(
|
|
2048
|
+
logger.error(
|
|
2049
|
+
"No pull request found for the current branch. Create one with 'gent pr' first."
|
|
2050
|
+
);
|
|
1973
2051
|
process.exit(1);
|
|
1974
2052
|
}
|
|
1975
2053
|
const lastCommitTimestamp = await getLastCommitTimestamp();
|
|
1976
|
-
const reviewData = await withSpinner(
|
|
1977
|
-
|
|
1978
|
-
|
|
2054
|
+
const reviewData = await withSpinner(
|
|
2055
|
+
"Fetching review feedback...",
|
|
2056
|
+
async () => {
|
|
2057
|
+
return getPrReviewData(pr.number);
|
|
2058
|
+
}
|
|
2059
|
+
);
|
|
1979
2060
|
const totalComments = countReviewComments(reviewData);
|
|
1980
2061
|
if (totalComments === 0) {
|
|
1981
2062
|
logger.error(`No review comments found for PR #${pr.number}.`);
|
|
1982
2063
|
process.exit(1);
|
|
1983
2064
|
}
|
|
1984
|
-
const { items, summary } = summarizeReviewFeedback(reviewData, {
|
|
2065
|
+
const { items, summary } = summarizeReviewFeedback(reviewData, {
|
|
2066
|
+
afterTimestamp: lastCommitTimestamp
|
|
2067
|
+
});
|
|
1985
2068
|
if (items.length === 0 || !summary) {
|
|
1986
|
-
logger.error(
|
|
2069
|
+
logger.error(
|
|
2070
|
+
"No new actionable review feedback found since your last commit."
|
|
2071
|
+
);
|
|
1987
2072
|
process.exit(1);
|
|
1988
2073
|
}
|
|
1989
2074
|
logger.newline();
|
|
@@ -2000,8 +2085,14 @@ async function fixCommand(options) {
|
|
|
2000
2085
|
});
|
|
2001
2086
|
const agentInstructions = loadAgentInstructions();
|
|
2002
2087
|
const progressContent = readProgress(config);
|
|
2003
|
-
const prompt = buildImplementationPrompt(
|
|
2004
|
-
|
|
2088
|
+
const prompt = buildImplementationPrompt(
|
|
2089
|
+
issue,
|
|
2090
|
+
agentInstructions,
|
|
2091
|
+
progressContent,
|
|
2092
|
+
config,
|
|
2093
|
+
`## Review Feedback
|
|
2094
|
+
${summary}`
|
|
2095
|
+
);
|
|
2005
2096
|
logger.newline();
|
|
2006
2097
|
logger.info(`Starting ${colors.provider(providerName)} fix session...`);
|
|
2007
2098
|
logger.dim("Review feedback will be appended to the implementation prompt.");
|
|
@@ -2015,7 +2106,11 @@ ${summary}`);
|
|
|
2015
2106
|
process.on("SIGTERM", handleSignal);
|
|
2016
2107
|
let aiExitCode;
|
|
2017
2108
|
try {
|
|
2018
|
-
const { result } = await invokeAIInteractive(
|
|
2109
|
+
const { result } = await invokeAIInteractive(
|
|
2110
|
+
prompt,
|
|
2111
|
+
config,
|
|
2112
|
+
options.provider
|
|
2113
|
+
);
|
|
2019
2114
|
aiExitCode = result.exitCode ?? void 0;
|
|
2020
2115
|
} catch (error) {
|
|
2021
2116
|
if (error && typeof error === "object" && "exitCode" in error) {
|
|
@@ -2039,18 +2134,28 @@ ${summary}`);
|
|
|
2039
2134
|
}
|
|
2040
2135
|
const isRateLimited = aiExitCode === 2;
|
|
2041
2136
|
if (isRateLimited) {
|
|
2042
|
-
logger.warning(
|
|
2137
|
+
logger.warning(
|
|
2138
|
+
`${providerName} session ended due to rate limits. No commits were created.`
|
|
2139
|
+
);
|
|
2043
2140
|
return;
|
|
2044
2141
|
}
|
|
2045
|
-
logger.warning(
|
|
2142
|
+
logger.warning(
|
|
2143
|
+
`${providerName} session completed but no commits were created.`
|
|
2144
|
+
);
|
|
2046
2145
|
}
|
|
2047
2146
|
function countReviewComments(data) {
|
|
2048
|
-
const reviewBodies = data.reviews.filter(
|
|
2147
|
+
const reviewBodies = data.reviews.filter(
|
|
2148
|
+
(review) => review.body?.trim()
|
|
2149
|
+
).length;
|
|
2049
2150
|
const threadBodies = data.reviewThreads.reduce((count, thread) => {
|
|
2050
|
-
const threadCount = (thread.comments ?? []).filter(
|
|
2151
|
+
const threadCount = (thread.comments ?? []).filter(
|
|
2152
|
+
(comment) => comment.body?.trim()
|
|
2153
|
+
).length;
|
|
2051
2154
|
return count + threadCount;
|
|
2052
2155
|
}, 0);
|
|
2053
|
-
const prComments = (data.comments ?? []).filter(
|
|
2156
|
+
const prComments = (data.comments ?? []).filter(
|
|
2157
|
+
(comment) => comment.body?.trim()
|
|
2158
|
+
).length;
|
|
2054
2159
|
return reviewBodies + threadBodies + prComments;
|
|
2055
2160
|
}
|
|
2056
2161
|
async function replyToFeedbackItems(prNumber, items) {
|
|
@@ -2069,7 +2174,9 @@ async function replyToFeedbackItems(prNumber, items) {
|
|
|
2069
2174
|
}
|
|
2070
2175
|
}
|
|
2071
2176
|
if (repliedCount > 0) {
|
|
2072
|
-
logger.dim(
|
|
2177
|
+
logger.dim(
|
|
2178
|
+
`Replied to ${repliedCount} feedback item${repliedCount > 1 ? "s" : ""}.`
|
|
2179
|
+
);
|
|
2073
2180
|
}
|
|
2074
2181
|
}
|
|
2075
2182
|
|
|
@@ -2081,7 +2188,7 @@ import { homedir } from "os";
|
|
|
2081
2188
|
// package.json
|
|
2082
2189
|
var package_default = {
|
|
2083
2190
|
name: "@rotorsoft/gent",
|
|
2084
|
-
version: "1.13.
|
|
2191
|
+
version: "1.13.2",
|
|
2085
2192
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
2086
2193
|
keywords: [
|
|
2087
2194
|
"cli",
|
|
@@ -2333,7 +2440,9 @@ async function statusCommand() {
|
|
|
2333
2440
|
logger.info(` Active: ${colors.provider(providerName)}`);
|
|
2334
2441
|
if (config.ai.fallback_provider) {
|
|
2335
2442
|
const fallbackName = getProviderDisplayName(config.ai.fallback_provider);
|
|
2336
|
-
logger.info(
|
|
2443
|
+
logger.info(
|
|
2444
|
+
` Fallback: ${fallbackName} (auto: ${config.ai.auto_fallback ? "enabled" : "disabled"})`
|
|
2445
|
+
);
|
|
2337
2446
|
}
|
|
2338
2447
|
logger.newline();
|
|
2339
2448
|
logger.bold("Prerequisites:");
|
|
@@ -2424,13 +2533,19 @@ async function statusCommand() {
|
|
|
2424
2533
|
try {
|
|
2425
2534
|
const lastCommitTimestamp = await getLastCommitTimestamp();
|
|
2426
2535
|
const reviewData = await getPrReviewData(prStatus.number);
|
|
2427
|
-
const { items } = summarizeReviewFeedback(reviewData, {
|
|
2536
|
+
const { items } = summarizeReviewFeedback(reviewData, {
|
|
2537
|
+
afterTimestamp: lastCommitTimestamp
|
|
2538
|
+
});
|
|
2428
2539
|
hasActionableFeedback = items.length > 0;
|
|
2429
2540
|
if (prStatus.reviewDecision) {
|
|
2430
|
-
logger.info(
|
|
2541
|
+
logger.info(
|
|
2542
|
+
` Review: ${formatReviewDecision(prStatus.reviewDecision)}`
|
|
2543
|
+
);
|
|
2431
2544
|
}
|
|
2432
2545
|
if (items.length > 0) {
|
|
2433
|
-
logger.warning(
|
|
2546
|
+
logger.warning(
|
|
2547
|
+
` ${items.length} actionable comment${items.length > 1 ? "s" : ""} to fix with ${colors.command("gent fix")}:`
|
|
2548
|
+
);
|
|
2434
2549
|
for (const item of items) {
|
|
2435
2550
|
const location = formatFeedbackLocation(item);
|
|
2436
2551
|
const body = truncateFeedbackBody(item.body, 60);
|
|
@@ -2445,10 +2560,14 @@ async function statusCommand() {
|
|
|
2445
2560
|
}
|
|
2446
2561
|
} else if (prStatus.state === "merged") {
|
|
2447
2562
|
logger.success(" This PR has been merged!");
|
|
2448
|
-
logger.dim(
|
|
2563
|
+
logger.dim(
|
|
2564
|
+
` Run ${colors.command("git checkout main && git pull")} to sync`
|
|
2565
|
+
);
|
|
2449
2566
|
} else if (prStatus.state === "closed") {
|
|
2450
2567
|
logger.warning(" This PR was closed without merging");
|
|
2451
|
-
logger.dim(
|
|
2568
|
+
logger.dim(
|
|
2569
|
+
` Consider reopening or creating a new PR if changes are still needed`
|
|
2570
|
+
);
|
|
2452
2571
|
}
|
|
2453
2572
|
} else {
|
|
2454
2573
|
logger.info(" No PR created yet");
|
|
@@ -2580,7 +2699,9 @@ async function aggregateState() {
|
|
|
2580
2699
|
try {
|
|
2581
2700
|
const lastCommitTimestamp = await getLastCommitTimestamp();
|
|
2582
2701
|
const reviewData = await getPrReviewData(pr.number);
|
|
2583
|
-
const { items } = summarizeReviewFeedback(reviewData, {
|
|
2702
|
+
const { items } = summarizeReviewFeedback(reviewData, {
|
|
2703
|
+
afterTimestamp: lastCommitTimestamp
|
|
2704
|
+
});
|
|
2584
2705
|
reviewFeedback = items;
|
|
2585
2706
|
hasActionableFeedback = items.length > 0;
|
|
2586
2707
|
} catch {
|
|
@@ -2621,7 +2742,7 @@ function getAvailableActions(state) {
|
|
|
2621
2742
|
if (state.isOnMain) {
|
|
2622
2743
|
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2623
2744
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2624
|
-
actions.push({ id: "switch-provider", label: "
|
|
2745
|
+
actions.push({ id: "switch-provider", label: "ai", shortcut: "a" });
|
|
2625
2746
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2626
2747
|
return actions;
|
|
2627
2748
|
}
|
|
@@ -2629,10 +2750,10 @@ function getAvailableActions(state) {
|
|
|
2629
2750
|
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2630
2751
|
}
|
|
2631
2752
|
if (state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2632
|
-
actions.push({ id: "push", label: "
|
|
2753
|
+
actions.push({ id: "push", label: "push", shortcut: "p" });
|
|
2633
2754
|
}
|
|
2634
2755
|
if (!state.pr && state.commits.length > 0) {
|
|
2635
|
-
actions.push({ id: "pr", label: "
|
|
2756
|
+
actions.push({ id: "pr", label: "pr", shortcut: "r" });
|
|
2636
2757
|
}
|
|
2637
2758
|
if (state.issue && state.pr?.state !== "merged") {
|
|
2638
2759
|
actions.push({ id: "implement", label: "implement", shortcut: "i" });
|
|
@@ -2646,7 +2767,7 @@ function getAvailableActions(state) {
|
|
|
2646
2767
|
actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
|
|
2647
2768
|
}
|
|
2648
2769
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2649
|
-
actions.push({ id: "switch-provider", label: "
|
|
2770
|
+
actions.push({ id: "switch-provider", label: "ai", shortcut: "a" });
|
|
2650
2771
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2651
2772
|
return actions;
|
|
2652
2773
|
}
|
|
@@ -2788,11 +2909,14 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2788
2909
|
const w = termWidth();
|
|
2789
2910
|
const descMax = w - 8;
|
|
2790
2911
|
const version2 = getVersion();
|
|
2791
|
-
const titleLabel =
|
|
2912
|
+
const titleLabel = `gent v${version2}`;
|
|
2792
2913
|
console.log(topRow(titleLabel, w));
|
|
2914
|
+
renderSettings(state, w);
|
|
2793
2915
|
if (!state.isGitRepo) {
|
|
2794
2916
|
console.log(row(chalk3.red("Not a git repository"), w));
|
|
2795
|
-
console.log(
|
|
2917
|
+
console.log(
|
|
2918
|
+
row(chalk3.dim("Run gent init in a git repo to get started"), w)
|
|
2919
|
+
);
|
|
2796
2920
|
console.log(botRow(w));
|
|
2797
2921
|
return;
|
|
2798
2922
|
}
|
|
@@ -2803,7 +2927,12 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2803
2927
|
return;
|
|
2804
2928
|
}
|
|
2805
2929
|
if (state.isOnMain) {
|
|
2806
|
-
console.log(
|
|
2930
|
+
console.log(
|
|
2931
|
+
row(
|
|
2932
|
+
chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"),
|
|
2933
|
+
w
|
|
2934
|
+
)
|
|
2935
|
+
);
|
|
2807
2936
|
if (state.hasUncommittedChanges) {
|
|
2808
2937
|
console.log(row(chalk3.yellow("\u25CF uncommitted changes"), w));
|
|
2809
2938
|
}
|
|
@@ -2814,8 +2943,12 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2814
2943
|
console.log(row(chalk3.yellow(hint), w));
|
|
2815
2944
|
}
|
|
2816
2945
|
console.log(divRow(w));
|
|
2817
|
-
|
|
2818
|
-
console.log(row(
|
|
2946
|
+
if (refreshing) {
|
|
2947
|
+
console.log(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
2948
|
+
} else {
|
|
2949
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2950
|
+
console.log(row(line, w));
|
|
2951
|
+
}
|
|
2819
2952
|
}
|
|
2820
2953
|
console.log(botRow(w));
|
|
2821
2954
|
console.log();
|
|
@@ -2826,14 +2959,17 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2826
2959
|
};
|
|
2827
2960
|
section("Ticket");
|
|
2828
2961
|
if (state.issue) {
|
|
2829
|
-
console.log(
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2962
|
+
console.log(
|
|
2963
|
+
row(
|
|
2964
|
+
chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
|
|
2965
|
+
w
|
|
2966
|
+
)
|
|
2967
|
+
);
|
|
2833
2968
|
const desc = extractDescription(state.issue.body, descMax);
|
|
2834
2969
|
if (desc) console.log(row(chalk3.dim(desc), w));
|
|
2835
2970
|
const tags = [];
|
|
2836
|
-
if (state.workflowStatus !== "none")
|
|
2971
|
+
if (state.workflowStatus !== "none")
|
|
2972
|
+
tags.push(workflowBadge(state.workflowStatus));
|
|
2837
2973
|
for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
|
|
2838
2974
|
const l = state.issue.labels.find((x) => x.startsWith(prefix));
|
|
2839
2975
|
if (l) tags.push(chalk3.dim(l));
|
|
@@ -2845,7 +2981,8 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2845
2981
|
section("Branch");
|
|
2846
2982
|
console.log(row(chalk3.magenta(state.branch), w));
|
|
2847
2983
|
const bits = [];
|
|
2848
|
-
if (state.commits.length > 0)
|
|
2984
|
+
if (state.commits.length > 0)
|
|
2985
|
+
bits.push(chalk3.dim(`${state.commits.length} ahead`));
|
|
2849
2986
|
if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
|
|
2850
2987
|
if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
|
|
2851
2988
|
if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
|
|
@@ -2855,20 +2992,29 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2855
2992
|
section("Pull Request");
|
|
2856
2993
|
if (state.pr) {
|
|
2857
2994
|
const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
|
|
2858
|
-
console.log(row(
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
));
|
|
2995
|
+
console.log(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
|
|
2996
|
+
console.log(
|
|
2997
|
+
row(
|
|
2998
|
+
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2999
|
+
w
|
|
3000
|
+
)
|
|
3001
|
+
);
|
|
2866
3002
|
if (state.hasActionableFeedback) {
|
|
2867
3003
|
const n = state.reviewFeedback.length;
|
|
2868
|
-
console.log(
|
|
3004
|
+
console.log(
|
|
3005
|
+
row(
|
|
3006
|
+
chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
|
|
3007
|
+
w
|
|
3008
|
+
)
|
|
3009
|
+
);
|
|
2869
3010
|
}
|
|
2870
3011
|
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
2871
|
-
console.log(
|
|
3012
|
+
console.log(
|
|
3013
|
+
row(
|
|
3014
|
+
chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
|
|
3015
|
+
w
|
|
3016
|
+
)
|
|
3017
|
+
);
|
|
2872
3018
|
}
|
|
2873
3019
|
console.log(row(chalk3.dim(state.pr.url), w));
|
|
2874
3020
|
} else {
|
|
@@ -2878,23 +3024,27 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2878
3024
|
if (state.commits.length > 0) {
|
|
2879
3025
|
const max = 6;
|
|
2880
3026
|
for (const c of state.commits.slice(0, max)) {
|
|
2881
|
-
console.log(row(c, w));
|
|
3027
|
+
console.log(row(c.substring(0, w - 5), w));
|
|
2882
3028
|
}
|
|
2883
3029
|
if (state.commits.length > max) {
|
|
2884
|
-
console.log(
|
|
3030
|
+
console.log(
|
|
3031
|
+
row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w)
|
|
3032
|
+
);
|
|
2885
3033
|
}
|
|
2886
3034
|
} else {
|
|
2887
3035
|
console.log(row(chalk3.dim("No commits"), w));
|
|
2888
3036
|
}
|
|
2889
|
-
section("Settings");
|
|
2890
|
-
renderSettings(state, w);
|
|
2891
3037
|
if (hint) {
|
|
2892
3038
|
section("Hint");
|
|
2893
3039
|
console.log(row(chalk3.yellow(hint), w));
|
|
2894
3040
|
}
|
|
2895
3041
|
console.log(divRow(w));
|
|
2896
|
-
|
|
2897
|
-
console.log(row(
|
|
3042
|
+
if (refreshing) {
|
|
3043
|
+
console.log(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
3044
|
+
} else {
|
|
3045
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
3046
|
+
console.log(row(line, w));
|
|
3047
|
+
}
|
|
2898
3048
|
}
|
|
2899
3049
|
console.log(botRow(w));
|
|
2900
3050
|
}
|
|
@@ -2940,7 +3090,7 @@ async function waitForKey(validKeys) {
|
|
|
2940
3090
|
stdin.on("data", onData);
|
|
2941
3091
|
});
|
|
2942
3092
|
}
|
|
2943
|
-
async function executeAction(actionId, state
|
|
3093
|
+
async function executeAction(actionId, state) {
|
|
2944
3094
|
switch (actionId) {
|
|
2945
3095
|
case "quit":
|
|
2946
3096
|
return false;
|
|
@@ -3017,7 +3167,7 @@ async function executeAction(actionId, state, providerSetter) {
|
|
|
3017
3167
|
}
|
|
3018
3168
|
case "switch-provider":
|
|
3019
3169
|
clearScreen();
|
|
3020
|
-
await handleSwitchProvider(state
|
|
3170
|
+
await handleSwitchProvider(state);
|
|
3021
3171
|
return true;
|
|
3022
3172
|
case "checkout-main": {
|
|
3023
3173
|
clearScreen();
|
|
@@ -3040,15 +3190,47 @@ async function handleCommit(state) {
|
|
|
3040
3190
|
console.log(status);
|
|
3041
3191
|
console.log();
|
|
3042
3192
|
await execa4("git", ["add", "-A"]);
|
|
3043
|
-
const { stdout: diffStat } = await execa4("git", [
|
|
3193
|
+
const { stdout: diffStat } = await execa4("git", [
|
|
3194
|
+
"diff",
|
|
3195
|
+
"--cached",
|
|
3196
|
+
"--stat"
|
|
3197
|
+
]);
|
|
3044
3198
|
const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
|
|
3045
3199
|
const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
|
|
3046
3200
|
const issueNumber = state.issue?.number ?? null;
|
|
3047
3201
|
const issueTitle = state.issue?.title ?? null;
|
|
3048
3202
|
const provider = state.config.ai.provider;
|
|
3049
3203
|
const providerName = getProviderDisplayName(provider);
|
|
3050
|
-
|
|
3051
|
-
|
|
3204
|
+
const { mode } = await inquirer7.prompt([
|
|
3205
|
+
{
|
|
3206
|
+
type: "list",
|
|
3207
|
+
name: "mode",
|
|
3208
|
+
message: "How would you like to provide the commit message?",
|
|
3209
|
+
choices: [
|
|
3210
|
+
{ name: `Generate with ${providerName}`, value: "ai" },
|
|
3211
|
+
{ name: "Enter manually", value: "manual" }
|
|
3212
|
+
]
|
|
3213
|
+
}
|
|
3214
|
+
]);
|
|
3215
|
+
let message;
|
|
3216
|
+
if (mode === "manual") {
|
|
3217
|
+
const { manualInput } = await inquirer7.prompt([
|
|
3218
|
+
{
|
|
3219
|
+
type: "input",
|
|
3220
|
+
name: "manualInput",
|
|
3221
|
+
message: "Commit message (empty to cancel):"
|
|
3222
|
+
}
|
|
3223
|
+
]);
|
|
3224
|
+
message = manualInput.trim() || CANCEL;
|
|
3225
|
+
} else {
|
|
3226
|
+
logger.info(`Generating commit message with ${providerName}...`);
|
|
3227
|
+
message = await generateCommitMessage(
|
|
3228
|
+
diffContent,
|
|
3229
|
+
issueNumber,
|
|
3230
|
+
issueTitle,
|
|
3231
|
+
state
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3052
3234
|
if (message === CANCEL) {
|
|
3053
3235
|
await execa4("git", ["reset", "HEAD"]);
|
|
3054
3236
|
logger.info("Cancelled");
|
|
@@ -3076,7 +3258,11 @@ Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
|
3076
3258
|
}
|
|
3077
3259
|
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
|
|
3078
3260
|
try {
|
|
3079
|
-
const prompt = buildCommitMessagePrompt(
|
|
3261
|
+
const prompt = buildCommitMessagePrompt(
|
|
3262
|
+
diffContent,
|
|
3263
|
+
issueNumber,
|
|
3264
|
+
issueTitle
|
|
3265
|
+
);
|
|
3080
3266
|
const result = await invokeAI({ prompt, streamOutput: true }, state.config);
|
|
3081
3267
|
let message = result.output.trim().split("\n")[0].trim();
|
|
3082
3268
|
for (const q of ['"', "'", "`"]) {
|
|
@@ -3157,7 +3343,7 @@ async function handlePush() {
|
|
|
3157
3343
|
}
|
|
3158
3344
|
}
|
|
3159
3345
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3160
|
-
async function handleSwitchProvider(state
|
|
3346
|
+
async function handleSwitchProvider(state) {
|
|
3161
3347
|
const current = state.config.ai.provider;
|
|
3162
3348
|
const { provider } = await inquirer7.prompt([
|
|
3163
3349
|
{
|
|
@@ -3172,7 +3358,7 @@ async function handleSwitchProvider(state, setProvider) {
|
|
|
3172
3358
|
}
|
|
3173
3359
|
]);
|
|
3174
3360
|
if (provider === current) return;
|
|
3175
|
-
|
|
3361
|
+
setRuntimeProvider(provider);
|
|
3176
3362
|
logger.success(`Provider switched to ${provider} (session only)`);
|
|
3177
3363
|
}
|
|
3178
3364
|
async function handleCheckoutMain() {
|
|
@@ -3224,11 +3410,7 @@ async function promptContinue() {
|
|
|
3224
3410
|
}
|
|
3225
3411
|
async function tuiCommand() {
|
|
3226
3412
|
let running = true;
|
|
3227
|
-
let providerOverride = null;
|
|
3228
3413
|
let lastActions = [];
|
|
3229
|
-
const setProvider = (p) => {
|
|
3230
|
-
providerOverride = p;
|
|
3231
|
-
};
|
|
3232
3414
|
const config = loadConfig();
|
|
3233
3415
|
let lastState = {
|
|
3234
3416
|
isGitRepo: true,
|
|
@@ -3256,9 +3438,6 @@ async function tuiCommand() {
|
|
|
3256
3438
|
clearScreen();
|
|
3257
3439
|
renderDashboard(lastState, lastActions, void 0, true);
|
|
3258
3440
|
const state = await aggregateState();
|
|
3259
|
-
if (providerOverride) {
|
|
3260
|
-
state.config.ai.provider = providerOverride;
|
|
3261
|
-
}
|
|
3262
3441
|
const actions = getAvailableActions(state);
|
|
3263
3442
|
lastState = state;
|
|
3264
3443
|
lastActions = actions;
|
|
@@ -3276,7 +3455,7 @@ async function tuiCommand() {
|
|
|
3276
3455
|
const key = await waitForKey(validKeys);
|
|
3277
3456
|
const action = actions.find((a) => a.shortcut === key);
|
|
3278
3457
|
if (action) {
|
|
3279
|
-
running = await executeAction(action.id, state
|
|
3458
|
+
running = await executeAction(action.id, state);
|
|
3280
3459
|
}
|
|
3281
3460
|
}
|
|
3282
3461
|
}
|
|
@@ -3288,13 +3467,17 @@ function startVersionCheck() {
|
|
|
3288
3467
|
checkForUpdates().then((result) => {
|
|
3289
3468
|
if (result.updateAvailable && result.latestVersion) {
|
|
3290
3469
|
logger.newline();
|
|
3291
|
-
logger.warning(
|
|
3470
|
+
logger.warning(
|
|
3471
|
+
formatUpgradeNotification(result.currentVersion, result.latestVersion)
|
|
3472
|
+
);
|
|
3292
3473
|
}
|
|
3293
3474
|
}).catch(() => {
|
|
3294
3475
|
});
|
|
3295
3476
|
}
|
|
3296
3477
|
var program = new Command();
|
|
3297
|
-
program.name("gent").description(
|
|
3478
|
+
program.name("gent").description(
|
|
3479
|
+
"AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs"
|
|
3480
|
+
).version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
|
|
3298
3481
|
if (!thisCommand.opts().skipUpdateCheck) {
|
|
3299
3482
|
startVersionCheck();
|
|
3300
3483
|
}
|
|
@@ -3305,27 +3488,46 @@ program.command("init").description("Initialize gent workflow in current reposit
|
|
|
3305
3488
|
program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
|
|
3306
3489
|
await setupLabelsCommand();
|
|
3307
3490
|
});
|
|
3308
|
-
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
|
|
3309
|
-
|
|
3491
|
+
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
|
|
3492
|
+
"-p, --provider <provider>",
|
|
3493
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3494
|
+
).option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
|
|
3495
|
+
await createCommand(description, {
|
|
3496
|
+
yes: options.yes,
|
|
3497
|
+
provider: options.provider,
|
|
3498
|
+
title: options.title
|
|
3499
|
+
});
|
|
3310
3500
|
});
|
|
3311
|
-
program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
|
|
3501
|
+
program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
|
|
3502
|
+
"-s, --status <status>",
|
|
3503
|
+
"Filter by workflow status (ready, in-progress, completed, blocked, all)"
|
|
3504
|
+
).option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
|
|
3312
3505
|
await listCommand({
|
|
3313
3506
|
label: options.label,
|
|
3314
3507
|
status: options.status,
|
|
3315
3508
|
limit: parseInt(options.limit, 10)
|
|
3316
3509
|
});
|
|
3317
3510
|
});
|
|
3318
|
-
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
|
|
3511
|
+
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
|
|
3512
|
+
"-p, --provider <provider>",
|
|
3513
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3514
|
+
).action(async (issueNumber, options) => {
|
|
3319
3515
|
await runCommand(issueNumber, { provider: options.provider });
|
|
3320
3516
|
});
|
|
3321
|
-
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
|
|
3517
|
+
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
|
|
3518
|
+
"-p, --provider <provider>",
|
|
3519
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3520
|
+
).option("--no-video", "Disable video capture for UI changes").action(async (options) => {
|
|
3322
3521
|
await prCommand({
|
|
3323
3522
|
draft: options.draft,
|
|
3324
3523
|
provider: options.provider,
|
|
3325
3524
|
video: options.video
|
|
3326
3525
|
});
|
|
3327
3526
|
});
|
|
3328
|
-
program.command("fix").description("Apply PR review feedback using AI").option(
|
|
3527
|
+
program.command("fix").description("Apply PR review feedback using AI").option(
|
|
3528
|
+
"-p, --provider <provider>",
|
|
3529
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3530
|
+
).action(async (options) => {
|
|
3329
3531
|
await fixCommand({ provider: options.provider });
|
|
3330
3532
|
});
|
|
3331
3533
|
program.command("status").description("Show current workflow status").action(async () => {
|