@rotorsoft/gent 1.21.0 → 1.23.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/index.js CHANGED
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ githubRemoteCommand
4
+ } from "./chunk-ZDOEOZDC.js";
5
+ import {
6
+ aiSpinnerText,
7
+ buildIssueLabels,
8
+ createSpinner,
9
+ extractTypeFromLabels,
10
+ getWorkflowLabels,
11
+ setupLabelsCommand,
12
+ sortByPriority,
13
+ withSpinner
14
+ } from "./chunk-ZQDWILU5.js";
2
15
  import {
3
16
  addIssueComment,
4
17
  addPrComment,
5
- aiSpinnerText,
6
18
  assignIssue,
7
- buildIssueLabels,
19
+ branchExists,
8
20
  checkAIProvider,
9
21
  checkClaudeCli,
10
22
  checkGeminiCli,
@@ -12,35 +24,47 @@ import {
12
24
  checkGitRepo,
13
25
  checkInitialized,
14
26
  checkLabelsExist,
27
+ checkoutBranch,
15
28
  colors,
16
29
  configExists,
30
+ createBranch,
17
31
  createIssue,
18
32
  createPullRequest,
19
- createSpinner,
20
- extractTypeFromLabels,
33
+ fetchAndCheckout,
21
34
  generateDefaultConfig,
35
+ getAuthorInitials,
36
+ getCommitsSinceBase,
22
37
  getConfigPath,
38
+ getCurrentBranch,
39
+ getCurrentCommitSha,
23
40
  getCurrentUser,
41
+ getDefaultBranch,
42
+ getDiffSummary,
24
43
  getIssue,
44
+ getLastCommitTimestamp,
25
45
  getPrForBranch,
26
46
  getPrReviewData,
27
47
  getPrStatus,
28
- getWorkflowLabels,
48
+ getRepoInfo,
49
+ getUnpushedCommits,
50
+ hasNewCommits,
51
+ hasUncommittedChanges,
52
+ isOnMainBranch,
29
53
  isValidIssueNumber,
30
54
  listIssues,
55
+ listLocalBranches,
31
56
  listOpenPrs,
32
57
  loadAgentInstructions,
33
58
  loadConfig,
34
59
  logger,
60
+ pushBranch,
61
+ remoteBranchExists,
35
62
  replyToReviewComment,
36
63
  resolveProvider,
37
64
  sanitizeSlug,
38
65
  setRuntimeProvider,
39
- setupLabelsCommand,
40
- sortByPriority,
41
- updateIssueLabels,
42
- withSpinner
43
- } from "./chunk-75KVN7ZC.js";
66
+ updateIssueLabels
67
+ } from "./chunk-HCJWQPMW.js";
44
68
 
45
69
  // src/index.ts
46
70
  import { Command } from "commander";
@@ -48,6 +72,7 @@ import { Command } from "commander";
48
72
  // src/commands/init.ts
49
73
  import { writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
50
74
  import { join as join2 } from "path";
75
+ import { execa } from "execa";
51
76
  import inquirer from "inquirer";
52
77
 
53
78
  // src/lib/progress.ts
@@ -86,6 +111,47 @@ Each entry documents: date, feature, decisions, files changed, tests, and concer
86
111
  }
87
112
 
88
113
  // src/commands/init.ts
