@rotorsoft/gent 1.22.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.22.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;
@@ -3140,22 +3107,16 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3140
3107
  }
3141
3108
  if (!state.hasConfig) {
3142
3109
  section("Setup");
3143
- out(row(chalk3.yellow('Run "gent init" to set up this repository'), w));
3144
- out(row(chalk3.dim("Press [i] to initialize"), w));
3145
- } 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) {
3146
3113
  section("Setup");
3147
- out(row(chalk3.yellow('Run "gent setup-labels" to create required GitHub labels'), w));
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) {
3117
+ section("Setup");
3118
+ out(row(chalk3.yellow("Step 3: Create workflow labels"), w));
3148
3119
  out(row(chalk3.dim("Press [b] to set up labels"), w));
3149
- } else if (!state.hasValidRemote) {
3150
- section("Hint");
3151
- out(
3152
- row(
3153
- chalk3.yellow(
3154
- "Press [g] to create a GitHub repo and push"
3155
- ),
3156
- w
3157
- )
3158
- );
3159
3120
  } else if (hint) {
3160
3121
  section("Hint");
3161
3122
  out(row(chalk3.yellow(hint), w));
@@ -3669,38 +3630,6 @@ function showStatus(title, message, dashboardLines) {
3669
3630
  renderOverlay(dashboardLines, lines, w);
3670
3631
  }
3671
3632
 
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
3633
  // src/commands/tui.ts
3705
3634
  var CANCEL = /* @__PURE__ */ Symbol("cancel");
3706
3635
  async function waitForKey(validKeys) {
@@ -3812,19 +3741,19 @@ async function executeAction(actionId, state, dashboardLines) {
3812
3741
  }
3813
3742
  async function handleCommit(state, dashboardLines) {
3814
3743
  try {
3815
- const { stdout: status } = await execa5("git", ["status", "--short"]);
3744
+ const { stdout: status } = await execa4("git", ["status", "--short"]);
3816
3745
  if (!status.trim()) {
3817
3746
  showStatus("Commit", "No changes to commit", dashboardLines);
3818
3747
  await new Promise((r) => setTimeout(r, 1500));
3819
3748
  return false;
3820
3749
  }
3821
- await execa5("git", ["add", "-A"]);
3822
- const { stdout: diffStat } = await execa5("git", [
3750
+ await execa4("git", ["add", "-A"]);
3751
+ const { stdout: diffStat } = await execa4("git", [
3823
3752
  "diff",
3824
3753
  "--cached",
3825
3754
  "--stat"
3826
3755
  ]);
3827
- const { stdout: diffPatch } = await execa5("git", ["diff", "--cached"]);
3756
+ const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
3828
3757
  const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
3829
3758
  const issueNumber = state.issue?.number ?? null;
3830
3759
  const issueTitle = state.issue?.title ?? null;
@@ -3839,7 +3768,7 @@ async function handleCommit(state, dashboardLines) {
3839
3768
  dashboardLines
3840
3769
  });
3841
3770
  if (!mode) {
3842
- await execa5("git", ["reset", "HEAD"]);
3771
+ await execa4("git", ["reset", "HEAD"]);
3843
3772
  return false;
3844
3773
  }
3845
3774
  let message;
@@ -3861,7 +3790,7 @@ async function handleCommit(state, dashboardLines) {
3861
3790
  );
3862
3791
  }
3863
3792
  if (message === CANCEL) {
3864
- await execa5("git", ["reset", "HEAD"]);
3793
+ await execa4("git", ["reset", "HEAD"]);
3865
3794
  return false;
3866
3795
  }
3867
3796
  const confirmed = await showConfirm({
@@ -3870,7 +3799,7 @@ async function handleCommit(state, dashboardLines) {
3870
3799
  dashboardLines
3871
3800
  });
3872
3801
  if (!confirmed) {
3873
- await execa5("git", ["reset", "HEAD"]);
3802
+ await execa4("git", ["reset", "HEAD"]);
3874
3803
  return false;
3875
3804
  }
3876
3805
  const providerEmail = getProviderEmail(provider);
@@ -3878,7 +3807,7 @@ async function handleCommit(state, dashboardLines) {
3878
3807
 
3879
3808
  Co-Authored-By: ${providerName} <${providerEmail}>`;
3880
3809
  showStatus("Committing", "Committing changes...", dashboardLines);
3881
- await execa5("git", ["commit", "-m", fullMessage]);
3810
+ await execa4("git", ["commit", "-m", fullMessage]);
3882
3811
  return true;
3883
3812
  } catch (error) {
3884
3813
  logger.error(`Commit failed: ${error}`);
@@ -3958,10 +3887,10 @@ ${feedbackLines}`);
3958
3887
  }
3959
3888
  async function handlePush(dashboardLines) {
3960
3889
  try {
3961
- const { stdout: branch } = await execa5("git", ["branch", "--show-current"]);
3890
+ const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
3962
3891
  const branchName = branch.trim();
3963
3892
  showStatus("Pushing", `Pushing ${branchName} to remote...`, dashboardLines);
3964
- await execa5("git", ["push", "-u", "origin", branchName]);
3893
+ await execa4("git", ["push", "-u", "origin", branchName]);
3965
3894
  } catch (error) {
3966
3895
  logger.error(`Push failed: ${error}`);
3967
3896
  }
@@ -4223,6 +4152,11 @@ async function checkPrerequisites() {
4223
4152
  logger.warning('Repository not initialized. Run "gent init" to set up this repository.');
4224
4153
  return false;
4225
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
+ }
4226
4160
  const labelsOk = await checkLabelsExist();
4227
4161
  if (!labelsOk) {
4228
4162
  logger.warning('GitHub labels not found. Run "gent setup-labels" to create required labels.');