@rotorsoft/gent 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/{chunk-3N6U3NTN.js → chunk-UWYWIOYZ.js} +5 -3
- package/dist/chunk-UWYWIOYZ.js.map +1 -0
- package/dist/index.js +875 -15
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-HKXXO65J.js → setup-labels-5EHRPUPF.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-3N6U3NTN.js.map +0 -1
- /package/dist/{setup-labels-HKXXO65J.js.map → setup-labels-5EHRPUPF.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
configExists,
|
|
14
14
|
createIssue,
|
|
15
15
|
createPullRequest,
|
|
16
|
+
createSpinner,
|
|
16
17
|
extractPriorityFromLabels,
|
|
17
18
|
extractTypeFromLabels,
|
|
18
19
|
generateDefaultConfig,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
sortByPriority,
|
|
35
36
|
updateIssueLabels,
|
|
36
37
|
withSpinner
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-UWYWIOYZ.js";
|
|
38
39
|
|
|
39
40
|
// src/index.ts
|
|
40
41
|
import { Command } from "commander";
|
|
@@ -195,7 +196,7 @@ async function initCommand(options) {
|
|
|
195
196
|
}
|
|
196
197
|
]);
|
|
197
198
|
if (setupLabels) {
|
|
198
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
199
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-5EHRPUPF.js");
|
|
199
200
|
await setupLabelsCommand2();
|
|
200
201
|
}
|
|
201
202
|
}
|
|
@@ -471,7 +472,7 @@ META:type=<type>,priority=<priority>,risk=<risk>,area=<area>
|
|
|
471
472
|
Example: META:type=feature,priority=high,risk=low,area=ui`;
|
|
472
473
|
return basePrompt;
|
|
473
474
|
}
|
|
474
|
-
function buildImplementationPrompt(issue, agentInstructions, progressContent, config,
|
|
475
|
+
function buildImplementationPrompt(issue, agentInstructions, progressContent, config, extraContext = null) {
|
|
475
476
|
const providerName = getProviderDisplayName(config.ai.provider);
|
|
476
477
|
const providerEmail = getProviderEmail(config.ai.provider);
|
|
477
478
|
return `GitHub Issue #${issue.number}: ${issue.title}
|
|
@@ -486,8 +487,7 @@ ${progressContent ? `## Previous Progress
|
|
|
486
487
|
${progressContent}
|
|
487
488
|
|
|
488
489
|
` : ""}
|
|
489
|
-
${
|
|
490
|
-
${reviewFeedback}
|
|
490
|
+
${extraContext ? `${extraContext}
|
|
491
491
|
|
|
492
492
|
` : ""}
|
|
493
493
|
|
|
@@ -540,6 +540,22 @@ ${issue ? `Closes #${issue.number}` : ""}
|
|
|
540
540
|
|
|
541
541
|
Only output the PR description, nothing else.`;
|
|
542
542
|
}
|
|
543
|
+
function buildCommitMessagePrompt(diff, issueNumber, issueTitle) {
|
|
544
|
+
const issueContext = issueNumber ? `
|
|
545
|
+
Related Issue: #${issueNumber}${issueTitle ? ` - ${issueTitle}` : ""}
|
|
546
|
+
` : "";
|
|
547
|
+
return `Generate a concise git commit message for the following changes.
|
|
548
|
+
${issueContext}
|
|
549
|
+
## Diff
|
|
550
|
+
${diff}
|
|
551
|
+
|
|
552
|
+
Rules:
|
|
553
|
+
- Use conventional commit format: <type>: <short description>
|
|
554
|
+
- Types: feat, fix, refactor, chore, docs, test, style, perf
|
|
555
|
+
- Keep the first line under 72 characters
|
|
556
|
+
- Do NOT include a body or footer
|
|
557
|
+
- Output ONLY the commit message, nothing else`;
|
|
558
|
+
}
|
|
543
559
|
function parseTicketMeta(output) {
|
|
544
560
|
const metaMatch = output.match(
|
|
545
561
|
/META:type=(\w+),priority=(\w+),risk=(\w+),area=(\w+)/
|
|
@@ -592,6 +608,46 @@ function generateFallbackTitle(description) {
|
|
|
592
608
|
}
|
|
593
609
|
return truncated;
|
|
594
610
|
}
|
|
611
|
+
function buildVideoPrompt(issueNumber, issueTitle, videoConfig, agentInstructions) {
|
|
612
|
+
return `You are helping capture a Playwright video demonstration of UI changes for GitHub Issue #${issueNumber}: ${issueTitle}
|
|
613
|
+
|
|
614
|
+
${agentInstructions ? `## Project-Specific Instructions
|
|
615
|
+
${agentInstructions}
|
|
616
|
+
|
|
617
|
+
` : ""}
|
|
618
|
+
|
|
619
|
+
## Task: Record UI Demo Video
|
|
620
|
+
|
|
621
|
+
Create a short video (max ${videoConfig.max_duration}s) demonstrating the UI changes made for this issue.
|
|
622
|
+
|
|
623
|
+
### Video Requirements
|
|
624
|
+
- Resolution: ${videoConfig.width}x${videoConfig.height}
|
|
625
|
+
- Format: WebM or MP4
|
|
626
|
+
- Duration: Under ${videoConfig.max_duration} seconds
|
|
627
|
+
- Show the key UI interactions and visual changes
|
|
628
|
+
|
|
629
|
+
### Steps
|
|
630
|
+
|
|
631
|
+
1. **Start the development server** if not already running
|
|
632
|
+
2. **Use Playwright to record video** of the relevant UI interactions:
|
|
633
|
+
- Navigate to the affected pages/components
|
|
634
|
+
- Demonstrate the new or changed functionality
|
|
635
|
+
- Show before/after if applicable
|
|
636
|
+
|
|
637
|
+
3. **Upload video to GitHub** as a release asset or use GitHub's drag-drop upload:
|
|
638
|
+
- Create a GitHub release or upload to issue comments
|
|
639
|
+
- Get the permanent URL for the video
|
|
640
|
+
- Do NOT commit video files to the repository
|
|
641
|
+
|
|
642
|
+
4. **Add video to PR** by commenting with the video URL or embedding it
|
|
643
|
+
|
|
644
|
+
### Important
|
|
645
|
+
- Upload video to GitHub assets, NOT to the repository
|
|
646
|
+
- Keep the video concise - focus on demonstrating the changes
|
|
647
|
+
- Ensure the video clearly shows the UI improvements
|
|
648
|
+
|
|
649
|
+
Output the GitHub URL where the video was uploaded when complete.`;
|
|
650
|
+
}
|
|
595
651
|
|
|
596
652
|
// src/commands/create.ts
|
|
597
653
|
async function createCommand(description, options) {
|
|
@@ -606,11 +662,11 @@ async function createCommand(description, options) {
|
|
|
606
662
|
]);
|
|
607
663
|
if (!ghAuth) {
|
|
608
664
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
609
|
-
|
|
665
|
+
return;
|
|
610
666
|
}
|
|
611
667
|
if (!aiOk) {
|
|
612
668
|
logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
|
|
613
|
-
|
|
669
|
+
return;
|
|
614
670
|
}
|
|
615
671
|
const agentInstructions = loadAgentInstructions();
|
|
616
672
|
let aiOutput;
|
|
@@ -1057,11 +1113,11 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1057
1113
|
]);
|
|
1058
1114
|
if (!ghAuth) {
|
|
1059
1115
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
1060
|
-
|
|
1116
|
+
return;
|
|
1061
1117
|
}
|
|
1062
1118
|
if (!aiOk) {
|
|
1063
1119
|
logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
|
|
1064
|
-
|
|
1120
|
+
return;
|
|
1065
1121
|
}
|
|
1066
1122
|
const hasChanges = await hasUncommittedChanges();
|
|
1067
1123
|
if (hasChanges) {
|
|
@@ -1076,7 +1132,7 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1076
1132
|
]);
|
|
1077
1133
|
if (!proceed) {
|
|
1078
1134
|
logger.info("Aborting. Please commit or stash your changes first.");
|
|
1079
|
-
|
|
1135
|
+
return;
|
|
1080
1136
|
}
|
|
1081
1137
|
}
|
|
1082
1138
|
const workflowLabels = getWorkflowLabels(config);
|
|
@@ -1085,19 +1141,19 @@ async function runCommand(issueNumberArg, options) {
|
|
|
1085
1141
|
const autoIssue = await autoSelectIssue(workflowLabels.ready);
|
|
1086
1142
|
if (!autoIssue) {
|
|
1087
1143
|
logger.error("No ai-ready issues found.");
|
|
1088
|
-
|
|
1144
|
+
return;
|
|
1089
1145
|
}
|
|
1090
1146
|
issueNumber = autoIssue.number;
|
|
1091
1147
|
logger.info(`Auto-selected: ${colors.issue(`#${issueNumber}`)} - ${autoIssue.title}`);
|
|
1092
1148
|
} else if (issueNumberArg) {
|
|
1093
1149
|
if (!isValidIssueNumber(issueNumberArg)) {
|
|
1094
1150
|
logger.error("Invalid issue number.");
|
|
1095
|
-
|
|
1151
|
+
return;
|
|
1096
1152
|
}
|
|
1097
1153
|
issueNumber = parseInt(issueNumberArg, 10);
|
|
1098
1154
|
} else {
|
|
1099
1155
|
logger.error("Please provide an issue number or use --auto");
|
|
1100
|
-
|
|
1156
|
+
return;
|
|
1101
1157
|
}
|
|
1102
1158
|
let issue;
|
|
1103
1159
|
try {
|
|
@@ -1756,7 +1812,8 @@ async function fixCommand(options) {
|
|
|
1756
1812
|
});
|
|
1757
1813
|
const agentInstructions = loadAgentInstructions();
|
|
1758
1814
|
const progressContent = readProgress(config);
|
|
1759
|
-
const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config,
|
|
1815
|
+
const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config, `## Review Feedback
|
|
1816
|
+
${summary}`);
|
|
1760
1817
|
logger.newline();
|
|
1761
1818
|
logger.info(`Starting ${colors.provider(providerName)} fix session...`);
|
|
1762
1819
|
logger.dim("Review feedback will be appended to the implementation prompt.");
|
|
@@ -1836,7 +1893,7 @@ import { homedir } from "os";
|
|
|
1836
1893
|
// package.json
|
|
1837
1894
|
var package_default = {
|
|
1838
1895
|
name: "@rotorsoft/gent",
|
|
1839
|
-
version: "1.
|
|
1896
|
+
version: "1.12.1",
|
|
1840
1897
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
1841
1898
|
keywords: [
|
|
1842
1899
|
"cli",
|
|
@@ -2245,6 +2302,803 @@ async function statusCommand() {
|
|
|
2245
2302
|
}
|
|
2246
2303
|
}
|
|
2247
2304
|
|
|
2305
|
+
// src/commands/tui.ts
|
|
2306
|
+
import inquirer6 from "inquirer";
|
|
2307
|
+
import { execa as execa4 } from "execa";
|
|
2308
|
+
|
|
2309
|
+
// src/tui/state.ts
|
|
2310
|
+
async function aggregateState() {
|
|
2311
|
+
const isGitRepo = await checkGitRepo();
|
|
2312
|
+
if (!isGitRepo) {
|
|
2313
|
+
const config2 = loadConfig();
|
|
2314
|
+
return {
|
|
2315
|
+
isGitRepo: false,
|
|
2316
|
+
isGhAuthenticated: false,
|
|
2317
|
+
isAIProviderAvailable: false,
|
|
2318
|
+
config: config2,
|
|
2319
|
+
hasConfig: false,
|
|
2320
|
+
hasProgress: false,
|
|
2321
|
+
branch: "",
|
|
2322
|
+
branchInfo: null,
|
|
2323
|
+
isOnMain: false,
|
|
2324
|
+
hasUncommittedChanges: false,
|
|
2325
|
+
hasUnpushedCommits: false,
|
|
2326
|
+
commits: [],
|
|
2327
|
+
baseBranch: "main",
|
|
2328
|
+
issue: null,
|
|
2329
|
+
workflowStatus: "none",
|
|
2330
|
+
pr: null,
|
|
2331
|
+
reviewFeedback: [],
|
|
2332
|
+
hasActionableFeedback: false,
|
|
2333
|
+
hasUIChanges: false,
|
|
2334
|
+
isPlaywrightAvailable: false
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
const config = loadConfig();
|
|
2338
|
+
const workflowLabels = getWorkflowLabels(config);
|
|
2339
|
+
const [
|
|
2340
|
+
isGhAuthenticated,
|
|
2341
|
+
isAIProviderAvailable,
|
|
2342
|
+
branch,
|
|
2343
|
+
isOnMain,
|
|
2344
|
+
uncommitted,
|
|
2345
|
+
baseBranch
|
|
2346
|
+
] = await Promise.all([
|
|
2347
|
+
checkGhAuth(),
|
|
2348
|
+
checkAIProvider(config.ai.provider),
|
|
2349
|
+
getCurrentBranch(),
|
|
2350
|
+
isOnMainBranch(),
|
|
2351
|
+
hasUncommittedChanges(),
|
|
2352
|
+
getDefaultBranch()
|
|
2353
|
+
]);
|
|
2354
|
+
const hasConfig = configExists();
|
|
2355
|
+
const hasProgress = progressExists(config);
|
|
2356
|
+
const branchInfo = parseBranchName(branch);
|
|
2357
|
+
const [commits, unpushed] = await Promise.all([
|
|
2358
|
+
getCommitsSinceBase(baseBranch),
|
|
2359
|
+
getUnpushedCommits()
|
|
2360
|
+
]);
|
|
2361
|
+
let issue = null;
|
|
2362
|
+
let workflowStatus = "none";
|
|
2363
|
+
let pr = null;
|
|
2364
|
+
let reviewFeedback = [];
|
|
2365
|
+
let hasActionableFeedback = false;
|
|
2366
|
+
let uiChanges = false;
|
|
2367
|
+
let playwrightAvailable = false;
|
|
2368
|
+
if (!isOnMain) {
|
|
2369
|
+
const issueNumber = extractIssueNumber(branch);
|
|
2370
|
+
const [issueResult, prResult, changedFiles, playwrightResult] = await Promise.all([
|
|
2371
|
+
issueNumber ? getIssue(issueNumber).catch(() => null) : Promise.resolve(null),
|
|
2372
|
+
getPrStatus().catch(() => null),
|
|
2373
|
+
getChangedFiles(baseBranch),
|
|
2374
|
+
isPlaywrightAvailable()
|
|
2375
|
+
]);
|
|
2376
|
+
issue = issueResult;
|
|
2377
|
+
pr = prResult;
|
|
2378
|
+
uiChanges = hasUIChanges(changedFiles);
|
|
2379
|
+
playwrightAvailable = playwrightResult;
|
|
2380
|
+
if (issue) {
|
|
2381
|
+
if (issue.labels.includes(workflowLabels.ready)) {
|
|
2382
|
+
workflowStatus = "ready";
|
|
2383
|
+
} else if (issue.labels.includes(workflowLabels.inProgress)) {
|
|
2384
|
+
workflowStatus = "in-progress";
|
|
2385
|
+
} else if (issue.labels.includes(workflowLabels.completed)) {
|
|
2386
|
+
workflowStatus = "completed";
|
|
2387
|
+
} else if (issue.labels.includes(workflowLabels.blocked)) {
|
|
2388
|
+
workflowStatus = "blocked";
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (pr && pr.state === "open") {
|
|
2392
|
+
try {
|
|
2393
|
+
const lastCommitTimestamp = await getLastCommitTimestamp();
|
|
2394
|
+
const reviewData = await getPrReviewData(pr.number);
|
|
2395
|
+
const { items } = summarizeReviewFeedback(reviewData, { afterTimestamp: lastCommitTimestamp });
|
|
2396
|
+
reviewFeedback = items;
|
|
2397
|
+
hasActionableFeedback = items.length > 0;
|
|
2398
|
+
} catch {
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
return {
|
|
2403
|
+
isGitRepo,
|
|
2404
|
+
isGhAuthenticated,
|
|
2405
|
+
isAIProviderAvailable,
|
|
2406
|
+
config,
|
|
2407
|
+
hasConfig,
|
|
2408
|
+
hasProgress,
|
|
2409
|
+
branch,
|
|
2410
|
+
branchInfo,
|
|
2411
|
+
isOnMain,
|
|
2412
|
+
hasUncommittedChanges: uncommitted,
|
|
2413
|
+
hasUnpushedCommits: unpushed,
|
|
2414
|
+
commits,
|
|
2415
|
+
baseBranch,
|
|
2416
|
+
issue,
|
|
2417
|
+
workflowStatus,
|
|
2418
|
+
pr,
|
|
2419
|
+
reviewFeedback,
|
|
2420
|
+
hasActionableFeedback,
|
|
2421
|
+
hasUIChanges: uiChanges,
|
|
2422
|
+
isPlaywrightAvailable: playwrightAvailable
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/tui/actions.ts
|
|
2427
|
+
function getAvailableActions(state) {
|
|
2428
|
+
const actions = [];
|
|
2429
|
+
if (!state.isGitRepo || !state.isGhAuthenticated) {
|
|
2430
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2431
|
+
return actions;
|
|
2432
|
+
}
|
|
2433
|
+
if (state.isOnMain) {
|
|
2434
|
+
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2435
|
+
actions.push({ id: "run-auto", label: "run next", shortcut: "r" });
|
|
2436
|
+
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2437
|
+
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2438
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2439
|
+
return actions;
|
|
2440
|
+
}
|
|
2441
|
+
if (state.hasUncommittedChanges) {
|
|
2442
|
+
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2443
|
+
}
|
|
2444
|
+
if (state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2445
|
+
actions.push({ id: "push", label: "Push", shortcut: "P" });
|
|
2446
|
+
}
|
|
2447
|
+
if (!state.pr && state.commits.length > 0) {
|
|
2448
|
+
actions.push({ id: "pr", label: "Create pr", shortcut: "C" });
|
|
2449
|
+
}
|
|
2450
|
+
if (state.issue) {
|
|
2451
|
+
actions.push({ id: "implement", label: "implement", shortcut: "i" });
|
|
2452
|
+
}
|
|
2453
|
+
if (state.pr && state.pr.state === "open") {
|
|
2454
|
+
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled) {
|
|
2455
|
+
actions.push({ id: "video", label: "video", shortcut: "v" });
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
if (state.pr && (state.pr.state === "merged" || state.pr.state === "closed")) {
|
|
2459
|
+
actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
|
|
2460
|
+
}
|
|
2461
|
+
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2462
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2463
|
+
return actions;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// src/tui/display.ts
|
|
2467
|
+
import chalk3 from "chalk";
|
|
2468
|
+
var stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2469
|
+
var visibleLen = (str) => stripAnsi(str).length;
|
|
2470
|
+
function termWidth() {
|
|
2471
|
+
return Math.min(process.stdout.columns || 80, 90);
|
|
2472
|
+
}
|
|
2473
|
+
function termHeight() {
|
|
2474
|
+
return process.stdout.rows || 24;
|
|
2475
|
+
}
|
|
2476
|
+
function truncate(text, max) {
|
|
2477
|
+
if (text.length <= max) return text;
|
|
2478
|
+
return text.slice(0, max - 1) + "\u2026";
|
|
2479
|
+
}
|
|
2480
|
+
function extractDescription(body, maxLen) {
|
|
2481
|
+
const lines = body.split("\n");
|
|
2482
|
+
for (const line of lines) {
|
|
2483
|
+
const trimmed = line.trim();
|
|
2484
|
+
if (!trimmed) continue;
|
|
2485
|
+
if (trimmed.startsWith("#")) continue;
|
|
2486
|
+
if (trimmed.startsWith("---")) continue;
|
|
2487
|
+
if (trimmed.startsWith("META:")) continue;
|
|
2488
|
+
if (trimmed.startsWith("**Type:**")) continue;
|
|
2489
|
+
const clean = trimmed.replace(/\*\*/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
2490
|
+
return truncate(clean, maxLen);
|
|
2491
|
+
}
|
|
2492
|
+
return "";
|
|
2493
|
+
}
|
|
2494
|
+
function topRow(title, w) {
|
|
2495
|
+
const label = ` ${title} `;
|
|
2496
|
+
const fill = w - 2 - label.length;
|
|
2497
|
+
return chalk3.dim("\u250C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2510");
|
|
2498
|
+
}
|
|
2499
|
+
function midRow(title, w) {
|
|
2500
|
+
const label = ` ${title} `;
|
|
2501
|
+
const fill = w - 2 - label.length;
|
|
2502
|
+
return chalk3.dim("\u251C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2524");
|
|
2503
|
+
}
|
|
2504
|
+
function divRow(w) {
|
|
2505
|
+
return chalk3.dim("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
2506
|
+
}
|
|
2507
|
+
function botRow(w) {
|
|
2508
|
+
return chalk3.dim("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
|
|
2509
|
+
}
|
|
2510
|
+
function row(text, w) {
|
|
2511
|
+
const inner = w - 4;
|
|
2512
|
+
const pad = Math.max(0, inner - visibleLen(text));
|
|
2513
|
+
return chalk3.dim("\u2502") + " " + text + " ".repeat(pad) + " " + chalk3.dim("\u2502");
|
|
2514
|
+
}
|
|
2515
|
+
function workflowBadge(status) {
|
|
2516
|
+
switch (status) {
|
|
2517
|
+
case "ready":
|
|
2518
|
+
return chalk3.bgGreen.black(" READY ");
|
|
2519
|
+
case "in-progress":
|
|
2520
|
+
return chalk3.bgYellow.black(" IN PROGRESS ");
|
|
2521
|
+
case "completed":
|
|
2522
|
+
return chalk3.bgBlue.white(" COMPLETED ");
|
|
2523
|
+
case "blocked":
|
|
2524
|
+
return chalk3.bgRed.white(" BLOCKED ");
|
|
2525
|
+
default:
|
|
2526
|
+
return "";
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
function prBadge(state, draft) {
|
|
2530
|
+
if (state === "merged") return chalk3.bgMagenta.white(" MERGED ");
|
|
2531
|
+
if (state === "closed") return chalk3.bgRed.white(" CLOSED ");
|
|
2532
|
+
return draft ? chalk3.bgYellow.black(" DRAFT ") : chalk3.bgGreen.black(" OPEN ");
|
|
2533
|
+
}
|
|
2534
|
+
function reviewBadge(decision) {
|
|
2535
|
+
if (!decision) return "";
|
|
2536
|
+
switch (decision) {
|
|
2537
|
+
case "APPROVED":
|
|
2538
|
+
return " " + chalk3.green("Approved");
|
|
2539
|
+
case "CHANGES_REQUESTED":
|
|
2540
|
+
return " " + chalk3.red("Changes requested");
|
|
2541
|
+
case "REVIEW_REQUIRED":
|
|
2542
|
+
return " " + chalk3.yellow("Review pending");
|
|
2543
|
+
default:
|
|
2544
|
+
return "";
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
var shortcutColors = [
|
|
2548
|
+
chalk3.cyan.bold,
|
|
2549
|
+
chalk3.green.bold,
|
|
2550
|
+
chalk3.yellow.bold,
|
|
2551
|
+
chalk3.magenta.bold,
|
|
2552
|
+
chalk3.blue.bold,
|
|
2553
|
+
chalk3.red.bold
|
|
2554
|
+
];
|
|
2555
|
+
function formatAction(a, color) {
|
|
2556
|
+
const key = a.shortcut;
|
|
2557
|
+
const idx = a.label.indexOf(key);
|
|
2558
|
+
if (idx >= 0) {
|
|
2559
|
+
const before = a.label.slice(0, idx);
|
|
2560
|
+
const after = a.label.slice(idx + key.length);
|
|
2561
|
+
return chalk3.dim(before) + color(key) + chalk3.dim(after);
|
|
2562
|
+
}
|
|
2563
|
+
return color(key) + " " + chalk3.dim(a.label);
|
|
2564
|
+
}
|
|
2565
|
+
function formatCommandBar(actions, w) {
|
|
2566
|
+
const parts = actions.map((a, i) => {
|
|
2567
|
+
const color = shortcutColors[i % shortcutColors.length];
|
|
2568
|
+
return formatAction(a, color);
|
|
2569
|
+
});
|
|
2570
|
+
const inner = w - 4;
|
|
2571
|
+
const lines = [];
|
|
2572
|
+
let cur = "";
|
|
2573
|
+
for (const part of parts) {
|
|
2574
|
+
const next = cur + (cur.length > 0 ? " " : "") + part;
|
|
2575
|
+
if (visibleLen(next) > inner) {
|
|
2576
|
+
lines.push(cur);
|
|
2577
|
+
cur = part;
|
|
2578
|
+
} else {
|
|
2579
|
+
cur = next;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
if (cur.length > 0) lines.push(cur);
|
|
2583
|
+
return lines;
|
|
2584
|
+
}
|
|
2585
|
+
function renderModal(message) {
|
|
2586
|
+
const w = termWidth();
|
|
2587
|
+
const h = termHeight();
|
|
2588
|
+
const modalW = Math.min(w - 4, Math.max(30, visibleLen(message) + 8));
|
|
2589
|
+
const padX = Math.max(0, Math.floor((w - modalW) / 2));
|
|
2590
|
+
const padY = Math.max(0, Math.floor((h - 5) / 2));
|
|
2591
|
+
const indent = " ".repeat(padX);
|
|
2592
|
+
const inner = modalW - 4;
|
|
2593
|
+
const textPad = Math.max(0, inner - visibleLen(message));
|
|
2594
|
+
const lines = [
|
|
2595
|
+
indent + chalk3.dim("\u250C" + "\u2500".repeat(modalW - 2) + "\u2510"),
|
|
2596
|
+
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2597
|
+
indent + chalk3.dim("\u2502") + " " + chalk3.bold(message) + " ".repeat(textPad) + " " + chalk3.dim("\u2502"),
|
|
2598
|
+
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2599
|
+
indent + chalk3.dim("\u2514" + "\u2500".repeat(modalW - 2) + "\u2518")
|
|
2600
|
+
];
|
|
2601
|
+
for (let i = 0; i < padY; i++) console.log();
|
|
2602
|
+
for (const line of lines) console.log(line);
|
|
2603
|
+
}
|
|
2604
|
+
function renderActionPanel(title, content) {
|
|
2605
|
+
const w = termWidth();
|
|
2606
|
+
console.log(topRow(title, w));
|
|
2607
|
+
for (const line of content) {
|
|
2608
|
+
console.log(row(line, w));
|
|
2609
|
+
}
|
|
2610
|
+
console.log(botRow(w));
|
|
2611
|
+
}
|
|
2612
|
+
function renderSettings(state, w) {
|
|
2613
|
+
const provider = getProviderDisplayName(state.config.ai.provider);
|
|
2614
|
+
const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
|
|
2615
|
+
const ghTag = state.isGhAuthenticated ? chalk3.green("authenticated") : chalk3.red("not authenticated");
|
|
2616
|
+
const videoTag = state.config.video.enabled ? chalk3.green("on") : chalk3.dim("off");
|
|
2617
|
+
console.log(row(chalk3.dim("Provider: ") + provTag, w));
|
|
2618
|
+
console.log(row(chalk3.dim("GitHub: ") + ghTag, w));
|
|
2619
|
+
console.log(row(chalk3.dim("Video: ") + videoTag, w));
|
|
2620
|
+
}
|
|
2621
|
+
function renderDashboard(state, actions, hint) {
|
|
2622
|
+
const w = termWidth();
|
|
2623
|
+
const descMax = w - 8;
|
|
2624
|
+
const version2 = getVersion();
|
|
2625
|
+
const titleLabel = `gent v${version2}`;
|
|
2626
|
+
console.log(topRow(titleLabel, w));
|
|
2627
|
+
if (!state.isGitRepo) {
|
|
2628
|
+
console.log(row(chalk3.red("Not a git repository"), w));
|
|
2629
|
+
console.log(row(chalk3.dim("Run gent init in a git repo to get started"), w));
|
|
2630
|
+
console.log(botRow(w));
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
if (!state.isGhAuthenticated) {
|
|
2634
|
+
console.log(row(chalk3.red("GitHub CLI not authenticated"), w));
|
|
2635
|
+
console.log(row(chalk3.dim("Run: gh auth login"), w));
|
|
2636
|
+
console.log(botRow(w));
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
if (state.isOnMain) {
|
|
2640
|
+
console.log(row(chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"), w));
|
|
2641
|
+
if (state.hasUncommittedChanges) {
|
|
2642
|
+
console.log(row(chalk3.yellow("\u25CF uncommitted changes"), w));
|
|
2643
|
+
}
|
|
2644
|
+
console.log(midRow("Settings", w));
|
|
2645
|
+
renderSettings(state, w);
|
|
2646
|
+
if (hint) {
|
|
2647
|
+
console.log(midRow("Hint", w));
|
|
2648
|
+
console.log(row(chalk3.yellow(hint), w));
|
|
2649
|
+
}
|
|
2650
|
+
console.log(divRow(w));
|
|
2651
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2652
|
+
console.log(row(line, w));
|
|
2653
|
+
}
|
|
2654
|
+
console.log(botRow(w));
|
|
2655
|
+
console.log();
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
const section = (title) => {
|
|
2659
|
+
console.log(midRow(title, w));
|
|
2660
|
+
};
|
|
2661
|
+
section("Ticket");
|
|
2662
|
+
if (state.issue) {
|
|
2663
|
+
console.log(row(
|
|
2664
|
+
chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
|
|
2665
|
+
w
|
|
2666
|
+
));
|
|
2667
|
+
const desc = extractDescription(state.issue.body, descMax);
|
|
2668
|
+
if (desc) console.log(row(chalk3.dim(desc), w));
|
|
2669
|
+
const tags = [];
|
|
2670
|
+
if (state.workflowStatus !== "none") tags.push(workflowBadge(state.workflowStatus));
|
|
2671
|
+
for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
|
|
2672
|
+
const l = state.issue.labels.find((x) => x.startsWith(prefix));
|
|
2673
|
+
if (l) tags.push(chalk3.dim(l));
|
|
2674
|
+
}
|
|
2675
|
+
if (tags.length) console.log(row(tags.join(" "), w));
|
|
2676
|
+
} else {
|
|
2677
|
+
console.log(row(chalk3.dim("No linked issue"), w));
|
|
2678
|
+
}
|
|
2679
|
+
section("Branch");
|
|
2680
|
+
console.log(row(chalk3.magenta(state.branch), w));
|
|
2681
|
+
const bits = [];
|
|
2682
|
+
if (state.commits.length > 0) bits.push(chalk3.dim(`${state.commits.length} ahead`));
|
|
2683
|
+
if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
|
|
2684
|
+
if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
|
|
2685
|
+
if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2686
|
+
bits.push(chalk3.green("\u25CF synced"));
|
|
2687
|
+
}
|
|
2688
|
+
if (bits.length) console.log(row(bits.join(chalk3.dim(" \xB7 ")), w));
|
|
2689
|
+
section("Pull Request");
|
|
2690
|
+
if (state.pr) {
|
|
2691
|
+
const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
|
|
2692
|
+
console.log(row(
|
|
2693
|
+
chalk3.cyan(`#${state.pr.number}`) + titleText,
|
|
2694
|
+
w
|
|
2695
|
+
));
|
|
2696
|
+
console.log(row(
|
|
2697
|
+
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2698
|
+
w
|
|
2699
|
+
));
|
|
2700
|
+
if (state.hasActionableFeedback) {
|
|
2701
|
+
const n = state.reviewFeedback.length;
|
|
2702
|
+
console.log(row(chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`), w));
|
|
2703
|
+
}
|
|
2704
|
+
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
2705
|
+
console.log(row(chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"), w));
|
|
2706
|
+
}
|
|
2707
|
+
console.log(row(chalk3.dim(state.pr.url), w));
|
|
2708
|
+
} else {
|
|
2709
|
+
console.log(row(chalk3.dim("No PR created"), w));
|
|
2710
|
+
}
|
|
2711
|
+
section("Commits");
|
|
2712
|
+
if (state.commits.length > 0) {
|
|
2713
|
+
const max = 6;
|
|
2714
|
+
for (const c of state.commits.slice(0, max)) {
|
|
2715
|
+
console.log(row(c, w));
|
|
2716
|
+
}
|
|
2717
|
+
if (state.commits.length > max) {
|
|
2718
|
+
console.log(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
|
|
2719
|
+
}
|
|
2720
|
+
} else {
|
|
2721
|
+
console.log(row(chalk3.dim("No commits"), w));
|
|
2722
|
+
}
|
|
2723
|
+
section("Settings");
|
|
2724
|
+
renderSettings(state, w);
|
|
2725
|
+
if (hint) {
|
|
2726
|
+
section("Hint");
|
|
2727
|
+
console.log(row(chalk3.yellow(hint), w));
|
|
2728
|
+
}
|
|
2729
|
+
console.log(divRow(w));
|
|
2730
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2731
|
+
console.log(row(line, w));
|
|
2732
|
+
}
|
|
2733
|
+
console.log(botRow(w));
|
|
2734
|
+
}
|
|
2735
|
+
function clearScreen() {
|
|
2736
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/commands/tui.ts
|
|
2740
|
+
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2741
|
+
async function confirm(message) {
|
|
2742
|
+
const { ok } = await inquirer6.prompt([
|
|
2743
|
+
{
|
|
2744
|
+
type: "confirm",
|
|
2745
|
+
name: "ok",
|
|
2746
|
+
message,
|
|
2747
|
+
default: true
|
|
2748
|
+
}
|
|
2749
|
+
]);
|
|
2750
|
+
return ok;
|
|
2751
|
+
}
|
|
2752
|
+
async function loadState() {
|
|
2753
|
+
clearScreen();
|
|
2754
|
+
renderModal("Loading workflow state...");
|
|
2755
|
+
const state = await aggregateState();
|
|
2756
|
+
return state;
|
|
2757
|
+
}
|
|
2758
|
+
async function waitForKey(validKeys) {
|
|
2759
|
+
return new Promise((resolve) => {
|
|
2760
|
+
const { stdin } = process;
|
|
2761
|
+
const wasRaw = stdin.isRaw;
|
|
2762
|
+
stdin.setRawMode(true);
|
|
2763
|
+
stdin.resume();
|
|
2764
|
+
stdin.setEncoding("utf8");
|
|
2765
|
+
const onData = (key) => {
|
|
2766
|
+
if (key === "") {
|
|
2767
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2768
|
+
stdin.pause();
|
|
2769
|
+
stdin.removeListener("data", onData);
|
|
2770
|
+
resolve("q");
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
if (validKeys.includes(key)) {
|
|
2774
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2775
|
+
stdin.pause();
|
|
2776
|
+
stdin.removeListener("data", onData);
|
|
2777
|
+
resolve(key);
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
stdin.on("data", onData);
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
async function executeAction(actionId, state, providerSetter) {
|
|
2784
|
+
switch (actionId) {
|
|
2785
|
+
case "quit":
|
|
2786
|
+
return false;
|
|
2787
|
+
case "list":
|
|
2788
|
+
clearScreen();
|
|
2789
|
+
await listCommand({ status: "ready", limit: 20 });
|
|
2790
|
+
await promptContinue();
|
|
2791
|
+
return true;
|
|
2792
|
+
case "run-auto": {
|
|
2793
|
+
clearScreen();
|
|
2794
|
+
if (!await confirm("Start AI agent to implement next ticket?")) return true;
|
|
2795
|
+
try {
|
|
2796
|
+
await runCommand(void 0, { auto: true });
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
logger.error(`Run failed: ${error}`);
|
|
2799
|
+
await promptContinue();
|
|
2800
|
+
}
|
|
2801
|
+
return false;
|
|
2802
|
+
}
|
|
2803
|
+
case "create": {
|
|
2804
|
+
clearScreen();
|
|
2805
|
+
const { description } = await inquirer6.prompt([
|
|
2806
|
+
{
|
|
2807
|
+
type: "input",
|
|
2808
|
+
name: "description",
|
|
2809
|
+
message: "Describe the ticket (empty to cancel):"
|
|
2810
|
+
}
|
|
2811
|
+
]);
|
|
2812
|
+
if (!description.trim()) {
|
|
2813
|
+
logger.info("Cancelled");
|
|
2814
|
+
return true;
|
|
2815
|
+
}
|
|
2816
|
+
try {
|
|
2817
|
+
await createCommand(description, {});
|
|
2818
|
+
} catch (error) {
|
|
2819
|
+
logger.error(`Create failed: ${error}`);
|
|
2820
|
+
}
|
|
2821
|
+
await promptContinue();
|
|
2822
|
+
return true;
|
|
2823
|
+
}
|
|
2824
|
+
case "commit":
|
|
2825
|
+
clearScreen();
|
|
2826
|
+
await handleCommit(state);
|
|
2827
|
+
await promptContinue();
|
|
2828
|
+
return true;
|
|
2829
|
+
case "push":
|
|
2830
|
+
clearScreen();
|
|
2831
|
+
await handlePush();
|
|
2832
|
+
await promptContinue();
|
|
2833
|
+
return true;
|
|
2834
|
+
case "pr": {
|
|
2835
|
+
clearScreen();
|
|
2836
|
+
if (!await confirm("Create a pull request?")) return true;
|
|
2837
|
+
await prCommand({});
|
|
2838
|
+
await promptContinue();
|
|
2839
|
+
return true;
|
|
2840
|
+
}
|
|
2841
|
+
case "implement": {
|
|
2842
|
+
clearScreen();
|
|
2843
|
+
const hasCommits = state.commits.length > 0;
|
|
2844
|
+
const hasFeedback = state.hasActionableFeedback;
|
|
2845
|
+
let msg;
|
|
2846
|
+
if (hasFeedback && hasCommits) {
|
|
2847
|
+
msg = "Start AI agent to address review feedback?";
|
|
2848
|
+
} else if (hasCommits) {
|
|
2849
|
+
msg = "Start AI agent to continue implementation from existing commits?";
|
|
2850
|
+
} else {
|
|
2851
|
+
msg = "Start AI agent to implement this ticket from scratch?";
|
|
2852
|
+
}
|
|
2853
|
+
if (!await confirm(msg)) return true;
|
|
2854
|
+
await handleImplement(state);
|
|
2855
|
+
return false;
|
|
2856
|
+
}
|
|
2857
|
+
case "video": {
|
|
2858
|
+
clearScreen();
|
|
2859
|
+
if (!await confirm("Record video of UI changes?")) return true;
|
|
2860
|
+
await handleVideoCapture(state);
|
|
2861
|
+
await promptContinue();
|
|
2862
|
+
return true;
|
|
2863
|
+
}
|
|
2864
|
+
case "switch-provider":
|
|
2865
|
+
clearScreen();
|
|
2866
|
+
await handleSwitchProvider(state, providerSetter);
|
|
2867
|
+
return true;
|
|
2868
|
+
case "checkout-main": {
|
|
2869
|
+
clearScreen();
|
|
2870
|
+
if (!await confirm("Switch to main branch?")) return true;
|
|
2871
|
+
await handleCheckoutMain();
|
|
2872
|
+
return true;
|
|
2873
|
+
}
|
|
2874
|
+
default:
|
|
2875
|
+
return true;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
async function handleCommit(state) {
|
|
2879
|
+
try {
|
|
2880
|
+
const { stdout: status } = await execa4("git", ["status", "--short"]);
|
|
2881
|
+
if (!status.trim()) {
|
|
2882
|
+
logger.info("No changes to commit");
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
logger.info("Changes:");
|
|
2886
|
+
console.log(status);
|
|
2887
|
+
console.log();
|
|
2888
|
+
await execa4("git", ["add", "-A"]);
|
|
2889
|
+
const { stdout: diffStat } = await execa4("git", ["diff", "--cached", "--stat"]);
|
|
2890
|
+
const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
|
|
2891
|
+
const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
|
|
2892
|
+
const issueNumber = state.issue?.number ?? null;
|
|
2893
|
+
const issueTitle = state.issue?.title ?? null;
|
|
2894
|
+
const provider = state.config.ai.provider;
|
|
2895
|
+
const providerName = getProviderDisplayName(provider);
|
|
2896
|
+
logger.info(`Generating commit message with ${providerName}...`);
|
|
2897
|
+
const message = await generateCommitMessage(diffContent, issueNumber, issueTitle, state);
|
|
2898
|
+
if (message === CANCEL) {
|
|
2899
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
2900
|
+
logger.info("Cancelled");
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
console.log();
|
|
2904
|
+
logger.info(`Message: ${message}`);
|
|
2905
|
+
console.log();
|
|
2906
|
+
if (!await confirm("Commit with this message?")) {
|
|
2907
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
2908
|
+
logger.info("Commit cancelled");
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const providerEmail = getProviderEmail(provider);
|
|
2912
|
+
const fullMessage = `${message}
|
|
2913
|
+
|
|
2914
|
+
Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
2915
|
+
const spinner = createSpinner("Committing...");
|
|
2916
|
+
spinner.start();
|
|
2917
|
+
await execa4("git", ["commit", "-m", fullMessage]);
|
|
2918
|
+
spinner.succeed("Changes committed");
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
logger.error(`Commit failed: ${error}`);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
|
|
2924
|
+
try {
|
|
2925
|
+
const prompt = buildCommitMessagePrompt(diffContent, issueNumber, issueTitle);
|
|
2926
|
+
const result = await invokeAI({ prompt, streamOutput: true }, state.config);
|
|
2927
|
+
let message = result.output.trim().split("\n")[0].trim();
|
|
2928
|
+
for (const q of ['"', "'", "`"]) {
|
|
2929
|
+
if (message.startsWith(q) && message.endsWith(q)) {
|
|
2930
|
+
message = message.slice(1, -1);
|
|
2931
|
+
break;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
message = message.replace(/^```\w*\s*/, "").replace(/\s*```$/, "");
|
|
2935
|
+
return message;
|
|
2936
|
+
} catch {
|
|
2937
|
+
logger.warning("AI commit message generation failed");
|
|
2938
|
+
console.log();
|
|
2939
|
+
const { message } = await inquirer6.prompt([
|
|
2940
|
+
{
|
|
2941
|
+
type: "input",
|
|
2942
|
+
name: "message",
|
|
2943
|
+
message: "Commit message (empty to cancel):"
|
|
2944
|
+
}
|
|
2945
|
+
]);
|
|
2946
|
+
return message.trim() || CANCEL;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
async function handleImplement(state) {
|
|
2950
|
+
if (!state.issue) {
|
|
2951
|
+
logger.error("No linked issue found");
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
const agentInstructions = loadAgentInstructions();
|
|
2955
|
+
const progressContent = readProgress(state.config);
|
|
2956
|
+
const contextParts = [];
|
|
2957
|
+
if (state.commits.length > 0) {
|
|
2958
|
+
contextParts.push(
|
|
2959
|
+
`## Current Progress
|
|
2960
|
+
There are ${state.commits.length} existing commit(s) on this branch:
|
|
2961
|
+
${state.commits.map((c) => `- ${c}`).join("\n")}
|
|
2962
|
+
|
|
2963
|
+
Continue the implementation from where it left off. Review the existing work and complete any remaining tasks.`
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
if (state.hasActionableFeedback && state.reviewFeedback.length > 0) {
|
|
2967
|
+
const feedbackLines = state.reviewFeedback.map((f) => `- [${f.source}] ${f.body.slice(0, 200)}`).join("\n");
|
|
2968
|
+
contextParts.push(`## Review Feedback
|
|
2969
|
+
${feedbackLines}`);
|
|
2970
|
+
}
|
|
2971
|
+
const extraContext = contextParts.length > 0 ? contextParts.join("\n\n") : null;
|
|
2972
|
+
const prompt = buildImplementationPrompt(
|
|
2973
|
+
state.issue,
|
|
2974
|
+
agentInstructions,
|
|
2975
|
+
progressContent,
|
|
2976
|
+
state.config,
|
|
2977
|
+
extraContext
|
|
2978
|
+
);
|
|
2979
|
+
const providerName = getProviderDisplayName(state.config.ai.provider);
|
|
2980
|
+
clearScreen();
|
|
2981
|
+
renderActionPanel(`${providerName} Session`, [
|
|
2982
|
+
`Implementing: #${state.issue.number} ${state.issue.title}`,
|
|
2983
|
+
state.commits.length > 0 ? `Continuing from ${state.commits.length} existing commit(s)` : "Starting fresh implementation",
|
|
2984
|
+
...state.hasActionableFeedback ? [`Includes ${state.reviewFeedback.length} review feedback item(s)`] : []
|
|
2985
|
+
]);
|
|
2986
|
+
console.log();
|
|
2987
|
+
try {
|
|
2988
|
+
await invokeAIInteractive(prompt, state.config);
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
logger.error(`${providerName} session failed: ${error}`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
async function handlePush() {
|
|
2994
|
+
try {
|
|
2995
|
+
const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
|
|
2996
|
+
if (!await confirm(`Push ${branch.trim()} to remote?`)) return;
|
|
2997
|
+
const spinner = createSpinner("Pushing...");
|
|
2998
|
+
spinner.start();
|
|
2999
|
+
await execa4("git", ["push", "-u", "origin", branch.trim()]);
|
|
3000
|
+
spinner.succeed("Pushed to remote");
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
logger.error(`Push failed: ${error}`);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3006
|
+
async function handleSwitchProvider(state, setProvider) {
|
|
3007
|
+
const current = state.config.ai.provider;
|
|
3008
|
+
const { provider } = await inquirer6.prompt([
|
|
3009
|
+
{
|
|
3010
|
+
type: "list",
|
|
3011
|
+
name: "provider",
|
|
3012
|
+
message: "Select AI provider:",
|
|
3013
|
+
choices: PROVIDERS.map((p) => ({
|
|
3014
|
+
name: p.charAt(0).toUpperCase() + p.slice(1) + (p === current ? " (current)" : ""),
|
|
3015
|
+
value: p
|
|
3016
|
+
})),
|
|
3017
|
+
default: current
|
|
3018
|
+
}
|
|
3019
|
+
]);
|
|
3020
|
+
if (provider === current) return;
|
|
3021
|
+
setProvider(provider);
|
|
3022
|
+
logger.success(`Provider switched to ${provider} (session only)`);
|
|
3023
|
+
}
|
|
3024
|
+
async function handleCheckoutMain() {
|
|
3025
|
+
try {
|
|
3026
|
+
const spinner = createSpinner("Switching to main...");
|
|
3027
|
+
spinner.start();
|
|
3028
|
+
await execa4("git", ["checkout", "main"]);
|
|
3029
|
+
await execa4("git", ["pull"]);
|
|
3030
|
+
spinner.succeed("Switched to main");
|
|
3031
|
+
} catch (error) {
|
|
3032
|
+
logger.error(`Checkout failed: ${error}`);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
async function handleVideoCapture(state) {
|
|
3036
|
+
if (!state.issue) {
|
|
3037
|
+
logger.error("No linked issue found");
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
const providerName = getProviderDisplayName(state.config.ai.provider);
|
|
3041
|
+
clearScreen();
|
|
3042
|
+
renderActionPanel("Video Capture", [
|
|
3043
|
+
`Recording: #${state.issue.number} ${state.issue.title}`,
|
|
3044
|
+
`Provider: ${providerName}`
|
|
3045
|
+
]);
|
|
3046
|
+
console.log();
|
|
3047
|
+
try {
|
|
3048
|
+
const agentInstructions = loadAgentInstructions();
|
|
3049
|
+
const videoPrompt = buildVideoPrompt(
|
|
3050
|
+
state.issue.number,
|
|
3051
|
+
state.issue.title,
|
|
3052
|
+
state.config.video,
|
|
3053
|
+
agentInstructions
|
|
3054
|
+
);
|
|
3055
|
+
await invokeAIInteractive(videoPrompt, state.config);
|
|
3056
|
+
logger.success("Video capture completed");
|
|
3057
|
+
} catch (error) {
|
|
3058
|
+
logger.error(`Video capture failed: ${error}`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
async function promptContinue() {
|
|
3062
|
+
console.log();
|
|
3063
|
+
await inquirer6.prompt([
|
|
3064
|
+
{
|
|
3065
|
+
type: "input",
|
|
3066
|
+
name: "continue",
|
|
3067
|
+
message: "Press Enter to continue..."
|
|
3068
|
+
}
|
|
3069
|
+
]);
|
|
3070
|
+
}
|
|
3071
|
+
async function tuiCommand() {
|
|
3072
|
+
let running = true;
|
|
3073
|
+
let providerOverride = null;
|
|
3074
|
+
const setProvider = (p) => {
|
|
3075
|
+
providerOverride = p;
|
|
3076
|
+
};
|
|
3077
|
+
while (running) {
|
|
3078
|
+
const state = await loadState();
|
|
3079
|
+
if (providerOverride) {
|
|
3080
|
+
state.config.ai.provider = providerOverride;
|
|
3081
|
+
}
|
|
3082
|
+
clearScreen();
|
|
3083
|
+
const actions = getAvailableActions(state);
|
|
3084
|
+
let hint;
|
|
3085
|
+
if (state.isOnMain) {
|
|
3086
|
+
hint = "Select an action to get started";
|
|
3087
|
+
} else if (state.hasUncommittedChanges && !state.pr) {
|
|
3088
|
+
hint = "Commit your changes before creating a PR";
|
|
3089
|
+
} else if (state.hasActionableFeedback) {
|
|
3090
|
+
hint = "Review feedback needs attention";
|
|
3091
|
+
}
|
|
3092
|
+
renderDashboard(state, actions, hint);
|
|
3093
|
+
const validKeys = actions.map((a) => a.shortcut);
|
|
3094
|
+
const key = await waitForKey(validKeys);
|
|
3095
|
+
const action = actions.find((a) => a.shortcut === key);
|
|
3096
|
+
if (action) {
|
|
3097
|
+
running = await executeAction(action.id, state, setProvider);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
2248
3102
|
// src/index.ts
|
|
2249
3103
|
var version = getVersion();
|
|
2250
3104
|
function startVersionCheck() {
|
|
@@ -2295,5 +3149,11 @@ program.command("fix").description("Apply PR review feedback using AI").option("
|
|
|
2295
3149
|
program.command("status").description("Show current workflow status").action(async () => {
|
|
2296
3150
|
await statusCommand();
|
|
2297
3151
|
});
|
|
3152
|
+
program.command("ui").description("Launch interactive dashboard").action(async () => {
|
|
3153
|
+
await tuiCommand();
|
|
3154
|
+
});
|
|
3155
|
+
program.action(async () => {
|
|
3156
|
+
await tuiCommand();
|
|
3157
|
+
});
|
|
2298
3158
|
program.parse();
|
|
2299
3159
|
//# sourceMappingURL=index.js.map
|