114
+ var DEFAULT_GITIGNORE = `# Dependencies
115
+ node_modules/
116
+
117
+ # Build output
118
+ dist/
119
+
120
+ # Test coverage
121
+ coverage/
122
+
123
+ # IDE
124
+ .idea/
125
+ .vscode/
126
+ *.swp
127
+ *.swo
128
+
129
+ # OS
130
+ .DS_Store
131
+ Thumbs.db
132
+
133
+ # Logs
134
+ *.log
135
+ npm-debug.log*
136
+
137
+ # Environment
138
+ .env
139
+ .env.local
140
+ .env.*.local
141
+
142
+ # Temporary files
143
+ *.tmp
144
+ *.temp
145
+ .cache/
146
+ `;
147
+ async function hasCommits() {
148
+ try {
149
+ await execa("git", ["rev-parse", "HEAD"]);
150
+ return true;
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
89
155
  var DEFAULT_AGENT_MD = `# AI Agent Instructions
90
156
 
91
157
  This file contains instructions for the AI when working on this repository.
@@ -147,7 +213,7 @@ async function initCommand(options) {
147
213
  const isGitRepo = await checkGitRepo();
148
214
  if (!isGitRepo) {
149
215
  logger.error("Not a git repository. Please run 'git init' first.");
150
- process.exit(1);
216
+ return;
151
217
  }
152
218
  const cwd = process.cwd();
153
219
  if (configExists(cwd) && !options.force) {
@@ -173,6 +239,13 @@ async function initCommand(options) {
173
239
  default: "claude"
174
240
  }
175
241
  ]);
242
+ const gitignorePath = join2(cwd, ".gitignore");
243
+ if (!existsSync2(gitignorePath)) {
244
+ writeFileSync2(gitignorePath, DEFAULT_GITIGNORE, "utf-8");
245
+ logger.success(`Created ${colors.file(".gitignore")}`);
246
+ } else {
247
+ logger.info(`${colors.file(".gitignore")} already exists, skipping`);
248
+ }
176
249
  const configPath = getConfigPath(cwd);
177
250
  writeFileSync2(configPath, generateDefaultConfig(provider), "utf-8");
178
251
  logger.success(`Created ${colors.file(".gent.yml")}`);
@@ -186,26 +259,72 @@ async function initCommand(options) {
186
259
  const config = loadConfig(cwd);
187
260
  initializeProgress(config, cwd);
188
261
  logger.success(`Created ${colors.file(config.progress.file)}`);
189
- logger.newline();
190
- logger.box(
191
- "Setup Complete",
192
- `Next steps:
262
+ if (!await hasCommits()) {
263
+ logger.newline();
264
+ logger.info("No commits found. Creating initial commit with gent config...");
265
+ await execa("git", ["add", ".gitignore", ".gent.yml", "AGENT.md", config.progress.file]);
266
+ await execa("git", ["commit", "-m", "chore: initialize gent workflow"]);
267
+ logger.success("Created initial commit");
268
+ }
269
+ const repoInfo = await getRepoInfo();
270
+ if (!repoInfo) {
271
+ logger.newline();
272
+ logger.box(
273
+ "Setup Complete",
274
+ `Next steps:
275
+ 1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
276
+ 2. Edit ${colors.file(".gent.yml")} to customize settings
277
+ 3. Create a GitHub remote: ${colors.command("gent github-remote")}
278
+ 4. Run ${colors.command("gent setup-labels")} to create GitHub labels`
279
+ );
280
+ const { createRemote } = await inquirer.prompt([
281
+ {
282
+ type: "confirm",
283
+ name: "createRemote",
284
+ message: "No GitHub remote found. Would you like to create one now?",
285
+ default: true
286
+ }
287
+ ]);
288
+ if (createRemote) {
289
+ const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-4TLUL2HX.js");
290
+ const success = await githubRemoteCommand2();
291
+ if (success) {
292
+ const { setupLabels } = await inquirer.prompt([
293
+ {
294
+ type: "confirm",
295
+ name: "setupLabels",
296
+ message: "Would you like to setup GitHub labels now?",
297
+ default: true
298
+ }
299
+ ]);
300
+ if (setupLabels) {
301
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-2SNO3QQL.js");
302
+ await setupLabelsCommand2();
303
+ }
304
+ }
305
+ }
306
+ } else {
307
+ logger.newline();
308
+ logger.box(
309
+ "Setup Complete",
310
+ `Next steps:
193
311
  1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
194
312
  2. Edit ${colors.file(".gent.yml")} to customize settings
195
313
  3. Run ${colors.command("gent setup-labels")} to create GitHub labels
196
314
  4. Run ${colors.command("gent create <description>")} to create your first ticket`
197
- );
198
- const { setupLabels } = await inquirer.prompt([
199
- {
200
- type: "confirm",
201
- name: "setupLabels",
202
- message: "Would you like to setup GitHub labels now?",
203
- default: true
315
+ );
316
+ const { setupLabels } = await inquirer.prompt([
317
+ {
318
+ type: "confirm",
319
+ name: "setupLabels",
320
+ message: "Would you like to setup GitHub labels now?",
321
+ default: true
322
+ }
323
+ ]);
324
+ if (setupLabels) {
325
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-2SNO3QQL.js");
326
+ await setupLabelsCommand2();
204
327
  }
205
- ]);
206
- if (setupLabels) {
207
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-STWAFV2E.js");
208
- await setupLabelsCommand2();
209
328
  }
210
329
  }
211
330
 
@@ -215,7 +334,7 @@ import chalk from "chalk";
215
334
 
216
335
  // src/lib/ai-provider.ts
217
336
  import { spawn } from "child_process";
218
- import { execa } from "execa";
337
+ import { execa as execa2 } from "execa";
219
338
  async function getOtherAvailableProviders(currentProvider) {
220
339
  const allProviders = ["claude", "gemini", "codex"];
221
340
  const others = allProviders.filter((p) => p !== currentProvider);
@@ -266,20 +385,20 @@ async function invokeAIInteractive(prompt, config, providerOverride) {
266
385
  case "claude": {
267
386
  const args = ["--permission-mode", config.claude.permission_mode, prompt];
268
387
  return {
269
- result: execa("claude", args, { stdio: "inherit" }),
388
+ result: execa2("claude", args, { stdio: "inherit" }),
270
389
  provider
271
390
  };
272
391
  }
273
392
  case "gemini": {
274
393
  return {
275
- result: execa("gemini", ["-i", prompt], { stdio: "inherit" }),
394
+ result: execa2("gemini", ["-i", prompt], { stdio: "inherit" }),
276
395
  provider
277
396
  };
278
397
  }
279
398
  case "codex": {
280
399
  const args = prompt ? [prompt] : [];
281
400
  return {
282
- result: execa("codex", args, { stdio: "inherit" }),
401
+ result: execa2("codex", args, { stdio: "inherit" }),
283
402
  provider
284
403
  };
285
404
  }
@@ -325,7 +444,7 @@ async function invokeClaudeInternal(options) {
325
444
  }
326
445
  args.push(options.prompt);
327
446
  if (options.printOutput) {
328
- const subprocess = execa("claude", args, {
447
+ const subprocess = execa2("claude", args, {
329
448
  stdio: "inherit"
330
449
  });
331
450
  await subprocess;
@@ -361,7 +480,7 @@ async function invokeClaudeInternal(options) {
361
480
  child.on("error", reject);
362
481
  });
363
482
  } else {
364
- const { stdout } = await execa("claude", args);
483
+ const { stdout } = await execa2("claude", args);
365
484
  return stdout;
366
485
  }
367
486
  }
@@ -369,7 +488,7 @@ async function invokeGeminiInternal(options) {
369
488
  const args = [];
370
489
  args.push(options.prompt);
371
490
  if (options.printOutput) {
372
- const subprocess = execa("gemini", args, {
491
+ const subprocess = execa2("gemini", args, {
373
492
  stdio: "inherit"
374
493
  });
375
494
  await subprocess;
@@ -405,14 +524,14 @@ async function invokeGeminiInternal(options) {
405
524
  child.on("error", reject);
406
525
  });
407
526
  } else {
408
- const { stdout } = await execa("gemini", args);
527
+ const { stdout } = await execa2("gemini", args);
409
528
  return stdout;
410
529
  }
411
530
  }
412
531
  async function invokeCodexInternal(options) {
413
532
  const args = ["exec", options.prompt];
414
533
  if (options.printOutput) {
415
- const subprocess = execa("codex", args, {
534
+ const subprocess = execa2("codex", args, {
416
535
  stdio: "inherit"
417
536
  });
418
537
  await subprocess;
@@ -448,7 +567,7 @@ async function invokeCodexInternal(options) {
448
567
  child.on("error", reject);
449
568
  });
450
569
  } else {
451
- const { stdout } = await execa("codex", args);
570
+ const { stdout } = await execa2("codex", args);
452
571
  return stdout;
453
572
  }
454
573
  }
@@ -842,158 +961,6 @@ Next steps:
842
961
  import chalk2 from "chalk";
843
962
  import inquirer3 from "inquirer";
844
963
 
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
964
  // src/lib/branch.ts
998
965
  async function generateBranchName(config, issueNumber, issueTitle, type) {
999
966
  const author = await resolveAuthor(config);
@@ -2240,7 +2207,7 @@ import { homedir } from "os";
2240
2207
  // package.json
2241
2208
  var package_default = {
2242
2209
  name: "@rotorsoft/gent",
2243
- version: "1.21.0",
2210
+ version: "1.23.0",
2244
2211
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2245
2212
  keywords: [
2246
2213
  "cli",
@@ -2662,7 +2629,7 @@ async function statusCommand() {
2662
2629
  }
2663
2630
 
2664
2631
  // src/commands/tui.ts
2665
- import { execa as execa5 } from "execa";
2632
+ import { execa as execa4 } from "execa";
2666
2633
 
2667
2634
  // src/tui/state.ts
2668
2635
  var envCache = null;
@@ -2878,19 +2845,24 @@ function truncate(text, max) {
2878
2845
  if (text.length <= max) return text;
2879
2846
  return text.slice(0, max - 1) + "\u2026";
2880
2847
  }
2881
- function extractDescription(body, maxLen) {
2848
+ function extractDescriptionLines(body, maxLen, maxLines = 3) {
2849
+ const result = [];
2882
2850
  const lines = body.split("\n");
2883
2851
  for (const line of lines) {
2852
+ if (result.length >= maxLines) break;
2884
2853
  const trimmed = line.trim();
2885
2854
  if (!trimmed) continue;
2886
2855
  if (trimmed.startsWith("#")) continue;
2887
2856
  if (trimmed.startsWith("---")) continue;
2888
2857
  if (trimmed.startsWith("META:")) continue;
2889
2858
  if (trimmed.startsWith("**Type:**")) continue;
2859
+ if (trimmed.startsWith("**Category:**")) continue;
2860
+ if (trimmed.startsWith("**Priority:**")) continue;
2861
+ if (trimmed.startsWith("**Risk:**")) continue;
2890
2862
  const clean = trimmed.replace(/\*\*/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
2891
- return truncate(clean, maxLen);
2863
+ result.push(truncate(clean, maxLen));
2892
2864
  }
2893
- return "";
2865
+ return result;
2894
2866
  }
2895
2867
  function topRow(title, w) {
2896
2868
  const label = ` ${title} `;
@@ -3050,8 +3022,10 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3050
3022
  w
3051
3023
  )
3052
3024
  );
3053
- const desc = extractDescription(state.issue.body, descMax);
3054
- if (desc) out(row(" " + chalk3.dim(desc), w));
3025
+ const descLines = extractDescriptionLines(state.issue.body, descMax);
3026
+ for (const desc of descLines) {
3027
+ out(row(" " + chalk3.dim(desc), w));
3028
+ }
3055
3029
  const tags = [];
3056
3030
  if (state.workflowStatus !== "none")
3057
3031
  tags.push(workflowBadge(state.workflowStatus));
@@ -3133,22 +3107,16 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3133
3107
  }
3134
3108
  if (!state.hasConfig) {
3135
3109
  section("Setup");
3136
- out(row(chalk3.yellow('Run "gent init" to set up this repository'), w));
3137
- out(row(chalk3.dim("Press [i] to initialize"), w));
3138
- } else if (state.hasValidRemote && !state.hasLabels) {
3110
+ out(row(chalk3.yellow("Step 1: Initialize gent configuration"), w));
3111
+ out(row(chalk3.dim("Press [i] to run gent init"), w));
3112
+ } else if (!state.hasValidRemote) {
3113
+ section("Setup");
3114
+ out(row(chalk3.yellow("Step 2: Create a GitHub repository"), w));
3115
+ out(row(chalk3.dim("Press [g] to create a GitHub repo and push"), w));
3116
+ } else if (!state.hasLabels) {
3139
3117
  section("Setup");
3140
- out(row(chalk3.yellow('Run "gent setup-labels" to create required GitHub labels'), w));
3118
+ out(row(chalk3.yellow("Step 3: Create workflow labels"), w));
3141
3119
  out(row(chalk3.dim("Press [b] to set up labels"), w));
3142
- } else if (!state.hasValidRemote) {
3143
- section("Hint");
3144
- out(
3145
- row(
3146
- chalk3.yellow(
3147
- "Press [g] to create a GitHub repo and push"
3148
- ),
3149
- w
3150
- )
3151
- );
3152
3120
  } else if (hint) {
3153
3121
  section("Hint");
3154
3122
  out(row(chalk3.yellow(hint), w));
@@ -3662,38 +3630,6 @@ function showStatus(title, message, dashboardLines) {
3662
3630
  renderOverlay(dashboardLines, lines, w);
3663
3631
  }
3664
3632
 
3665
- // src/commands/github-remote.ts
3666
- import { execa as execa4 } from "execa";
3667
- async function githubRemoteCommand() {
3668
- const isAuthenticated = await checkGhAuth();
3669
- if (!isAuthenticated) {
3670
- logger.error("GitHub CLI is not authenticated. Run: gh auth login");
3671
- return false;
3672
- }
3673
- try {
3674
- try {
3675
- const { stdout } = await execa4("git", [
3676
- "config",
3677
- "--get",
3678
- "remote.origin.url"
3679
- ]);
3680
- if (stdout.trim()) {
3681
- logger.error("Remote origin already exists: " + stdout.trim());
3682
- return false;
3683
- }
3684
- } catch {
3685
- }
3686
- logger.info("Creating GitHub repository...");
3687
- await execa4("gh", ["repo", "create", "--source=.", "--push", "--private"]);
3688
- const branch = await getCurrentBranch();
3689
- logger.success(`Repository created and ${branch} pushed to GitHub`);
3690
- return true;
3691
- } catch (error) {
3692
- logger.error(`Failed to create remote: ${error}`);
3693
- return false;
3694
- }
3695
- }
3696
-
3697
3633
  // src/commands/tui.ts
3698
3634
  var CANCEL = /* @__PURE__ */ Symbol("cancel");
3699
3635
  async function waitForKey(validKeys) {
@@ -3805,19 +3741,19 @@ async function executeAction(actionId, state, dashboardLines) {
3805
3741
  }
3806
3742
  async function handleCommit(state, dashboardLines) {
3807
3743
  try {
3808
- const { stdout: status } = await execa5("git", ["status", "--short"]);
3744
+ const { stdout: status } = await execa4("git", ["status", "--short"]);
3809
3745
  if (!status.trim()) {
3810
3746
  showStatus("Commit", "No changes to commit", dashboardLines);
3811
3747
  await new Promise((r) => setTimeout(r, 1500));
3812
3748
  return false;
3813
3749
  }
3814
- await execa5("git", ["add", "-A"]);
3815
- const { stdout: diffStat } = await execa5("git", [
3750
+ await execa4("git", ["add", "-A"]);
3751
+ const { stdout: diffStat } = await execa4("git", [
3816
3752
  "diff",
3817
3753
  "--cached",
3818
3754
  "--stat"
3819
3755
  ]);
3820
- const { stdout: diffPatch } = await execa5("git", ["diff", "--cached"]);
3756
+ const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
3821
3757
  const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
3822
3758
  const issueNumber = state.issue?.number ?? null;
3823
3759
  const issueTitle = state.issue?.title ?? null;
@@ -3832,7 +3768,7 @@ async function handleCommit(state, dashboardLines) {
3832
3768
  dashboardLines
3833
3769
  });
3834
3770
  if (!mode) {
3835
- await execa5("git", ["reset", "HEAD"]);
3771
+ await execa4("git", ["reset", "HEAD"]);
3836
3772
  return false;
3837
3773
  }
3838
3774
  let message;
@@ -3854,7 +3790,7 @@ async function handleCommit(state, dashboardLines) {
3854
3790
  );
3855
3791
  }
3856
3792
  if (message === CANCEL) {
3857
- await execa5("git", ["reset", "HEAD"]);
3793
+ await execa4("git", ["reset", "HEAD"]);
3858
3794
  return false;
3859
3795
  }
3860
3796
  const confirmed = await showConfirm({
@@ -3863,7 +3799,7 @@ async function handleCommit(state, dashboardLines) {
3863
3799
  dashboardLines
3864
3800
  });
3865
3801
  if (!confirmed) {
3866
- await execa5("git", ["reset", "HEAD"]);
3802
+ await execa4("git", ["reset", "HEAD"]);
3867
3803
  return false;
3868
3804
  }
3869
3805
  const providerEmail = getProviderEmail(provider);
@@ -3871,7 +3807,7 @@ async function handleCommit(state, dashboardLines) {
3871
3807
 
3872
3808
  Co-Authored-By: ${providerName} <${providerEmail}>`;
3873
3809
  showStatus("Committing", "Committing changes...", dashboardLines);
3874
- await execa5("git", ["commit", "-m", fullMessage]);
3810
+ await execa4("git", ["commit", "-m", fullMessage]);
3875
3811
  return true;
3876
3812
  } catch (error) {
3877
3813
  logger.error(`Commit failed: ${error}`);
@@ -3951,10 +3887,10 @@ ${feedbackLines}`);
3951
3887
  }
3952
3888
  async function handlePush(dashboardLines) {
3953
3889
  try {
3954
- const { stdout: branch } = await execa5("git", ["branch", "--show-current"]);
3890
+ const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
3955
3891
  const branchName = branch.trim();
3956
3892
  showStatus("Pushing", `Pushing ${branchName} to remote...`, dashboardLines);
3957
- await execa5("git", ["push", "-u", "origin", branchName]);
3893
+ await execa4("git", ["push", "-u", "origin", branchName]);
3958
3894
  } catch (error) {
3959
3895
  logger.error(`Push failed: ${error}`);
3960
3896
  }
@@ -4216,6 +4152,11 @@ async function checkPrerequisites() {
4216
4152
  logger.warning('Repository not initialized. Run "gent init" to set up this repository.');
4217
4153
  return false;
4218
4154
  }
4155
+ const repoInfo = await getRepoInfo();
4156
+ if (!repoInfo) {
4157
+ logger.warning('No GitHub remote found. Run "gent github-remote" to create one.');
4158
+ return false;
4159
+ }
4219
4160
  const labelsOk = await checkLabelsExist();
4220
4161
  if (!labelsOk) {
4221
4162
  logger.warning('GitHub labels not found. Run "gent setup-labels" to create required labels.');