@rotorsoft/gent 1.13.1 → 1.13.3
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 -146
- 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.3",
|
|
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,19 +2927,26 @@ 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
|
}
|
|
2810
|
-
console.log(midRow("Settings", w));
|
|
2811
|
-
renderSettings(state, w);
|
|
2812
2939
|
if (hint) {
|
|
2813
2940
|
console.log(midRow("Hint", w));
|
|
2814
2941
|
console.log(row(chalk3.yellow(hint), w));
|
|
2815
2942
|
}
|
|
2816
2943
|
console.log(divRow(w));
|
|
2817
|
-
|
|
2818
|
-
console.log(row(
|
|
2944
|
+
if (refreshing) {
|
|
2945
|
+
console.log(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
2946
|
+
} else {
|
|
2947
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2948
|
+
console.log(row(line, w));
|
|
2949
|
+
}
|
|
2819
2950
|
}
|
|
2820
2951
|
console.log(botRow(w));
|
|
2821
2952
|
console.log();
|
|
@@ -2826,14 +2957,17 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2826
2957
|
};
|
|
2827
2958
|
section("Ticket");
|
|
2828
2959
|
if (state.issue) {
|
|
2829
|
-
console.log(
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2960
|
+
console.log(
|
|
2961
|
+
row(
|
|
2962
|
+
chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
|
|
2963
|
+
w
|
|
2964
|
+
)
|
|
2965
|
+
);
|
|
2833
2966
|
const desc = extractDescription(state.issue.body, descMax);
|
|
2834
2967
|
if (desc) console.log(row(chalk3.dim(desc), w));
|
|
2835
2968
|
const tags = [];
|
|
2836
|
-
if (state.workflowStatus !== "none")
|
|
2969
|
+
if (state.workflowStatus !== "none")
|
|
2970
|
+
tags.push(workflowBadge(state.workflowStatus));
|
|
2837
2971
|
for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
|
|
2838
2972
|
const l = state.issue.labels.find((x) => x.startsWith(prefix));
|
|
2839
2973
|
if (l) tags.push(chalk3.dim(l));
|
|
@@ -2845,7 +2979,8 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2845
2979
|
section("Branch");
|
|
2846
2980
|
console.log(row(chalk3.magenta(state.branch), w));
|
|
2847
2981
|
const bits = [];
|
|
2848
|
-
if (state.commits.length > 0)
|
|
2982
|
+
if (state.commits.length > 0)
|
|
2983
|
+
bits.push(chalk3.dim(`${state.commits.length} ahead`));
|
|
2849
2984
|
if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
|
|
2850
2985
|
if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
|
|
2851
2986
|
if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
|
|
@@ -2855,20 +2990,29 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2855
2990
|
section("Pull Request");
|
|
2856
2991
|
if (state.pr) {
|
|
2857
2992
|
const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
|
|
2858
|
-
console.log(row(
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
));
|
|
2993
|
+
console.log(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
|
|
2994
|
+
console.log(
|
|
2995
|
+
row(
|
|
2996
|
+
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2997
|
+
w
|
|
2998
|
+
)
|
|
2999
|
+
);
|
|
2866
3000
|
if (state.hasActionableFeedback) {
|
|
2867
3001
|
const n = state.reviewFeedback.length;
|
|
2868
|
-
console.log(
|
|
3002
|
+
console.log(
|
|
3003
|
+
row(
|
|
3004
|
+
chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
|
|
3005
|
+
w
|
|
3006
|
+
)
|
|
3007
|
+
);
|
|
2869
3008
|
}
|
|
2870
3009
|
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
2871
|
-
console.log(
|
|
3010
|
+
console.log(
|
|
3011
|
+
row(
|
|
3012
|
+
chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
|
|
3013
|
+
w
|
|
3014
|
+
)
|
|
3015
|
+
);
|
|
2872
3016
|
}
|
|
2873
3017
|
console.log(row(chalk3.dim(state.pr.url), w));
|
|
2874
3018
|
} else {
|
|
@@ -2878,23 +3022,27 @@ function renderDashboard(state, actions, hint, refreshing) {
|
|
|
2878
3022
|
if (state.commits.length > 0) {
|
|
2879
3023
|
const max = 6;
|
|
2880
3024
|
for (const c of state.commits.slice(0, max)) {
|
|
2881
|
-
console.log(row(c, w));
|
|
3025
|
+
console.log(row(c.substring(0, w - 5), w));
|
|
2882
3026
|
}
|
|
2883
3027
|
if (state.commits.length > max) {
|
|
2884
|
-
console.log(
|
|
3028
|
+
console.log(
|
|
3029
|
+
row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w)
|
|
3030
|
+
);
|
|
2885
3031
|
}
|
|
2886
3032
|
} else {
|
|
2887
3033
|
console.log(row(chalk3.dim("No commits"), w));
|
|
2888
3034
|
}
|
|
2889
|
-
section("Settings");
|
|
2890
|
-
renderSettings(state, w);
|
|
2891
3035
|
if (hint) {
|
|
2892
3036
|
section("Hint");
|
|
2893
3037
|
console.log(row(chalk3.yellow(hint), w));
|
|
2894
3038
|
}
|
|
2895
3039
|
console.log(divRow(w));
|
|
2896
|
-
|
|
2897
|
-
console.log(row(
|
|
3040
|
+
if (refreshing) {
|
|
3041
|
+
console.log(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
3042
|
+
} else {
|
|
3043
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
3044
|
+
console.log(row(line, w));
|
|
3045
|
+
}
|
|
2898
3046
|
}
|
|
2899
3047
|
console.log(botRow(w));
|
|
2900
3048
|
}
|
|
@@ -2940,7 +3088,7 @@ async function waitForKey(validKeys) {
|
|
|
2940
3088
|
stdin.on("data", onData);
|
|
2941
3089
|
});
|
|
2942
3090
|
}
|
|
2943
|
-
async function executeAction(actionId, state
|
|
3091
|
+
async function executeAction(actionId, state) {
|
|
2944
3092
|
switch (actionId) {
|
|
2945
3093
|
case "quit":
|
|
2946
3094
|
return false;
|
|
@@ -3017,7 +3165,7 @@ async function executeAction(actionId, state, providerSetter) {
|
|
|
3017
3165
|
}
|
|
3018
3166
|
case "switch-provider":
|
|
3019
3167
|
clearScreen();
|
|
3020
|
-
await handleSwitchProvider(state
|
|
3168
|
+
await handleSwitchProvider(state);
|
|
3021
3169
|
return true;
|
|
3022
3170
|
case "checkout-main": {
|
|
3023
3171
|
clearScreen();
|
|
@@ -3040,15 +3188,47 @@ async function handleCommit(state) {
|
|
|
3040
3188
|
console.log(status);
|
|
3041
3189
|
console.log();
|
|
3042
3190
|
await execa4("git", ["add", "-A"]);
|
|
3043
|
-
const { stdout: diffStat } = await execa4("git", [
|
|
3191
|
+
const { stdout: diffStat } = await execa4("git", [
|
|
3192
|
+
"diff",
|
|
3193
|
+
"--cached",
|
|
3194
|
+
"--stat"
|
|
3195
|
+
]);
|
|
3044
3196
|
const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
|
|
3045
3197
|
const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
|
|
3046
3198
|
const issueNumber = state.issue?.number ?? null;
|
|
3047
3199
|
const issueTitle = state.issue?.title ?? null;
|
|
3048
3200
|
const provider = state.config.ai.provider;
|
|
3049
3201
|
const providerName = getProviderDisplayName(provider);
|
|
3050
|
-
|
|
3051
|
-
|
|
3202
|
+
const { mode } = await inquirer7.prompt([
|
|
3203
|
+
{
|
|
3204
|
+
type: "list",
|
|
3205
|
+
name: "mode",
|
|
3206
|
+
message: "How would you like to provide the commit message?",
|
|
3207
|
+
choices: [
|
|
3208
|
+
{ name: `Generate with ${providerName}`, value: "ai" },
|
|
3209
|
+
{ name: "Enter manually", value: "manual" }
|
|
3210
|
+
]
|
|
3211
|
+
}
|
|
3212
|
+
]);
|
|
3213
|
+
let message;
|
|
3214
|
+
if (mode === "manual") {
|
|
3215
|
+
const { manualInput } = await inquirer7.prompt([
|
|
3216
|
+
{
|
|
3217
|
+
type: "input",
|
|
3218
|
+
name: "manualInput",
|
|
3219
|
+
message: "Commit message (empty to cancel):"
|
|
3220
|
+
}
|
|
3221
|
+
]);
|
|
3222
|
+
message = manualInput.trim() || CANCEL;
|
|
3223
|
+
} else {
|
|
3224
|
+
logger.info(`Generating commit message with ${providerName}...`);
|
|
3225
|
+
message = await generateCommitMessage(
|
|
3226
|
+
diffContent,
|
|
3227
|
+
issueNumber,
|
|
3228
|
+
issueTitle,
|
|
3229
|
+
state
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3052
3232
|
if (message === CANCEL) {
|
|
3053
3233
|
await execa4("git", ["reset", "HEAD"]);
|
|
3054
3234
|
logger.info("Cancelled");
|
|
@@ -3076,7 +3256,11 @@ Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
|
3076
3256
|
}
|
|
3077
3257
|
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
|
|
3078
3258
|
try {
|
|
3079
|
-
const prompt = buildCommitMessagePrompt(
|
|
3259
|
+
const prompt = buildCommitMessagePrompt(
|
|
3260
|
+
diffContent,
|
|
3261
|
+
issueNumber,
|
|
3262
|
+
issueTitle
|
|
3263
|
+
);
|
|
3080
3264
|
const result = await invokeAI({ prompt, streamOutput: true }, state.config);
|
|
3081
3265
|
let message = result.output.trim().split("\n")[0].trim();
|
|
3082
3266
|
for (const q of ['"', "'", "`"]) {
|
|
@@ -3157,7 +3341,7 @@ async function handlePush() {
|
|
|
3157
3341
|
}
|
|
3158
3342
|
}
|
|
3159
3343
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3160
|
-
async function handleSwitchProvider(state
|
|
3344
|
+
async function handleSwitchProvider(state) {
|
|
3161
3345
|
const current = state.config.ai.provider;
|
|
3162
3346
|
const { provider } = await inquirer7.prompt([
|
|
3163
3347
|
{
|
|
@@ -3172,7 +3356,7 @@ async function handleSwitchProvider(state, setProvider) {
|
|
|
3172
3356
|
}
|
|
3173
3357
|
]);
|
|
3174
3358
|
if (provider === current) return;
|
|
3175
|
-
|
|
3359
|
+
setRuntimeProvider(provider);
|
|
3176
3360
|
logger.success(`Provider switched to ${provider} (session only)`);
|
|
3177
3361
|
}
|
|
3178
3362
|
async function handleCheckoutMain() {
|
|
@@ -3224,11 +3408,7 @@ async function promptContinue() {
|
|
|
3224
3408
|
}
|
|
3225
3409
|
async function tuiCommand() {
|
|
3226
3410
|
let running = true;
|
|
3227
|
-
let providerOverride = null;
|
|
3228
3411
|
let lastActions = [];
|
|
3229
|
-
const setProvider = (p) => {
|
|
3230
|
-
providerOverride = p;
|
|
3231
|
-
};
|
|
3232
3412
|
const config = loadConfig();
|
|
3233
3413
|
let lastState = {
|
|
3234
3414
|
isGitRepo: true,
|
|
@@ -3256,9 +3436,6 @@ async function tuiCommand() {
|
|
|
3256
3436
|
clearScreen();
|
|
3257
3437
|
renderDashboard(lastState, lastActions, void 0, true);
|
|
3258
3438
|
const state = await aggregateState();
|
|
3259
|
-
if (providerOverride) {
|
|
3260
|
-
state.config.ai.provider = providerOverride;
|
|
3261
|
-
}
|
|
3262
3439
|
const actions = getAvailableActions(state);
|
|
3263
3440
|
lastState = state;
|
|
3264
3441
|
lastActions = actions;
|
|
@@ -3276,7 +3453,7 @@ async function tuiCommand() {
|
|
|
3276
3453
|
const key = await waitForKey(validKeys);
|
|
3277
3454
|
const action = actions.find((a) => a.shortcut === key);
|
|
3278
3455
|
if (action) {
|
|
3279
|
-
running = await executeAction(action.id, state
|
|
3456
|
+
running = await executeAction(action.id, state);
|
|
3280
3457
|
}
|
|
3281
3458
|
}
|
|
3282
3459
|
}
|
|
@@ -3288,13 +3465,17 @@ function startVersionCheck() {
|
|
|
3288
3465
|
checkForUpdates().then((result) => {
|
|
3289
3466
|
if (result.updateAvailable && result.latestVersion) {
|
|
3290
3467
|
logger.newline();
|
|
3291
|
-
logger.warning(
|
|
3468
|
+
logger.warning(
|
|
3469
|
+
formatUpgradeNotification(result.currentVersion, result.latestVersion)
|
|
3470
|
+
);
|
|
3292
3471
|
}
|
|
3293
3472
|
}).catch(() => {
|
|
3294
3473
|
});
|
|
3295
3474
|
}
|
|
3296
3475
|
var program = new Command();
|
|
3297
|
-
program.name("gent").description(
|
|
3476
|
+
program.name("gent").description(
|
|
3477
|
+
"AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs"
|
|
3478
|
+
).version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
|
|
3298
3479
|
if (!thisCommand.opts().skipUpdateCheck) {
|
|
3299
3480
|
startVersionCheck();
|
|
3300
3481
|
}
|
|
@@ -3305,27 +3486,46 @@ program.command("init").description("Initialize gent workflow in current reposit
|
|
|
3305
3486
|
program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
|
|
3306
3487
|
await setupLabelsCommand();
|
|
3307
3488
|
});
|
|
3308
|
-
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
|
|
3309
|
-
|
|
3489
|
+
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
|
|
3490
|
+
"-p, --provider <provider>",
|
|
3491
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3492
|
+
).option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
|
|
3493
|
+
await createCommand(description, {
|
|
3494
|
+
yes: options.yes,
|
|
3495
|
+
provider: options.provider,
|
|
3496
|
+
title: options.title
|
|
3497
|
+
});
|
|
3310
3498
|
});
|
|
3311
|
-
program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
|
|
3499
|
+
program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
|
|
3500
|
+
"-s, --status <status>",
|
|
3501
|
+
"Filter by workflow status (ready, in-progress, completed, blocked, all)"
|
|
3502
|
+
).option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
|
|
3312
3503
|
await listCommand({
|
|
3313
3504
|
label: options.label,
|
|
3314
3505
|
status: options.status,
|
|
3315
3506
|
limit: parseInt(options.limit, 10)
|
|
3316
3507
|
});
|
|
3317
3508
|
});
|
|
3318
|
-
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
|
|
3509
|
+
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
|
|
3510
|
+
"-p, --provider <provider>",
|
|
3511
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3512
|
+
).action(async (issueNumber, options) => {
|
|
3319
3513
|
await runCommand(issueNumber, { provider: options.provider });
|
|
3320
3514
|
});
|
|
3321
|
-
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
|
|
3515
|
+
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
|
|
3516
|
+
"-p, --provider <provider>",
|
|
3517
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3518
|
+
).option("--no-video", "Disable video capture for UI changes").action(async (options) => {
|
|
3322
3519
|
await prCommand({
|
|
3323
3520
|
draft: options.draft,
|
|
3324
3521
|
provider: options.provider,
|
|
3325
3522
|
video: options.video
|
|
3326
3523
|
});
|
|
3327
3524
|
});
|
|
3328
|
-
program.command("fix").description("Apply PR review feedback using AI").option(
|
|
3525
|
+
program.command("fix").description("Apply PR review feedback using AI").option(
|
|
3526
|
+
"-p, --provider <provider>",
|
|
3527
|
+
"AI provider to use (claude, gemini, or codex)"
|
|
3528
|
+
).action(async (options) => {
|
|
3329
3529
|
await fixCommand({ provider: options.provider });
|
|
3330
3530
|
});
|
|
3331
3531
|
program.command("status").description("Show current workflow status").action(async () => {
|