@rotorsoft/gent 1.7.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -7
- package/dist/{chunk-2LGYNV6S.js → chunk-CMDTYS6L.js} +60 -4
- package/dist/chunk-CMDTYS6L.js.map +1 -0
- package/dist/index.js +356 -26
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-3IMSEEKN.js → setup-labels-IQOZF4U7.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-2LGYNV6S.js.map +0 -1
- /package/dist/{setup-labels-3IMSEEKN.js.map → setup-labels-IQOZF4U7.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getCurrentUser,
|
|
20
20
|
getIssue,
|
|
21
21
|
getPrForBranch,
|
|
22
|
+
getPrReviewData,
|
|
22
23
|
getWorkflowLabels,
|
|
23
24
|
isValidIssueNumber,
|
|
24
25
|
listIssues,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
sortByPriority,
|
|
31
32
|
updateIssueLabels,
|
|
32
33
|
withSpinner
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-CMDTYS6L.js";
|
|
34
35
|
|
|
35
36
|
// src/index.ts
|
|
36
37
|
import { Command } from "commander";
|
|
@@ -154,8 +155,17 @@ async function initCommand(options) {
|
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
}
|
|
158
|
+
const { provider } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: "list",
|
|
161
|
+
name: "provider",
|
|
162
|
+
message: "Which AI provider would you like to use by default?",
|
|
163
|
+
choices: ["claude", "gemini", "codex"],
|
|
164
|
+
default: "claude"
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
157
167
|
const configPath = getConfigPath(cwd);
|
|
158
|
-
writeFileSync2(configPath, generateDefaultConfig(), "utf-8");
|
|
168
|
+
writeFileSync2(configPath, generateDefaultConfig(provider), "utf-8");
|
|
159
169
|
logger.success(`Created ${colors.file(".gent.yml")}`);
|
|
160
170
|
const agentPath = join2(cwd, "AGENT.md");
|
|
161
171
|
if (!existsSync2(agentPath) || options.force) {
|
|
@@ -182,7 +192,7 @@ async function initCommand(options) {
|
|
|
182
192
|
}
|
|
183
193
|
]);
|
|
184
194
|
if (setupLabels) {
|
|
185
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
195
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-IQOZF4U7.js");
|
|
186
196
|
await setupLabelsCommand2();
|
|
187
197
|
}
|
|
188
198
|
}
|
|
@@ -194,17 +204,27 @@ import chalk from "chalk";
|
|
|
194
204
|
// src/lib/ai-provider.ts
|
|
195
205
|
import { spawn } from "child_process";
|
|
196
206
|
import { execa } from "execa";
|
|
207
|
+
async function invokeInternal(provider, options) {
|
|
208
|
+
switch (provider) {
|
|
209
|
+
case "claude":
|
|
210
|
+
return invokeClaudeInternal(options);
|
|
211
|
+
case "gemini":
|
|
212
|
+
return invokeGeminiInternal(options);
|
|
213
|
+
case "codex":
|
|
214
|
+
return invokeCodexInternal(options);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
197
217
|
async function invokeAI(options, config, providerOverride) {
|
|
198
218
|
const provider = providerOverride ?? config.ai.provider;
|
|
199
219
|
try {
|
|
200
|
-
const output =
|
|
220
|
+
const output = await invokeInternal(provider, options);
|
|
201
221
|
return { output, provider };
|
|
202
222
|
} catch (error) {
|
|
203
223
|
if (isRateLimitError(error, provider)) {
|
|
204
224
|
if (config.ai.auto_fallback && config.ai.fallback_provider && !providerOverride) {
|
|
205
225
|
const fallback = config.ai.fallback_provider;
|
|
206
226
|
logger.warning(`Rate limit reached on ${getProviderDisplayName(provider)}, switching to ${getProviderDisplayName(fallback)}...`);
|
|
207
|
-
const output =
|
|
227
|
+
const output = await invokeInternal(fallback, options);
|
|
208
228
|
return { output, provider: fallback };
|
|
209
229
|
}
|
|
210
230
|
const err = error;
|
|
@@ -217,28 +237,52 @@ async function invokeAI(options, config, providerOverride) {
|
|
|
217
237
|
}
|
|
218
238
|
async function invokeAIInteractive(prompt, config, providerOverride) {
|
|
219
239
|
const provider = providerOverride ?? config.ai.provider;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
switch (provider) {
|
|
241
|
+
case "claude": {
|
|
242
|
+
const args = ["--permission-mode", config.claude.permission_mode, prompt];
|
|
243
|
+
return {
|
|
244
|
+
result: execa("claude", args, { stdio: "inherit" }),
|
|
245
|
+
provider
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
case "gemini": {
|
|
249
|
+
return {
|
|
250
|
+
result: execa("gemini", ["-i", prompt], { stdio: "inherit" }),
|
|
251
|
+
provider
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
case "codex": {
|
|
255
|
+
const args = prompt ? [prompt] : [];
|
|
256
|
+
return {
|
|
257
|
+
result: execa("codex", args, { stdio: "inherit" }),
|
|
258
|
+
provider
|
|
259
|
+
};
|
|
260
|
+
}
|
|
231
261
|
}
|
|
232
262
|
}
|
|
233
263
|
function getProviderDisplayName(provider) {
|
|
234
|
-
|
|
264
|
+
switch (provider) {
|
|
265
|
+
case "claude":
|
|
266
|
+
return "Claude";
|
|
267
|
+
case "gemini":
|
|
268
|
+
return "Gemini";
|
|
269
|
+
case "codex":
|
|
270
|
+
return "Codex";
|
|
271
|
+
}
|
|
235
272
|
}
|
|
236
273
|
function getProviderEmail(provider) {
|
|
237
|
-
|
|
274
|
+
switch (provider) {
|
|
275
|
+
case "claude":
|
|
276
|
+
return "noreply@anthropic.com";
|
|
277
|
+
case "gemini":
|
|
278
|
+
return "noreply@google.com";
|
|
279
|
+
case "codex":
|
|
280
|
+
return "noreply@openai.com";
|
|
281
|
+
}
|
|
238
282
|
}
|
|
239
283
|
function isRateLimitError(error, provider) {
|
|
240
284
|
if (!error || typeof error !== "object") return false;
|
|
241
|
-
if (provider === "claude" && "exitCode" in error && error.exitCode === 2) {
|
|
285
|
+
if ((provider === "claude" || provider === "codex") && "exitCode" in error && error.exitCode === 2) {
|
|
242
286
|
return true;
|
|
243
287
|
}
|
|
244
288
|
if ("message" in error && typeof error.message === "string") {
|
|
@@ -330,6 +374,44 @@ async function invokeGeminiInternal(options) {
|
|
|
330
374
|
return stdout;
|
|
331
375
|
}
|
|
332
376
|
}
|
|
377
|
+
async function invokeCodexInternal(options) {
|
|
378
|
+
const args = ["exec", options.prompt];
|
|
379
|
+
if (options.printOutput) {
|
|
380
|
+
const subprocess = execa("codex", args, {
|
|
381
|
+
stdio: "inherit"
|
|
382
|
+
});
|
|
383
|
+
await subprocess;
|
|
384
|
+
return "";
|
|
385
|
+
} else if (options.streamOutput) {
|
|
386
|
+
return new Promise((resolve, reject) => {
|
|
387
|
+
const child = spawn("codex", args, {
|
|
388
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
389
|
+
});
|
|
390
|
+
let output = "";
|
|
391
|
+
child.stdout.on("data", (chunk) => {
|
|
392
|
+
const text = chunk.toString();
|
|
393
|
+
output += text;
|
|
394
|
+
process.stdout.write(text);
|
|
395
|
+
});
|
|
396
|
+
child.stderr.on("data", (chunk) => {
|
|
397
|
+
process.stderr.write(chunk);
|
|
398
|
+
});
|
|
399
|
+
child.on("close", (code) => {
|
|
400
|
+
if (code === 0) {
|
|
401
|
+
resolve(output);
|
|
402
|
+
} else {
|
|
403
|
+
const error = new Error(`Codex exited with code ${code}`);
|
|
404
|
+
error.exitCode = code ?? 1;
|
|
405
|
+
reject(error);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
child.on("error", reject);
|
|
409
|
+
});
|
|
410
|
+
} else {
|
|
411
|
+
const { stdout } = await execa("codex", args);
|
|
412
|
+
return stdout;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
333
415
|
|
|
334
416
|
// src/lib/prompts.ts
|
|
335
417
|
function buildTicketPrompt(description, agentInstructions, additionalHints = null) {
|
|
@@ -386,7 +468,7 @@ META:type=<type>,priority=<priority>,risk=<risk>,area=<area>
|
|
|
386
468
|
Example: META:type=feature,priority=high,risk=low,area=ui`;
|
|
387
469
|
return basePrompt;
|
|
388
470
|
}
|
|
389
|
-
function buildImplementationPrompt(issue, agentInstructions, progressContent, config) {
|
|
471
|
+
function buildImplementationPrompt(issue, agentInstructions, progressContent, config, reviewFeedback = null) {
|
|
390
472
|
const providerName = getProviderDisplayName(config.ai.provider);
|
|
391
473
|
const providerEmail = getProviderEmail(config.ai.provider);
|
|
392
474
|
return `GitHub Issue #${issue.number}: ${issue.title}
|
|
@@ -400,6 +482,10 @@ ${agentInstructions}
|
|
|
400
482
|
${progressContent ? `## Previous Progress
|
|
401
483
|
${progressContent}
|
|
402
484
|
|
|
485
|
+
` : ""}
|
|
486
|
+
${reviewFeedback ? `## Review Feedback
|
|
487
|
+
${reviewFeedback}
|
|
488
|
+
|
|
403
489
|
` : ""}
|
|
404
490
|
|
|
405
491
|
## Your Task
|
|
@@ -1353,6 +1439,247 @@ function generateFallbackBody(issue, commits) {
|
|
|
1353
1439
|
return body;
|
|
1354
1440
|
}
|
|
1355
1441
|
|
|
1442
|
+
// src/commands/fix.ts
|
|
1443
|
+
import inquirer5 from "inquirer";
|
|
1444
|
+
|
|
1445
|
+
// src/lib/review-feedback.ts
|
|
1446
|
+
var ACTIONABLE_KEYWORDS = [
|
|
1447
|
+
"todo",
|
|
1448
|
+
"fix",
|
|
1449
|
+
"should",
|
|
1450
|
+
"must",
|
|
1451
|
+
"needs",
|
|
1452
|
+
"please",
|
|
1453
|
+
"consider",
|
|
1454
|
+
"can you",
|
|
1455
|
+
"change",
|
|
1456
|
+
"update",
|
|
1457
|
+
"remove",
|
|
1458
|
+
"add"
|
|
1459
|
+
];
|
|
1460
|
+
var TRIVIAL_COMMENTS = ["lgtm", "looks good", "approved"];
|
|
1461
|
+
function summarizeReviewFeedback(data) {
|
|
1462
|
+
const items = extractReviewFeedbackItems(data);
|
|
1463
|
+
return {
|
|
1464
|
+
items,
|
|
1465
|
+
summary: items.length > 0 ? formatReviewFeedbackSummary(items) : ""
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
function extractReviewFeedbackItems(data) {
|
|
1469
|
+
const items = [];
|
|
1470
|
+
for (const review of data.reviews) {
|
|
1471
|
+
const body = review.body?.trim() ?? "";
|
|
1472
|
+
if (!body || isTrivialComment(body)) {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
const isChangesRequested = review.state === "CHANGES_REQUESTED";
|
|
1476
|
+
const actionable = isChangesRequested || isActionableText(body);
|
|
1477
|
+
if (!actionable) {
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
items.push({
|
|
1481
|
+
source: "review",
|
|
1482
|
+
author: review.author,
|
|
1483
|
+
body,
|
|
1484
|
+
state: review.state
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
for (const thread of data.reviewThreads) {
|
|
1488
|
+
if (!isActionableThread(thread)) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const comments = thread.comments ?? [];
|
|
1492
|
+
const latestComment = findLatestMeaningfulComment(comments);
|
|
1493
|
+
if (!latestComment) {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
items.push({
|
|
1497
|
+
source: "thread",
|
|
1498
|
+
author: latestComment.author,
|
|
1499
|
+
body: latestComment.body,
|
|
1500
|
+
path: thread.path ?? latestComment.path,
|
|
1501
|
+
line: thread.line ?? latestComment.line ?? null
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
return items;
|
|
1505
|
+
}
|
|
1506
|
+
function formatReviewFeedbackSummary(items) {
|
|
1507
|
+
return items.map((item) => {
|
|
1508
|
+
const location = formatLocation(item);
|
|
1509
|
+
const stateLabel = item.state ? formatState(item.state) : null;
|
|
1510
|
+
const author = item.author ? `@${item.author}` : "Reviewer";
|
|
1511
|
+
const body = truncateComment(item.body);
|
|
1512
|
+
const header = item.source === "review" ? stateLabel ? `Review (${stateLabel})` : "Review" : location;
|
|
1513
|
+
return `- [${header}] ${author}: ${body}`;
|
|
1514
|
+
}).join("\n");
|
|
1515
|
+
}
|
|
1516
|
+
function isActionableThread(thread) {
|
|
1517
|
+
if (thread.isResolved === false || thread.isResolved === void 0 || thread.isResolved === null) {
|
|
1518
|
+
return true;
|
|
1519
|
+
}
|
|
1520
|
+
return (thread.comments ?? []).some((comment) => isActionableText(comment.body));
|
|
1521
|
+
}
|
|
1522
|
+
function isActionableText(text) {
|
|
1523
|
+
const normalized = text.toLowerCase();
|
|
1524
|
+
return ACTIONABLE_KEYWORDS.some((keyword) => normalized.includes(keyword));
|
|
1525
|
+
}
|
|
1526
|
+
function isTrivialComment(text) {
|
|
1527
|
+
const normalized = text.trim().toLowerCase();
|
|
1528
|
+
return TRIVIAL_COMMENTS.some((entry) => normalized === entry);
|
|
1529
|
+
}
|
|
1530
|
+
function findLatestMeaningfulComment(comments) {
|
|
1531
|
+
for (let i = comments.length - 1; i >= 0; i -= 1) {
|
|
1532
|
+
const body = comments[i].body?.trim() ?? "";
|
|
1533
|
+
if (body && !isTrivialComment(body)) {
|
|
1534
|
+
return comments[i];
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
function formatLocation(item) {
|
|
1540
|
+
if (item.path && item.line) {
|
|
1541
|
+
return `${item.path}:${item.line}`;
|
|
1542
|
+
}
|
|
1543
|
+
if (item.path) {
|
|
1544
|
+
return item.path;
|
|
1545
|
+
}
|
|
1546
|
+
return "Thread";
|
|
1547
|
+
}
|
|
1548
|
+
function formatState(state) {
|
|
1549
|
+
return state.replace(/_/g, " ").toLowerCase();
|
|
1550
|
+
}
|
|
1551
|
+
function truncateComment(body, maxLength = 200) {
|
|
1552
|
+
const normalized = body.replace(/\s+/g, " ").trim();
|
|
1553
|
+
if (normalized.length <= maxLength) {
|
|
1554
|
+
return normalized;
|
|
1555
|
+
}
|
|
1556
|
+
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// src/commands/fix.ts
|
|
1560
|
+
async function fixCommand(options) {
|
|
1561
|
+
logger.bold("Applying PR review feedback with AI...");
|
|
1562
|
+
logger.newline();
|
|
1563
|
+
const config = loadConfig();
|
|
1564
|
+
const provider = options.provider ?? config.ai.provider;
|
|
1565
|
+
const providerName = getProviderDisplayName(provider);
|
|
1566
|
+
const [ghAuth, aiOk] = await Promise.all([
|
|
1567
|
+
checkGhAuth(),
|
|
1568
|
+
checkAIProvider(provider)
|
|
1569
|
+
]);
|
|
1570
|
+
if (!ghAuth) {
|
|
1571
|
+
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
1572
|
+
process.exit(1);
|
|
1573
|
+
}
|
|
1574
|
+
if (!aiOk) {
|
|
1575
|
+
logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
if (await isOnMainBranch()) {
|
|
1579
|
+
logger.error("Cannot apply fixes from main/master branch. Switch to the PR branch first.");
|
|
1580
|
+
process.exit(1);
|
|
1581
|
+
}
|
|
1582
|
+
const hasChanges = await hasUncommittedChanges();
|
|
1583
|
+
if (hasChanges) {
|
|
1584
|
+
logger.warning("You have uncommitted changes.");
|
|
1585
|
+
const { proceed } = await inquirer5.prompt([
|
|
1586
|
+
{
|
|
1587
|
+
type: "confirm",
|
|
1588
|
+
name: "proceed",
|
|
1589
|
+
message: "Continue anyway?",
|
|
1590
|
+
default: false
|
|
1591
|
+
}
|
|
1592
|
+
]);
|
|
1593
|
+
if (!proceed) {
|
|
1594
|
+
logger.info("Aborting. Please commit or stash your changes first.");
|
|
1595
|
+
process.exit(0);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
const pr = await withSpinner("Resolving pull request...", async () => {
|
|
1599
|
+
return getPrForBranch();
|
|
1600
|
+
});
|
|
1601
|
+
if (!pr) {
|
|
1602
|
+
logger.error("No pull request found for the current branch. Create one with 'gent pr' first.");
|
|
1603
|
+
process.exit(1);
|
|
1604
|
+
}
|
|
1605
|
+
const reviewData = await withSpinner("Fetching review feedback...", async () => {
|
|
1606
|
+
return getPrReviewData(pr.number);
|
|
1607
|
+
});
|
|
1608
|
+
const totalComments = countReviewComments(reviewData);
|
|
1609
|
+
if (totalComments === 0) {
|
|
1610
|
+
logger.error(`No review comments found for PR #${pr.number}.`);
|
|
1611
|
+
process.exit(1);
|
|
1612
|
+
}
|
|
1613
|
+
const { items, summary } = summarizeReviewFeedback(reviewData);
|
|
1614
|
+
if (items.length === 0 || !summary) {
|
|
1615
|
+
logger.error("No actionable review feedback found.");
|
|
1616
|
+
process.exit(1);
|
|
1617
|
+
}
|
|
1618
|
+
logger.newline();
|
|
1619
|
+
logger.box("Review Feedback Summary", summary);
|
|
1620
|
+
logger.newline();
|
|
1621
|
+
const currentBranch = await getCurrentBranch();
|
|
1622
|
+
const issueNumber = extractIssueNumber(currentBranch);
|
|
1623
|
+
if (!issueNumber) {
|
|
1624
|
+
logger.error("Could not determine issue number from branch name.");
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
const issue = await withSpinner("Fetching linked issue...", async () => {
|
|
1628
|
+
return getIssue(issueNumber);
|
|
1629
|
+
});
|
|
1630
|
+
const agentInstructions = loadAgentInstructions();
|
|
1631
|
+
const progressContent = readProgress(config);
|
|
1632
|
+
const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config, summary);
|
|
1633
|
+
logger.newline();
|
|
1634
|
+
logger.info(`Starting ${colors.provider(providerName)} fix session...`);
|
|
1635
|
+
logger.dim("Review feedback will be appended to the implementation prompt.");
|
|
1636
|
+
logger.newline();
|
|
1637
|
+
const beforeSha = await getCurrentCommitSha();
|
|
1638
|
+
let wasCancelled = false;
|
|
1639
|
+
const handleSignal = () => {
|
|
1640
|
+
wasCancelled = true;
|
|
1641
|
+
};
|
|
1642
|
+
process.on("SIGINT", handleSignal);
|
|
1643
|
+
process.on("SIGTERM", handleSignal);
|
|
1644
|
+
let aiExitCode;
|
|
1645
|
+
try {
|
|
1646
|
+
const { result } = await invokeAIInteractive(prompt, config, options.provider);
|
|
1647
|
+
aiExitCode = result.exitCode ?? void 0;
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
if (error && typeof error === "object" && "exitCode" in error) {
|
|
1650
|
+
aiExitCode = error.exitCode;
|
|
1651
|
+
}
|
|
1652
|
+
logger.error(`${providerName} session failed: ${error}`);
|
|
1653
|
+
} finally {
|
|
1654
|
+
process.off("SIGINT", handleSignal);
|
|
1655
|
+
process.off("SIGTERM", handleSignal);
|
|
1656
|
+
}
|
|
1657
|
+
logger.newline();
|
|
1658
|
+
if (wasCancelled) {
|
|
1659
|
+
logger.warning("Operation was cancelled. No changes were recorded.");
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
const commitsCreated = await hasNewCommits(beforeSha);
|
|
1663
|
+
if (commitsCreated) {
|
|
1664
|
+
logger.success(`${providerName} session completed with new commits.`);
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const isRateLimited = aiExitCode === 2;
|
|
1668
|
+
if (isRateLimited) {
|
|
1669
|
+
logger.warning(`${providerName} session ended due to rate limits. No commits were created.`);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
logger.warning(`${providerName} session completed but no commits were created.`);
|
|
1673
|
+
}
|
|
1674
|
+
function countReviewComments(data) {
|
|
1675
|
+
const reviewBodies = data.reviews.filter((review) => review.body?.trim()).length;
|
|
1676
|
+
const threadBodies = data.reviewThreads.reduce((count, thread) => {
|
|
1677
|
+
const threadCount = (thread.comments ?? []).filter((comment) => comment.body?.trim()).length;
|
|
1678
|
+
return count + threadCount;
|
|
1679
|
+
}, 0);
|
|
1680
|
+
return reviewBodies + threadBodies;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1356
1683
|
// src/lib/version.ts
|
|
1357
1684
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1358
1685
|
import { join as join3 } from "path";
|
|
@@ -1361,8 +1688,8 @@ import { homedir } from "os";
|
|
|
1361
1688
|
// package.json
|
|
1362
1689
|
var package_default = {
|
|
1363
1690
|
name: "@rotorsoft/gent",
|
|
1364
|
-
version: "1.
|
|
1365
|
-
description: "AI-powered GitHub workflow CLI - leverage AI (Claude or
|
|
1691
|
+
version: "1.9.0",
|
|
1692
|
+
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
1366
1693
|
keywords: [
|
|
1367
1694
|
"cli",
|
|
1368
1695
|
"ai",
|
|
@@ -1703,7 +2030,7 @@ function startVersionCheck() {
|
|
|
1703
2030
|
});
|
|
1704
2031
|
}
|
|
1705
2032
|
var program = new Command();
|
|
1706
|
-
program.name("gent").description("AI-powered GitHub workflow CLI - leverage AI (Claude or
|
|
2033
|
+
program.name("gent").description("AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs").version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
|
|
1707
2034
|
if (!thisCommand.opts().skipUpdateCheck) {
|
|
1708
2035
|
startVersionCheck();
|
|
1709
2036
|
}
|
|
@@ -1714,7 +2041,7 @@ program.command("init").description("Initialize gent workflow in current reposit
|
|
|
1714
2041
|
program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
|
|
1715
2042
|
await setupLabelsCommand();
|
|
1716
2043
|
});
|
|
1717
|
-
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option("-p, --provider <provider>", "AI provider to use (claude or
|
|
2044
|
+
program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
|
|
1718
2045
|
await createCommand(description, { yes: options.yes, provider: options.provider, title: options.title });
|
|
1719
2046
|
});
|
|
1720
2047
|
program.command("list").description("List GitHub issues by label/status").option("-l, --label <label>", "Filter by label").option("-s, --status <status>", "Filter by workflow status (ready, in-progress, completed, blocked, all)").option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
|
|
@@ -1724,12 +2051,15 @@ program.command("list").description("List GitHub issues by label/status").option
|
|
|
1724
2051
|
limit: parseInt(options.limit, 10)
|
|
1725
2052
|
});
|
|
1726
2053
|
});
|
|
1727
|
-
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-a, --auto", "Auto-select highest priority ai-ready issue").option("-p, --provider <provider>", "AI provider to use (claude or
|
|
2054
|
+
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-a, --auto", "Auto-select highest priority ai-ready issue").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (issueNumber, options) => {
|
|
1728
2055
|
await runCommand(issueNumber, { auto: options.auto, provider: options.provider });
|
|
1729
2056
|
});
|
|
1730
|
-
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude or
|
|
2057
|
+
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (options) => {
|
|
1731
2058
|
await prCommand({ draft: options.draft, provider: options.provider });
|
|
1732
2059
|
});
|
|
2060
|
+
program.command("fix").description("Apply PR review feedback using AI").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (options) => {
|
|
2061
|
+
await fixCommand({ provider: options.provider });
|
|
2062
|
+
});
|
|
1733
2063
|
program.command("status").description("Show current workflow status").action(async () => {
|
|
1734
2064
|
await statusCommand();
|
|
1735
2065
|
});
|