@rotorsoft/gent 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-OIHSXI5X.js +43 -0
- package/dist/chunk-OIHSXI5X.js.map +1 -0
- package/dist/{chunk-75KVN7ZC.js → chunk-P5MZOU4B.js} +211 -308
- package/dist/chunk-P5MZOU4B.js.map +1 -0
- package/dist/chunk-YS7HWP4W.js +283 -0
- package/dist/chunk-YS7HWP4W.js.map +1 -0
- package/dist/github-remote-G6UKRDUB.js +9 -0
- package/dist/index.js +187 -259
- package/dist/index.js.map +1 -1
- package/dist/setup-labels-FZEN5TKM.js +9 -0
- package/dist/setup-labels-FZEN5TKM.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-75KVN7ZC.js.map +0 -1
- package/dist/setup-labels-STWAFV2E.js +0 -8
- /package/dist/{setup-labels-STWAFV2E.js.map → github-remote-G6UKRDUB.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,46 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
githubRemoteCommand
|
|
4
|
+
} from "./chunk-OIHSXI5X.js";
|
|
5
|
+
import {
|
|
6
|
+
aiSpinnerText,
|
|
7
|
+
buildIssueLabels,
|
|
8
|
+
createSpinner,
|
|
9
|
+
extractTypeFromLabels,
|
|
10
|
+
getWorkflowLabels,
|
|
11
|
+
setupLabelsCommand,
|
|
12
|
+
sortByPriority,
|
|
13
|
+
withSpinner
|
|
14
|
+
} from "./chunk-YS7HWP4W.js";
|
|
2
15
|
import {
|
|
3
16
|
addIssueComment,
|
|
4
17
|
addPrComment,
|
|
5
|
-
aiSpinnerText,
|
|
6
18
|
assignIssue,
|
|
7
|
-
|
|
19
|
+
branchExists,
|
|
8
20
|
checkAIProvider,
|
|
9
21
|
checkClaudeCli,
|
|
10
22
|
checkGeminiCli,
|
|
11
23
|
checkGhAuth,
|
|
12
24
|
checkGitRepo,
|
|
13
|
-
checkInitialized,
|
|
14
25
|
checkLabelsExist,
|
|
26
|
+
checkoutBranch,
|
|
15
27
|
colors,
|
|
16
28
|
configExists,
|
|
29
|
+
createBranch,
|
|
17
30
|
createIssue,
|
|
18
31
|
createPullRequest,
|
|
19
|
-
|
|
20
|
-
extractTypeFromLabels,
|
|
32
|
+
fetchAndCheckout,
|
|
21
33
|
generateDefaultConfig,
|
|
34
|
+
getAuthorInitials,
|
|
35
|
+
getCommitsSinceBase,
|
|
22
36
|
getConfigPath,
|
|
37
|
+
getCurrentBranch,
|
|
38
|
+
getCurrentCommitSha,
|
|
23
39
|
getCurrentUser,
|
|
40
|
+
getDefaultBranch,
|
|
41
|
+
getDiffSummary,
|
|
24
42
|
getIssue,
|
|
43
|
+
getLastCommitTimestamp,
|
|
25
44
|
getPrForBranch,
|
|
26
45
|
getPrReviewData,
|
|
27
46
|
getPrStatus,
|
|
28
|
-
|
|
47
|
+
getRepoInfo,
|
|
48
|
+
getUnpushedCommits,
|
|
49
|
+
hasNewCommits,
|
|
50
|
+
hasUncommittedChanges,
|
|
51
|
+
isOnMainBranch,
|
|
29
52
|
isValidIssueNumber,
|
|
30
53
|
listIssues,
|
|
54
|
+
listLocalBranches,
|
|
31
55
|
listOpenPrs,
|
|
32
56
|
loadAgentInstructions,
|
|
33
57
|
loadConfig,
|
|
34
58
|
logger,
|
|
59
|
+
pushBranch,
|
|
60
|
+
remoteBranchExists,
|
|
35
61
|
replyToReviewComment,
|
|
36
62
|
resolveProvider,
|
|
37
63
|
sanitizeSlug,
|
|
38
64
|
setRuntimeProvider,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
updateIssueLabels,
|
|
42
|
-
withSpinner
|
|
43
|
-
} from "./chunk-75KVN7ZC.js";
|
|
65
|
+
updateIssueLabels
|
|
66
|
+
} from "./chunk-P5MZOU4B.js";
|
|
44
67
|
|
|
45
68
|
// src/index.ts
|
|
46
69
|
import { Command } from "commander";
|
|
@@ -48,6 +71,7 @@ import { Command } from "commander";
|
|
|
48
71
|
// src/commands/init.ts
|
|
49
72
|
import { writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
50
73
|
import { join as join2 } from "path";
|
|
74
|
+
import { execa } from "execa";
|
|
51
75
|
import inquirer from "inquirer";
|
|
52
76
|
|
|
53
77
|
// src/lib/progress.ts
|
|
@@ -86,6 +110,47 @@ Each entry documents: date, feature, decisions, files changed, tests, and concer
|
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
// src/commands/init.ts
|
|
113
|
+
var DEFAULT_GITIGNORE = `# Dependencies
|
|
114
|
+
node_modules/
|
|
115
|
+
|
|
116
|
+
# Build output
|
|
117
|
+
dist/
|
|
118
|
+
|
|
119
|
+
# Test coverage
|
|
120
|
+
coverage/
|
|
121
|
+
|
|
122
|
+
# IDE
|
|
123
|
+
.idea/
|
|
124
|
+
.vscode/
|
|
125
|
+
*.swp
|
|
126
|
+
*.swo
|
|
127
|
+
|
|
128
|
+
# OS
|
|
129
|
+
.DS_Store
|
|
130
|
+
Thumbs.db
|
|
131
|
+
|
|
132
|
+
# Logs
|
|
133
|
+
*.log
|
|
134
|
+
npm-debug.log*
|
|
135
|
+
|
|
136
|
+
# Environment
|
|
137
|
+
.env
|
|
138
|
+
.env.local
|
|
139
|
+
.env.*.local
|
|
140
|
+
|
|
141
|
+
# Temporary files
|
|
142
|
+
*.tmp
|
|
143
|
+
*.temp
|
|
144
|
+
.cache/
|
|
145
|
+
`;
|
|
146
|
+
async function hasCommits() {
|
|
147
|
+
try {
|
|
148
|
+
await execa("git", ["rev-parse", "HEAD"]);
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
89
154
|
var DEFAULT_AGENT_MD = `# AI Agent Instructions
|
|
90
155
|
|
|
91
156
|
This file contains instructions for the AI when working on this repository.
|
|
@@ -142,12 +207,12 @@ All AI commits should include the Co-Authored-By trailer as specified in the tas
|
|
|
142
207
|
- [E.g., "Always use async/await over callbacks"]
|
|
143
208
|
`;
|
|
144
209
|
async function initCommand(options) {
|
|
145
|
-
logger.bold("Initializing gent workflow...");
|
|
210
|
+
logger.bold("Initializing gent workflow (optional \u2014 gent works with sensible defaults)...");
|
|
146
211
|
logger.newline();
|
|
147
212
|
const isGitRepo = await checkGitRepo();
|
|
148
213
|
if (!isGitRepo) {
|
|
149
214
|
logger.error("Not a git repository. Please run 'git init' first.");
|
|
150
|
-
|
|
215
|
+
return;
|
|
151
216
|
}
|
|
152
217
|
const cwd = process.cwd();
|
|
153
218
|
if (configExists(cwd) && !options.force) {
|
|
@@ -173,6 +238,13 @@ async function initCommand(options) {
|
|
|
173
238
|
default: "claude"
|
|
174
239
|
}
|
|
175
240
|
]);
|
|
241
|
+
const gitignorePath = join2(cwd, ".gitignore");
|
|
242
|
+
if (!existsSync2(gitignorePath)) {
|
|
243
|
+
writeFileSync2(gitignorePath, DEFAULT_GITIGNORE, "utf-8");
|
|
244
|
+
logger.success(`Created ${colors.file(".gitignore")}`);
|
|
245
|
+
} else {
|
|
246
|
+
logger.info(`${colors.file(".gitignore")} already exists, skipping`);
|
|
247
|
+
}
|
|
176
248
|
const configPath = getConfigPath(cwd);
|
|
177
249
|
writeFileSync2(configPath, generateDefaultConfig(provider), "utf-8");
|
|
178
250
|
logger.success(`Created ${colors.file(".gent.yml")}`);
|
|
@@ -186,26 +258,72 @@ async function initCommand(options) {
|
|
|
186
258
|
const config = loadConfig(cwd);
|
|
187
259
|
initializeProgress(config, cwd);
|
|
188
260
|
logger.success(`Created ${colors.file(config.progress.file)}`);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"
|
|
192
|
-
|
|
261
|
+
if (!await hasCommits()) {
|
|
262
|
+
logger.newline();
|
|
263
|
+
logger.info("No commits found. Creating initial commit with gent config...");
|
|
264
|
+
await execa("git", ["add", ".gitignore", ".gent.yml", "AGENT.md", config.progress.file]);
|
|
265
|
+
await execa("git", ["commit", "-m", "chore: initialize gent workflow"]);
|
|
266
|
+
logger.success("Created initial commit");
|
|
267
|
+
}
|
|
268
|
+
const repoInfo = await getRepoInfo();
|
|
269
|
+
if (!repoInfo) {
|
|
270
|
+
logger.newline();
|
|
271
|
+
logger.box(
|
|
272
|
+
"Setup Complete",
|
|
273
|
+
`Next steps:
|
|
274
|
+
1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
|
|
275
|
+
2. Edit ${colors.file(".gent.yml")} to customize settings
|
|
276
|
+
3. Create a GitHub remote: ${colors.command("gent github-remote")}
|
|
277
|
+
4. Run ${colors.command("gent setup-labels")} to create GitHub labels`
|
|
278
|
+
);
|
|
279
|
+
const { createRemote } = await inquirer.prompt([
|
|
280
|
+
{
|
|
281
|
+
type: "confirm",
|
|
282
|
+
name: "createRemote",
|
|
283
|
+
message: "No GitHub remote found. Would you like to create one now?",
|
|
284
|
+
default: true
|
|
285
|
+
}
|
|
286
|
+
]);
|
|
287
|
+
if (createRemote) {
|
|
288
|
+
const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-G6UKRDUB.js");
|
|
289
|
+
const success = await githubRemoteCommand2();
|
|
290
|
+
if (success) {
|
|
291
|
+
const { setupLabels } = await inquirer.prompt([
|
|
292
|
+
{
|
|
293
|
+
type: "confirm",
|
|
294
|
+
name: "setupLabels",
|
|
295
|
+
message: "Would you like to setup GitHub labels now?",
|
|
296
|
+
default: true
|
|
297
|
+
}
|
|
298
|
+
]);
|
|
299
|
+
if (setupLabels) {
|
|
300
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-FZEN5TKM.js");
|
|
301
|
+
await setupLabelsCommand2();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
logger.newline();
|
|
307
|
+
logger.box(
|
|
308
|
+
"Setup Complete",
|
|
309
|
+
`Next steps:
|
|
193
310
|
1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
|
|
194
311
|
2. Edit ${colors.file(".gent.yml")} to customize settings
|
|
195
312
|
3. Run ${colors.command("gent setup-labels")} to create GitHub labels
|
|
196
313
|
4. Run ${colors.command("gent create <description>")} to create your first ticket`
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
314
|
+
);
|
|
315
|
+
const { setupLabels } = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: "confirm",
|
|
318
|
+
name: "setupLabels",
|
|
319
|
+
message: "Would you like to setup GitHub labels now?",
|
|
320
|
+
default: true
|
|
321
|
+
}
|
|
322
|
+
]);
|
|
323
|
+
if (setupLabels) {
|
|
324
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-FZEN5TKM.js");
|
|
325
|
+
await setupLabelsCommand2();
|
|
204
326
|
}
|
|
205
|
-
]);
|
|
206
|
-
if (setupLabels) {
|
|
207
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-STWAFV2E.js");
|
|
208
|
-
await setupLabelsCommand2();
|
|
209
327
|
}
|
|
210
328
|
}
|
|
211
329
|
|
|
@@ -215,7 +333,7 @@ import chalk from "chalk";
|
|
|
215
333
|
|
|
216
334
|
// src/lib/ai-provider.ts
|
|
217
335
|
import { spawn } from "child_process";
|
|
218
|
-
import { execa } from "execa";
|
|
336
|
+
import { execa as execa2 } from "execa";
|
|
219
337
|
async function getOtherAvailableProviders(currentProvider) {
|
|
220
338
|
const allProviders = ["claude", "gemini", "codex"];
|
|
221
339
|
const others = allProviders.filter((p) => p !== currentProvider);
|
|
@@ -266,20 +384,20 @@ async function invokeAIInteractive(prompt, config, providerOverride) {
|
|
|
266
384
|
case "claude": {
|
|
267
385
|
const args = ["--permission-mode", config.claude.permission_mode, prompt];
|
|
268
386
|
return {
|
|
269
|
-
result:
|
|
387
|
+
result: execa2("claude", args, { stdio: "inherit" }),
|
|
270
388
|
provider
|
|
271
389
|
};
|
|
272
390
|
}
|
|
273
391
|
case "gemini": {
|
|
274
392
|
return {
|
|
275
|
-
result:
|
|
393
|
+
result: execa2("gemini", ["-i", prompt], { stdio: "inherit" }),
|
|
276
394
|
provider
|
|
277
395
|
};
|
|
278
396
|
}
|
|
279
397
|
case "codex": {
|
|
280
398
|
const args = prompt ? [prompt] : [];
|
|
281
399
|
return {
|
|
282
|
-
result:
|
|
400
|
+
result: execa2("codex", args, { stdio: "inherit" }),
|
|
283
401
|
provider
|
|
284
402
|
};
|
|
285
403
|
}
|
|
@@ -325,7 +443,7 @@ async function invokeClaudeInternal(options) {
|
|
|
325
443
|
}
|
|
326
444
|
args.push(options.prompt);
|
|
327
445
|
if (options.printOutput) {
|
|
328
|
-
const subprocess =
|
|
446
|
+
const subprocess = execa2("claude", args, {
|
|
329
447
|
stdio: "inherit"
|
|
330
448
|
});
|
|
331
449
|
await subprocess;
|
|
@@ -361,7 +479,7 @@ async function invokeClaudeInternal(options) {
|
|
|
361
479
|
child.on("error", reject);
|
|
362
480
|
});
|
|
363
481
|
} else {
|
|
364
|
-
const { stdout } = await
|
|
482
|
+
const { stdout } = await execa2("claude", args);
|
|
365
483
|
return stdout;
|
|
366
484
|
}
|
|
367
485
|
}
|
|
@@ -369,7 +487,7 @@ async function invokeGeminiInternal(options) {
|
|
|
369
487
|
const args = [];
|
|
370
488
|
args.push(options.prompt);
|
|
371
489
|
if (options.printOutput) {
|
|
372
|
-
const subprocess =
|
|
490
|
+
const subprocess = execa2("gemini", args, {
|
|
373
491
|
stdio: "inherit"
|
|
374
492
|
});
|
|
375
493
|
await subprocess;
|
|
@@ -405,14 +523,14 @@ async function invokeGeminiInternal(options) {
|
|
|
405
523
|
child.on("error", reject);
|
|
406
524
|
});
|
|
407
525
|
} else {
|
|
408
|
-
const { stdout } = await
|
|
526
|
+
const { stdout } = await execa2("gemini", args);
|
|
409
527
|
return stdout;
|
|
410
528
|
}
|
|
411
529
|
}
|
|
412
530
|
async function invokeCodexInternal(options) {
|
|
413
531
|
const args = ["exec", options.prompt];
|
|
414
532
|
if (options.printOutput) {
|
|
415
|
-
const subprocess =
|
|
533
|
+
const subprocess = execa2("codex", args, {
|
|
416
534
|
stdio: "inherit"
|
|
417
535
|
});
|
|
418
536
|
await subprocess;
|
|
@@ -448,7 +566,7 @@ async function invokeCodexInternal(options) {
|
|
|
448
566
|
child.on("error", reject);
|
|
449
567
|
});
|
|
450
568
|
} else {
|
|
451
|
-
const { stdout } = await
|
|
569
|
+
const { stdout } = await execa2("codex", args);
|
|
452
570
|
return stdout;
|
|
453
571
|
}
|
|
454
572
|
}
|
|
@@ -842,158 +960,6 @@ Next steps:
|
|
|
842
960
|
import chalk2 from "chalk";
|
|
843
961
|
import inquirer3 from "inquirer";
|
|
844
962
|
|
|
845
|
-
// src/lib/git.ts
|
|
846
|
-
import { execa as execa2 } from "execa";
|
|
847
|
-
async function getCurrentBranch() {
|
|
848
|
-
const { stdout } = await execa2("git", ["branch", "--show-current"]);
|
|
849
|
-
return stdout.trim();
|
|
850
|
-
}
|
|
851
|
-
async function isOnMainBranch() {
|
|
852
|
-
const branch = await getCurrentBranch();
|
|
853
|
-
return branch === "main" || branch === "master";
|
|
854
|
-
}
|
|
855
|
-
async function getDefaultBranch() {
|
|
856
|
-
try {
|
|
857
|
-
const { stdout } = await execa2("git", [
|
|
858
|
-
"symbolic-ref",
|
|
859
|
-
"refs/remotes/origin/HEAD"
|
|
860
|
-
]);
|
|
861
|
-
return stdout.trim().replace("refs/remotes/origin/", "");
|
|
862
|
-
} catch {
|
|
863
|
-
try {
|
|
864
|
-
await execa2("git", ["rev-parse", "--verify", "main"]);
|
|
865
|
-
return "main";
|
|
866
|
-
} catch {
|
|
867
|
-
return "master";
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
async function branchExists(name) {
|
|
872
|
-
try {
|
|
873
|
-
await execa2("git", ["rev-parse", "--verify", name]);
|
|
874
|
-
return true;
|
|
875
|
-
} catch {
|
|
876
|
-
return false;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
async function createBranch(name, from) {
|
|
880
|
-
if (from) {
|
|
881
|
-
await execa2("git", ["checkout", "-b", name, from]);
|
|
882
|
-
} else {
|
|
883
|
-
await execa2("git", ["checkout", "-b", name]);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
async function checkoutBranch(name) {
|
|
887
|
-
await execa2("git", ["checkout", name]);
|
|
888
|
-
}
|
|
889
|
-
async function hasUncommittedChanges() {
|
|
890
|
-
const { stdout } = await execa2("git", ["status", "--porcelain"]);
|
|
891
|
-
return stdout.trim().length > 0;
|
|
892
|
-
}
|
|
893
|
-
async function getUnpushedCommits() {
|
|
894
|
-
try {
|
|
895
|
-
const { stdout } = await execa2("git", ["log", "@{u}..HEAD", "--oneline"]);
|
|
896
|
-
return stdout.trim().length > 0;
|
|
897
|
-
} catch {
|
|
898
|
-
return true;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
async function pushBranch(branch) {
|
|
902
|
-
const branchName = branch || await getCurrentBranch();
|
|
903
|
-
await execa2("git", ["push", "-u", "origin", branchName]);
|
|
904
|
-
}
|
|
905
|
-
async function getAuthorInitials() {
|
|
906
|
-
try {
|
|
907
|
-
const { stdout } = await execa2("git", ["config", "user.initials"]);
|
|
908
|
-
if (stdout.trim()) {
|
|
909
|
-
return stdout.trim();
|
|
910
|
-
}
|
|
911
|
-
} catch {
|
|
912
|
-
}
|
|
913
|
-
try {
|
|
914
|
-
const { stdout } = await execa2("git", ["config", "user.name"]);
|
|
915
|
-
const name = stdout.trim();
|
|
916
|
-
if (name) {
|
|
917
|
-
const parts = name.split(/\s+/);
|
|
918
|
-
return parts.map((p) => p[0]?.toLowerCase() || "").join("");
|
|
919
|
-
}
|
|
920
|
-
} catch {
|
|
921
|
-
}
|
|
922
|
-
return "dev";
|
|
923
|
-
}
|
|
924
|
-
async function getRepoInfo() {
|
|
925
|
-
try {
|
|
926
|
-
const { stdout } = await execa2("git", [
|
|
927
|
-
"config",
|
|
928
|
-
"--get",
|
|
929
|
-
"remote.origin.url"
|
|
930
|
-
]);
|
|
931
|
-
const url = stdout.trim();
|
|
932
|
-
const sshMatch = url.match(/git@github\.com:([^/]+)\/([^.]+)/);
|
|
933
|
-
if (sshMatch) {
|
|
934
|
-
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
935
|
-
}
|
|
936
|
-
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^.]+)/);
|
|
937
|
-
if (httpsMatch) {
|
|
938
|
-
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
939
|
-
}
|
|
940
|
-
return null;
|
|
941
|
-
} catch {
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
async function getCommitsSinceBase(base = "main") {
|
|
946
|
-
try {
|
|
947
|
-
const { stdout } = await execa2("git", [
|
|
948
|
-
"log",
|
|
949
|
-
`${base}..HEAD`,
|
|
950
|
-
"--pretty=format:%s"
|
|
951
|
-
]);
|
|
952
|
-
return stdout.trim().split("\n").filter(Boolean);
|
|
953
|
-
} catch {
|
|
954
|
-
return [];
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
async function getDiffSummary(base = "main") {
|
|
958
|
-
try {
|
|
959
|
-
const { stdout } = await execa2("git", ["diff", `${base}...HEAD`, "--stat"]);
|
|
960
|
-
return stdout.trim();
|
|
961
|
-
} catch {
|
|
962
|
-
return "";
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
async function getCurrentCommitSha() {
|
|
966
|
-
const { stdout } = await execa2("git", ["rev-parse", "HEAD"]);
|
|
967
|
-
return stdout.trim();
|
|
968
|
-
}
|
|
969
|
-
async function hasNewCommits(beforeSha) {
|
|
970
|
-
const currentSha = await getCurrentCommitSha();
|
|
971
|
-
return currentSha !== beforeSha;
|
|
972
|
-
}
|
|
973
|
-
async function getLastCommitTimestamp() {
|
|
974
|
-
const { stdout } = await execa2("git", ["log", "-1", "--format=%cI"]);
|
|
975
|
-
return stdout.trim();
|
|
976
|
-
}
|
|
977
|
-
async function listLocalBranches() {
|
|
978
|
-
const { stdout } = await execa2("git", [
|
|
979
|
-
"branch",
|
|
980
|
-
"--format=%(refname:short)"
|
|
981
|
-
]);
|
|
982
|
-
return stdout.trim().split("\n").filter(Boolean);
|
|
983
|
-
}
|
|
984
|
-
async function remoteBranchExists(name) {
|
|
985
|
-
try {
|
|
986
|
-
await execa2("git", ["ls-remote", "--exit-code", "--heads", "origin", name]);
|
|
987
|
-
return true;
|
|
988
|
-
} catch {
|
|
989
|
-
return false;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
async function fetchAndCheckout(name) {
|
|
993
|
-
await execa2("git", ["fetch", "origin", `${name}:${name}`]);
|
|
994
|
-
await execa2("git", ["checkout", name]);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
963
|
// src/lib/branch.ts
|
|
998
964
|
async function generateBranchName(config, issueNumber, issueTitle, type) {
|
|
999
965
|
const author = await resolveAuthor(config);
|
|
@@ -2240,7 +2206,7 @@ import { homedir } from "os";
|
|
|
2240
2206
|
// package.json
|
|
2241
2207
|
var package_default = {
|
|
2242
2208
|
name: "@rotorsoft/gent",
|
|
2243
|
-
version: "1.
|
|
2209
|
+
version: "1.24.0",
|
|
2244
2210
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
2245
2211
|
keywords: [
|
|
2246
2212
|
"cli",
|
|
@@ -2662,7 +2628,7 @@ async function statusCommand() {
|
|
|
2662
2628
|
}
|
|
2663
2629
|
|
|
2664
2630
|
// src/commands/tui.ts
|
|
2665
|
-
import { execa as
|
|
2631
|
+
import { execa as execa4 } from "execa";
|
|
2666
2632
|
|
|
2667
2633
|
// src/tui/state.ts
|
|
2668
2634
|
var envCache = null;
|
|
@@ -2730,7 +2696,7 @@ async function aggregateState() {
|
|
|
2730
2696
|
getUnpushedCommits()
|
|
2731
2697
|
]);
|
|
2732
2698
|
const hasRemote = repoInfo !== null;
|
|
2733
|
-
if (
|
|
2699
|
+
if (hasRemote && envCache.hasLabels === null) {
|
|
2734
2700
|
envCache.hasLabels = await checkLabelsExist().catch(() => false);
|
|
2735
2701
|
}
|
|
2736
2702
|
const hasLabels = envCache.hasLabels ?? false;
|
|
@@ -2814,14 +2780,14 @@ function getAvailableActions(state) {
|
|
|
2814
2780
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2815
2781
|
return actions;
|
|
2816
2782
|
}
|
|
2817
|
-
const
|
|
2818
|
-
|
|
2819
|
-
if (needsInit) {
|
|
2820
|
-
actions.push({ id: "init", label: "init", shortcut: "i" });
|
|
2821
|
-
} else if (needsLabels) {
|
|
2783
|
+
const needsLabels = state.hasValidRemote && !state.hasLabels;
|
|
2784
|
+
if (needsLabels) {
|
|
2822
2785
|
actions.push({ id: "setup-labels", label: "setup-labels", shortcut: "b" });
|
|
2823
2786
|
}
|
|
2824
|
-
|
|
2787
|
+
if (!state.hasConfig) {
|
|
2788
|
+
actions.push({ id: "init", label: "init", shortcut: "i" });
|
|
2789
|
+
}
|
|
2790
|
+
const isSetUp = !state.hasValidRemote || state.hasLabels;
|
|
2825
2791
|
if (isSetUp && state.hasValidRemote) {
|
|
2826
2792
|
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2827
2793
|
}
|
|
@@ -3138,24 +3104,17 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
|
|
|
3138
3104
|
out(row(chalk3.dim(" No commits"), w));
|
|
3139
3105
|
}
|
|
3140
3106
|
}
|
|
3141
|
-
if (!state.
|
|
3107
|
+
if (!state.hasValidRemote) {
|
|
3142
3108
|
section("Setup");
|
|
3143
|
-
out(row(chalk3.yellow(
|
|
3144
|
-
out(row(chalk3.dim("Press [
|
|
3145
|
-
} else if (
|
|
3109
|
+
out(row(chalk3.yellow("Step 1: Create a GitHub repository"), w));
|
|
3110
|
+
out(row(chalk3.dim("Press [g] to create a GitHub repo and push"), w));
|
|
3111
|
+
} else if (!state.hasLabels) {
|
|
3146
3112
|
section("Setup");
|
|
3147
|
-
out(row(chalk3.yellow(
|
|
3113
|
+
out(row(chalk3.yellow("Step 2: Create workflow labels"), w));
|
|
3148
3114
|
out(row(chalk3.dim("Press [b] to set up labels"), w));
|
|
3149
|
-
} else if (!state.
|
|
3150
|
-
section("
|
|
3151
|
-
out(
|
|
3152
|
-
row(
|
|
3153
|
-
chalk3.yellow(
|
|
3154
|
-
"Press [g] to create a GitHub repo and push"
|
|
3155
|
-
),
|
|
3156
|
-
w
|
|
3157
|
-
)
|
|
3158
|
-
);
|
|
3115
|
+
} else if (!state.hasConfig) {
|
|
3116
|
+
section("Tip");
|
|
3117
|
+
out(row(chalk3.dim("Press [i] to customize configuration (optional)"), w));
|
|
3159
3118
|
} else if (hint) {
|
|
3160
3119
|
section("Hint");
|
|
3161
3120
|
out(row(chalk3.yellow(hint), w));
|
|
@@ -3669,38 +3628,6 @@ function showStatus(title, message, dashboardLines) {
|
|
|
3669
3628
|
renderOverlay(dashboardLines, lines, w);
|
|
3670
3629
|
}
|
|
3671
3630
|
|
|
3672
|
-
// src/commands/github-remote.ts
|
|
3673
|
-
import { execa as execa4 } from "execa";
|
|
3674
|
-
async function githubRemoteCommand() {
|
|
3675
|
-
const isAuthenticated = await checkGhAuth();
|
|
3676
|
-
if (!isAuthenticated) {
|
|
3677
|
-
logger.error("GitHub CLI is not authenticated. Run: gh auth login");
|
|
3678
|
-
return false;
|
|
3679
|
-
}
|
|
3680
|
-
try {
|
|
3681
|
-
try {
|
|
3682
|
-
const { stdout } = await execa4("git", [
|
|
3683
|
-
"config",
|
|
3684
|
-
"--get",
|
|
3685
|
-
"remote.origin.url"
|
|
3686
|
-
]);
|
|
3687
|
-
if (stdout.trim()) {
|
|
3688
|
-
logger.error("Remote origin already exists: " + stdout.trim());
|
|
3689
|
-
return false;
|
|
3690
|
-
}
|
|
3691
|
-
} catch {
|
|
3692
|
-
}
|
|
3693
|
-
logger.info("Creating GitHub repository...");
|
|
3694
|
-
await execa4("gh", ["repo", "create", "--source=.", "--push", "--private"]);
|
|
3695
|
-
const branch = await getCurrentBranch();
|
|
3696
|
-
logger.success(`Repository created and ${branch} pushed to GitHub`);
|
|
3697
|
-
return true;
|
|
3698
|
-
} catch (error) {
|
|
3699
|
-
logger.error(`Failed to create remote: ${error}`);
|
|
3700
|
-
return false;
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
|
|
3704
3631
|
// src/commands/tui.ts
|
|
3705
3632
|
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
3706
3633
|
async function waitForKey(validKeys) {
|
|
@@ -3812,19 +3739,19 @@ async function executeAction(actionId, state, dashboardLines) {
|
|
|
3812
3739
|
}
|
|
3813
3740
|
async function handleCommit(state, dashboardLines) {
|
|
3814
3741
|
try {
|
|
3815
|
-
const { stdout: status } = await
|
|
3742
|
+
const { stdout: status } = await execa4("git", ["status", "--short"]);
|
|
3816
3743
|
if (!status.trim()) {
|
|
3817
3744
|
showStatus("Commit", "No changes to commit", dashboardLines);
|
|
3818
3745
|
await new Promise((r) => setTimeout(r, 1500));
|
|
3819
3746
|
return false;
|
|
3820
3747
|
}
|
|
3821
|
-
await
|
|
3822
|
-
const { stdout: diffStat } = await
|
|
3748
|
+
await execa4("git", ["add", "-A"]);
|
|
3749
|
+
const { stdout: diffStat } = await execa4("git", [
|
|
3823
3750
|
"diff",
|
|
3824
3751
|
"--cached",
|
|
3825
3752
|
"--stat"
|
|
3826
3753
|
]);
|
|
3827
|
-
const { stdout: diffPatch } = await
|
|
3754
|
+
const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
|
|
3828
3755
|
const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
|
|
3829
3756
|
const issueNumber = state.issue?.number ?? null;
|
|
3830
3757
|
const issueTitle = state.issue?.title ?? null;
|
|
@@ -3839,7 +3766,7 @@ async function handleCommit(state, dashboardLines) {
|
|
|
3839
3766
|
dashboardLines
|
|
3840
3767
|
});
|
|
3841
3768
|
if (!mode) {
|
|
3842
|
-
await
|
|
3769
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
3843
3770
|
return false;
|
|
3844
3771
|
}
|
|
3845
3772
|
let message;
|
|
@@ -3861,7 +3788,7 @@ async function handleCommit(state, dashboardLines) {
|
|
|
3861
3788
|
);
|
|
3862
3789
|
}
|
|
3863
3790
|
if (message === CANCEL) {
|
|
3864
|
-
await
|
|
3791
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
3865
3792
|
return false;
|
|
3866
3793
|
}
|
|
3867
3794
|
const confirmed = await showConfirm({
|
|
@@ -3870,7 +3797,7 @@ async function handleCommit(state, dashboardLines) {
|
|
|
3870
3797
|
dashboardLines
|
|
3871
3798
|
});
|
|
3872
3799
|
if (!confirmed) {
|
|
3873
|
-
await
|
|
3800
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
3874
3801
|
return false;
|
|
3875
3802
|
}
|
|
3876
3803
|
const providerEmail = getProviderEmail(provider);
|
|
@@ -3878,7 +3805,7 @@ async function handleCommit(state, dashboardLines) {
|
|
|
3878
3805
|
|
|
3879
3806
|
Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
3880
3807
|
showStatus("Committing", "Committing changes...", dashboardLines);
|
|
3881
|
-
await
|
|
3808
|
+
await execa4("git", ["commit", "-m", fullMessage]);
|
|
3882
3809
|
return true;
|
|
3883
3810
|
} catch (error) {
|
|
3884
3811
|
logger.error(`Commit failed: ${error}`);
|
|
@@ -3958,10 +3885,10 @@ ${feedbackLines}`);
|
|
|
3958
3885
|
}
|
|
3959
3886
|
async function handlePush(dashboardLines) {
|
|
3960
3887
|
try {
|
|
3961
|
-
const { stdout: branch } = await
|
|
3888
|
+
const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
|
|
3962
3889
|
const branchName = branch.trim();
|
|
3963
3890
|
showStatus("Pushing", `Pushing ${branchName} to remote...`, dashboardLines);
|
|
3964
|
-
await
|
|
3891
|
+
await execa4("git", ["push", "-u", "origin", branchName]);
|
|
3965
3892
|
} catch (error) {
|
|
3966
3893
|
logger.error(`Push failed: ${error}`);
|
|
3967
3894
|
}
|
|
@@ -4219,8 +4146,9 @@ function startVersionCheck() {
|
|
|
4219
4146
|
});
|
|
4220
4147
|
}
|
|
4221
4148
|
async function checkPrerequisites() {
|
|
4222
|
-
|
|
4223
|
-
|
|
4149
|
+
const repoInfo = await getRepoInfo();
|
|
4150
|
+
if (!repoInfo) {
|
|
4151
|
+
logger.warning('No GitHub remote found. Run "gent github-remote" to create one.');
|
|
4224
4152
|
return false;
|
|
4225
4153
|
}
|
|
4226
4154
|
const labelsOk = await checkLabelsExist();
|
|
@@ -4238,7 +4166,7 @@ program.name("gent").description(
|
|
|
4238
4166
|
startVersionCheck();
|
|
4239
4167
|
}
|
|
4240
4168
|
});
|
|
4241
|
-
program.command("init").description("Initialize gent workflow in current repository").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
|
|
4169
|
+
program.command("init").description("Initialize gent workflow in current repository (optional \u2014 for customization)").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
|
|
4242
4170
|
await initCommand(options);
|
|
4243
4171
|
});
|
|
4244
4172
|
program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
|