contribute-now 0.5.0-dev.9ad3d01 → 0.5.0-dev.fefd5d0

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.
Files changed (2) hide show
  1. package/dist/index.js +452 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/index.ts
6
- import { defineCommand as defineCommand14, runMain } from "citty";
6
+ import { defineCommand as defineCommand16, runMain } from "citty";
7
7
 
8
8
  // src/commands/branch.ts
9
9
  import { defineCommand } from "citty";
@@ -2268,7 +2268,7 @@ import pc7 from "picocolors";
2268
2268
  // package.json
2269
2269
  var package_default = {
2270
2270
  name: "contribute-now",
2271
- version: "0.5.0-dev.9ad3d01",
2271
+ version: "0.5.0-dev.fefd5d0",
2272
2272
  description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
2273
2273
  type: "module",
2274
2274
  bin: {
@@ -3090,9 +3090,167 @@ function colorizeSubject(subject) {
3090
3090
  return pc9.white(subject);
3091
3091
  }
3092
3092
 
3093
- // src/commands/setup.ts
3093
+ // src/commands/save.ts
3094
3094
  import { defineCommand as defineCommand7 } from "citty";
3095
3095
  import pc10 from "picocolors";
3096
+ import { execFile as execFileCb4 } from "node:child_process";
3097
+ function gitRun(args) {
3098
+ return new Promise((resolve) => {
3099
+ execFileCb4("git", args, (err, stdout, stderr) => {
3100
+ resolve({
3101
+ exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
3102
+ stdout: stdout ?? "",
3103
+ stderr: stderr ?? ""
3104
+ });
3105
+ });
3106
+ });
3107
+ }
3108
+ var save_default = defineCommand7({
3109
+ meta: {
3110
+ name: "save",
3111
+ description: "Save uncommitted changes for later. Use --list, --restore, or --drop to manage saves."
3112
+ },
3113
+ args: {
3114
+ list: {
3115
+ type: "boolean",
3116
+ alias: "l",
3117
+ description: "List all saved changes"
3118
+ },
3119
+ restore: {
3120
+ type: "boolean",
3121
+ alias: "r",
3122
+ description: "Restore previously saved changes"
3123
+ },
3124
+ drop: {
3125
+ type: "boolean",
3126
+ alias: "d",
3127
+ description: "Discard a specific save entry"
3128
+ },
3129
+ message: {
3130
+ type: "string",
3131
+ alias: "m",
3132
+ description: "Description for saved changes (used with default save)"
3133
+ }
3134
+ },
3135
+ async run({ args }) {
3136
+ if (!await isGitRepo()) {
3137
+ error("Not inside a git repository.");
3138
+ process.exit(1);
3139
+ }
3140
+ const flags = [args.list, args.restore, args.drop].filter(Boolean);
3141
+ if (flags.length > 1) {
3142
+ error("Please use only one flag at a time: --list, --restore, or --drop.");
3143
+ process.exit(1);
3144
+ }
3145
+ if (args.list) {
3146
+ await handleList();
3147
+ } else if (args.restore) {
3148
+ await handleRestore();
3149
+ } else if (args.drop) {
3150
+ await handleDrop();
3151
+ } else {
3152
+ await handleSave(args.message);
3153
+ }
3154
+ }
3155
+ });
3156
+ async function handleSave(message) {
3157
+ heading("\uD83D\uDCBE contrib save");
3158
+ const currentBranch = await getCurrentBranch();
3159
+ const label = message ?? `work-in-progress on ${currentBranch ?? "unknown"}`;
3160
+ const stashMsg = `contrib-save: ${label}`;
3161
+ const result = await gitRun(["stash", "push", "-m", stashMsg]);
3162
+ if (result.exitCode !== 0) {
3163
+ error(`Failed to save: ${result.stderr}`);
3164
+ process.exit(1);
3165
+ }
3166
+ if (result.stdout.includes("No local changes to save")) {
3167
+ info("No uncommitted changes to save.");
3168
+ return;
3169
+ }
3170
+ success(`Saved: ${pc10.dim(label)}`);
3171
+ info(`Use ${pc10.bold("contrib save --restore")} to bring them back.`);
3172
+ }
3173
+ async function handleRestore() {
3174
+ heading("\uD83D\uDCBE contrib save --restore");
3175
+ const stashes = await getStashList();
3176
+ if (stashes.length === 0) {
3177
+ info("No saved changes found.");
3178
+ return;
3179
+ }
3180
+ if (stashes.length === 1) {
3181
+ const result2 = await gitRun(["stash", "pop", "stash@{0}"]);
3182
+ if (result2.exitCode !== 0) {
3183
+ error(`Failed to restore: ${result2.stderr}`);
3184
+ warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
3185
+ process.exit(1);
3186
+ }
3187
+ success(`Restored: ${pc10.dim(stashes[0].message)}`);
3188
+ return;
3189
+ }
3190
+ const choices = stashes.map((s) => `${s.index} ${s.message}`);
3191
+ const selected = await selectPrompt("Which save to restore?", choices);
3192
+ const idx = selected.split(/\s{2,}/)[0].trim();
3193
+ const result = await gitRun(["stash", "pop", `stash@{${idx}}`]);
3194
+ if (result.exitCode !== 0) {
3195
+ error(`Failed to restore: ${result.stderr}`);
3196
+ warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
3197
+ process.exit(1);
3198
+ }
3199
+ const match = stashes.find((s) => String(s.index) === idx);
3200
+ success(`Restored: ${pc10.dim(match?.message ?? "saved changes")}`);
3201
+ }
3202
+ async function handleList() {
3203
+ heading("\uD83D\uDCBE contrib save --list");
3204
+ const stashes = await getStashList();
3205
+ if (stashes.length === 0) {
3206
+ info("No saved changes.");
3207
+ return;
3208
+ }
3209
+ console.log();
3210
+ for (const s of stashes) {
3211
+ const idx = pc10.dim(`[${s.index}]`);
3212
+ const msg = s.message;
3213
+ console.log(` ${idx} ${msg}`);
3214
+ }
3215
+ console.log();
3216
+ info(`Use ${pc10.bold("contrib save --restore")} to bring changes back.`);
3217
+ info(`Use ${pc10.bold("contrib save --drop")} to discard saved changes.`);
3218
+ }
3219
+ async function handleDrop() {
3220
+ heading("\uD83D\uDCBE contrib save --drop");
3221
+ const stashes = await getStashList();
3222
+ if (stashes.length === 0) {
3223
+ info("No saved changes to drop.");
3224
+ return;
3225
+ }
3226
+ const choices = stashes.map((s) => `${s.index} ${s.message}`);
3227
+ const selected = await selectPrompt("Which save to drop?", choices);
3228
+ const idx = selected.split(/\s{2,}/)[0].trim();
3229
+ const result = await gitRun(["stash", "drop", `stash@{${idx}}`]);
3230
+ if (result.exitCode !== 0) {
3231
+ error(`Failed to drop: ${result.stderr}`);
3232
+ process.exit(1);
3233
+ }
3234
+ const match = stashes.find((s) => String(s.index) === idx);
3235
+ success(`Dropped: ${pc10.dim(match?.message ?? "saved changes")}`);
3236
+ }
3237
+ async function getStashList() {
3238
+ const result = await gitRun(["stash", "list"]);
3239
+ if (result.exitCode !== 0 || !result.stdout.trim())
3240
+ return [];
3241
+ return result.stdout.trimEnd().split(`
3242
+ `).filter(Boolean).map((line) => {
3243
+ const idxMatch = line.match(/^stash@\{(\d+)\}/);
3244
+ const index = idxMatch ? Number.parseInt(idxMatch[1], 10) : 0;
3245
+ const parts = line.split(": ");
3246
+ const message = parts.length > 2 ? parts.slice(2).join(": ") : parts[parts.length - 1];
3247
+ return { index, message };
3248
+ });
3249
+ }
3250
+
3251
+ // src/commands/setup.ts
3252
+ import { defineCommand as defineCommand8 } from "citty";
3253
+ import pc11 from "picocolors";
3096
3254
  async function shouldContinueSetupWithExistingConfig(options) {
3097
3255
  const {
3098
3256
  existingConfig,
@@ -3130,7 +3288,7 @@ async function shouldContinueSetupWithExistingConfig(options) {
3130
3288
  }
3131
3289
  return true;
3132
3290
  }
3133
- var setup_default = defineCommand7({
3291
+ var setup_default = defineCommand8({
3134
3292
  meta: {
3135
3293
  name: "setup",
3136
3294
  description: "Initialize contribute-now config for this repo (.contributerc.json)"
@@ -3165,7 +3323,7 @@ var setup_default = defineCommand7({
3165
3323
  workflow = "github-flow";
3166
3324
  else if (workflowChoice.startsWith("Git Flow"))
3167
3325
  workflow = "git-flow";
3168
- info(`Workflow: ${pc10.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
3326
+ info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
3169
3327
  const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
3170
3328
  `${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
3171
3329
  CONVENTION_DESCRIPTIONS.conventional,
@@ -3229,15 +3387,15 @@ var setup_default = defineCommand7({
3229
3387
  detectedRole = roleChoice;
3230
3388
  detectionSource = "user selection";
3231
3389
  } else {
3232
- info(`Detected role: ${pc10.bold(detectedRole)} (via ${detectionSource})`);
3233
- const confirmed = await confirmPrompt(`Role detected as ${pc10.bold(detectedRole)}. Is this correct?`);
3390
+ info(`Detected role: ${pc11.bold(detectedRole)} (via ${detectionSource})`);
3391
+ const confirmed = await confirmPrompt(`Role detected as ${pc11.bold(detectedRole)}. Is this correct?`);
3234
3392
  if (!confirmed) {
3235
3393
  const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
3236
3394
  detectedRole = roleChoice;
3237
3395
  }
3238
3396
  }
3239
3397
  const defaultConfig = getDefaultConfig();
3240
- info(pc10.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
3398
+ info(pc11.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
3241
3399
  const mainBranchDefault = defaultConfig.mainBranch;
3242
3400
  const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} — press Enter to keep)`, mainBranchDefault);
3243
3401
  let devBranch;
@@ -3263,7 +3421,7 @@ var setup_default = defineCommand7({
3263
3421
  error("Setup cannot continue without the upstream remote for contributors.");
3264
3422
  process.exit(1);
3265
3423
  }
3266
- success(`Added remote ${pc10.bold(upstreamRemote)} → ${upstreamUrl}`);
3424
+ success(`Added remote ${pc11.bold(upstreamRemote)} → ${upstreamUrl}`);
3267
3425
  } else {
3268
3426
  error("An upstream remote URL is required for contributors.");
3269
3427
  info("Add it manually: git remote add upstream <url>");
@@ -3284,17 +3442,17 @@ var setup_default = defineCommand7({
3284
3442
  writeConfig(config);
3285
3443
  success(`✅ Config written to .contributerc.json`);
3286
3444
  const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
3287
- info(`Fetching ${pc10.bold(syncRemote)} to verify branch configuration...`);
3445
+ info(`Fetching ${pc11.bold(syncRemote)} to verify branch configuration...`);
3288
3446
  await fetchRemote(syncRemote);
3289
3447
  const mainRef = `${syncRemote}/${config.mainBranch}`;
3290
3448
  if (!await refExists(mainRef)) {
3291
- warn(`Main branch ref ${pc10.bold(mainRef)} not found on remote.`);
3449
+ warn(`Main branch ref ${pc11.bold(mainRef)} not found on remote.`);
3292
3450
  warn("Config was saved — verify the branch name and re-run setup if needed.");
3293
3451
  }
3294
3452
  if (config.devBranch) {
3295
3453
  const devRef = `${syncRemote}/${config.devBranch}`;
3296
3454
  if (!await refExists(devRef)) {
3297
- warn(`Dev branch ref ${pc10.bold(devRef)} not found on remote.`);
3455
+ warn(`Dev branch ref ${pc11.bold(devRef)} not found on remote.`);
3298
3456
  warn("Config was saved — verify the branch name and re-run setup if needed.");
3299
3457
  }
3300
3458
  }
@@ -3302,33 +3460,33 @@ var setup_default = defineCommand7({
3302
3460
  info("Added .contributerc.json to .gitignore to avoid committing personal config.");
3303
3461
  }
3304
3462
  console.log();
3305
- info(`Workflow: ${pc10.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3306
- info(`Convention: ${pc10.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
3307
- info(`Role: ${pc10.bold(config.role)}`);
3463
+ info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3464
+ info(`Convention: ${pc11.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
3465
+ info(`Role: ${pc11.bold(config.role)}`);
3308
3466
  if (config.devBranch) {
3309
- info(`Main: ${pc10.bold(config.mainBranch)} | Dev: ${pc10.bold(config.devBranch)}`);
3467
+ info(`Main: ${pc11.bold(config.mainBranch)} | Dev: ${pc11.bold(config.devBranch)}`);
3310
3468
  } else {
3311
- info(`Main: ${pc10.bold(config.mainBranch)}`);
3469
+ info(`Main: ${pc11.bold(config.mainBranch)}`);
3312
3470
  }
3313
- info(`Origin: ${pc10.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc10.bold(config.upstream)}` : ""}`);
3471
+ info(`Origin: ${pc11.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc11.bold(config.upstream)}` : ""}`);
3314
3472
  }
3315
3473
  });
3316
3474
  function logConfigSummary(config) {
3317
- info(`Workflow: ${pc10.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3318
- info(`Convention: ${pc10.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
3319
- info(`Role: ${pc10.bold(config.role)}`);
3475
+ info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3476
+ info(`Convention: ${pc11.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
3477
+ info(`Role: ${pc11.bold(config.role)}`);
3320
3478
  if (config.devBranch) {
3321
- info(`Main: ${pc10.bold(config.mainBranch)} | Dev: ${pc10.bold(config.devBranch)}`);
3479
+ info(`Main: ${pc11.bold(config.mainBranch)} | Dev: ${pc11.bold(config.devBranch)}`);
3322
3480
  } else {
3323
- info(`Main: ${pc10.bold(config.mainBranch)}`);
3481
+ info(`Main: ${pc11.bold(config.mainBranch)}`);
3324
3482
  }
3325
- info(`Origin: ${pc10.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc10.bold(config.upstream)}` : ""}`);
3483
+ info(`Origin: ${pc11.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc11.bold(config.upstream)}` : ""}`);
3326
3484
  }
3327
3485
 
3328
3486
  // src/commands/start.ts
3329
- import { defineCommand as defineCommand8 } from "citty";
3330
- import pc11 from "picocolors";
3331
- var start_default = defineCommand8({
3487
+ import { defineCommand as defineCommand9 } from "citty";
3488
+ import pc12 from "picocolors";
3489
+ var start_default = defineCommand9({
3332
3490
  meta: {
3333
3491
  name: "start",
3334
3492
  description: "Create a new feature branch from the latest base branch"
@@ -3384,8 +3542,8 @@ var start_default = defineCommand8({
3384
3542
  if (suggested) {
3385
3543
  spinner.success("Branch name suggestion ready.");
3386
3544
  console.log(`
3387
- ${pc11.dim("AI suggestion:")} ${pc11.bold(pc11.cyan(suggested))}`);
3388
- const accepted = await confirmPrompt(`Use ${pc11.bold(suggested)} as your branch name?`);
3545
+ ${pc12.dim("AI suggestion:")} ${pc12.bold(pc12.cyan(suggested))}`);
3546
+ const accepted = await confirmPrompt(`Use ${pc12.bold(suggested)} as your branch name?`);
3389
3547
  if (accepted) {
3390
3548
  branchName = suggested;
3391
3549
  } else {
@@ -3396,28 +3554,28 @@ var start_default = defineCommand8({
3396
3554
  }
3397
3555
  }
3398
3556
  if (!hasPrefix(branchName, branchPrefixes)) {
3399
- const prefix = await selectPrompt(`Choose a branch type for ${pc11.bold(branchName)}:`, branchPrefixes);
3557
+ const prefix = await selectPrompt(`Choose a branch type for ${pc12.bold(branchName)}:`, branchPrefixes);
3400
3558
  branchName = formatBranchName(prefix, branchName);
3401
3559
  }
3402
3560
  if (!isValidBranchName(branchName)) {
3403
3561
  error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
3404
3562
  process.exit(1);
3405
3563
  }
3406
- info(`Creating branch: ${pc11.bold(branchName)}`);
3564
+ info(`Creating branch: ${pc12.bold(branchName)}`);
3407
3565
  if (await branchExists(branchName)) {
3408
- error(`Branch ${pc11.bold(branchName)} already exists.`);
3409
- info(` Use ${pc11.bold(`git checkout ${branchName}`)} to switch to it, or choose a different name.`);
3566
+ error(`Branch ${pc12.bold(branchName)} already exists.`);
3567
+ info(` Use ${pc12.bold(`git checkout ${branchName}`)} to switch to it, or choose a different name.`);
3410
3568
  process.exit(1);
3411
3569
  }
3412
3570
  await fetchRemote(syncSource.remote);
3413
3571
  if (!await refExists(syncSource.ref)) {
3414
- warn(`Remote ref ${pc11.bold(syncSource.ref)} not found. Creating branch from local ${pc11.bold(baseBranch)}.`);
3572
+ warn(`Remote ref ${pc12.bold(syncSource.ref)} not found. Creating branch from local ${pc12.bold(baseBranch)}.`);
3415
3573
  }
3416
3574
  const currentBranch = await getCurrentBranch();
3417
3575
  if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
3418
3576
  const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
3419
3577
  if (ahead > 0) {
3420
- warn(`You are on ${pc11.bold(baseBranch)} with ${pc11.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${pc11.bold(syncSource.ref)}.`);
3578
+ warn(`You are on ${pc12.bold(baseBranch)} with ${pc12.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${pc12.bold(syncSource.ref)}.`);
3421
3579
  info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
3422
3580
  const proceed = await confirmPrompt("Discard local commits and sync to remote?");
3423
3581
  if (!proceed) {
@@ -3434,10 +3592,10 @@ var start_default = defineCommand8({
3434
3592
  error(`Failed to create branch: ${result2.stderr}`);
3435
3593
  process.exit(1);
3436
3594
  }
3437
- success(`✅ Created ${pc11.bold(branchName)} from ${pc11.bold(syncSource.ref)}`);
3595
+ success(`✅ Created ${pc12.bold(branchName)} from ${pc12.bold(syncSource.ref)}`);
3438
3596
  return;
3439
3597
  }
3440
- error(`Failed to update ${pc11.bold(baseBranch)}: ${updateResult.stderr}`);
3598
+ error(`Failed to update ${pc12.bold(baseBranch)}: ${updateResult.stderr}`);
3441
3599
  info("Make sure your base branch exists locally or the remote ref is available.");
3442
3600
  process.exit(1);
3443
3601
  }
@@ -3446,14 +3604,14 @@ var start_default = defineCommand8({
3446
3604
  error(`Failed to create branch: ${result.stderr}`);
3447
3605
  process.exit(1);
3448
3606
  }
3449
- success(`✅ Created ${pc11.bold(branchName)} from latest ${pc11.bold(baseBranch)}`);
3607
+ success(`✅ Created ${pc12.bold(branchName)} from latest ${pc12.bold(baseBranch)}`);
3450
3608
  }
3451
3609
  });
3452
3610
 
3453
3611
  // src/commands/status.ts
3454
- import { defineCommand as defineCommand9 } from "citty";
3455
- import pc12 from "picocolors";
3456
- var status_default = defineCommand9({
3612
+ import { defineCommand as defineCommand10 } from "citty";
3613
+ import pc13 from "picocolors";
3614
+ var status_default = defineCommand10({
3457
3615
  meta: {
3458
3616
  name: "status",
3459
3617
  description: "Show sync status of branches"
@@ -3469,8 +3627,8 @@ var status_default = defineCommand9({
3469
3627
  process.exit(1);
3470
3628
  }
3471
3629
  heading("\uD83D\uDCCA contribute-now status");
3472
- console.log(` ${pc12.dim("Workflow:")} ${pc12.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3473
- console.log(` ${pc12.dim("Role:")} ${pc12.bold(config.role)}`);
3630
+ console.log(` ${pc13.dim("Workflow:")} ${pc13.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
3631
+ console.log(` ${pc13.dim("Role:")} ${pc13.bold(config.role)}`);
3474
3632
  console.log();
3475
3633
  await fetchAll();
3476
3634
  const currentBranch = await getCurrentBranch();
@@ -3479,7 +3637,7 @@ var status_default = defineCommand9({
3479
3637
  const isContributor = config.role === "contributor";
3480
3638
  const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
3481
3639
  if (dirty) {
3482
- console.log(` ${pc12.yellow("⚠")} ${pc12.yellow("Uncommitted changes in working tree")}`);
3640
+ console.log(` ${pc13.yellow("⚠")} ${pc13.yellow("Uncommitted changes in working tree")}`);
3483
3641
  console.log();
3484
3642
  }
3485
3643
  const mainRemote = `${origin}/${mainBranch}`;
@@ -3498,82 +3656,82 @@ var status_default = defineCommand9({
3498
3656
  if (isFeatureBranch) {
3499
3657
  const branchDiv = await getDivergence(currentBranch, baseBranch);
3500
3658
  const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
3501
- console.log(branchLine + pc12.dim(` (current ${pc12.green("*")})`));
3659
+ console.log(branchLine + pc13.dim(` (current ${pc13.green("*")})`));
3502
3660
  branchStatus = await detectBranchStatus(currentBranch, baseBranch);
3503
3661
  if (branchStatus.merged) {
3504
- console.log(` ${pc12.green("✓")} ${pc12.green("Branch merged")} — ${pc12.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
3662
+ console.log(` ${pc13.green("✓")} ${pc13.green("Branch merged")} — ${pc13.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
3505
3663
  }
3506
3664
  if (branchStatus.stale) {
3507
- console.log(` ${pc12.yellow("⏳")} ${pc12.yellow("Branch is stale")} — ${pc12.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
3665
+ console.log(` ${pc13.yellow("⏳")} ${pc13.yellow("Branch is stale")} — ${pc13.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
3508
3666
  }
3509
3667
  } else if (currentBranch) {
3510
- console.log(pc12.dim(` (on ${pc12.bold(currentBranch)} branch)`));
3668
+ console.log(pc13.dim(` (on ${pc13.bold(currentBranch)} branch)`));
3511
3669
  }
3512
3670
  const hasFiles = fileStatus.staged.length > 0 || fileStatus.modified.length > 0 || fileStatus.untracked.length > 0;
3513
3671
  if (hasFiles) {
3514
3672
  console.log();
3515
3673
  if (fileStatus.staged.length > 0) {
3516
- console.log(` ${pc12.green("Staged for commit:")}`);
3674
+ console.log(` ${pc13.green("Staged for commit:")}`);
3517
3675
  for (const { file, status } of fileStatus.staged) {
3518
- console.log(` ${pc12.green("+")} ${pc12.dim(`${status}:`)} ${file}`);
3676
+ console.log(` ${pc13.green("+")} ${pc13.dim(`${status}:`)} ${file}`);
3519
3677
  }
3520
3678
  }
3521
3679
  if (fileStatus.modified.length > 0) {
3522
- console.log(` ${pc12.yellow("Unstaged changes:")}`);
3680
+ console.log(` ${pc13.yellow("Unstaged changes:")}`);
3523
3681
  for (const { file, status } of fileStatus.modified) {
3524
- console.log(` ${pc12.yellow("~")} ${pc12.dim(`${status}:`)} ${file}`);
3682
+ console.log(` ${pc13.yellow("~")} ${pc13.dim(`${status}:`)} ${file}`);
3525
3683
  }
3526
3684
  }
3527
3685
  if (fileStatus.untracked.length > 0) {
3528
- console.log(` ${pc12.red("Untracked files:")}`);
3686
+ console.log(` ${pc13.red("Untracked files:")}`);
3529
3687
  for (const file of fileStatus.untracked) {
3530
- console.log(` ${pc12.red("?")} ${file}`);
3688
+ console.log(` ${pc13.red("?")} ${file}`);
3531
3689
  }
3532
3690
  }
3533
3691
  } else if (!dirty) {
3534
- console.log(` ${pc12.green("✓")} ${pc12.dim("Working tree clean")}`);
3692
+ console.log(` ${pc13.green("✓")} ${pc13.dim("Working tree clean")}`);
3535
3693
  }
3536
3694
  const tips = [];
3537
3695
  if (fileStatus.staged.length > 0) {
3538
- tips.push(`Run ${pc12.bold("contrib commit")} to commit staged changes`);
3696
+ tips.push(`Run ${pc13.bold("contrib commit")} to commit staged changes`);
3539
3697
  }
3540
3698
  if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
3541
- tips.push(`Run ${pc12.bold("contrib commit")} to stage and commit changes`);
3699
+ tips.push(`Run ${pc13.bold("contrib commit")} to stage and commit changes`);
3542
3700
  }
3543
3701
  if (isFeatureBranch && branchStatus) {
3544
3702
  if (branchStatus.merged) {
3545
- tips.push(`Run ${pc12.bold("contrib clean")} to delete this merged branch`);
3703
+ tips.push(`Run ${pc13.bold("contrib clean")} to delete this merged branch`);
3546
3704
  } else if (branchStatus.stale) {
3547
- tips.push(`Run ${pc12.bold("contrib sync")} to rebase on latest changes, or ${pc12.bold("contrib clean")} if no longer needed`);
3705
+ tips.push(`Run ${pc13.bold("contrib sync")} to rebase on latest changes, or ${pc13.bold("contrib clean")} if no longer needed`);
3548
3706
  } else if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0) {
3549
3707
  const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
3550
3708
  if (branchDiv.ahead > 0) {
3551
- tips.push(`Run ${pc12.bold("contrib submit")} to push and create/update your PR`);
3709
+ tips.push(`Run ${pc13.bold("contrib submit")} to push and create/update your PR`);
3552
3710
  }
3553
3711
  }
3554
3712
  }
3555
3713
  if (tips.length > 0) {
3556
3714
  console.log();
3557
- console.log(` ${pc12.dim("\uD83D\uDCA1 Tip:")}`);
3715
+ console.log(` ${pc13.dim("\uD83D\uDCA1 Tip:")}`);
3558
3716
  for (const tip of tips) {
3559
- console.log(` ${pc12.dim(tip)}`);
3717
+ console.log(` ${pc13.dim(tip)}`);
3560
3718
  }
3561
3719
  }
3562
3720
  console.log();
3563
3721
  }
3564
3722
  });
3565
3723
  function formatStatus(branch, base, ahead, behind) {
3566
- const label = pc12.bold(branch.padEnd(20));
3724
+ const label = pc13.bold(branch.padEnd(20));
3567
3725
  if (ahead === 0 && behind === 0) {
3568
- return ` ${pc12.green("✓")} ${label} ${pc12.dim(`in sync with ${base}`)}`;
3726
+ return ` ${pc13.green("✓")} ${label} ${pc13.dim(`in sync with ${base}`)}`;
3569
3727
  }
3570
3728
  if (ahead > 0 && behind === 0) {
3571
- return ` ${pc12.yellow("↑")} ${label} ${pc12.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
3729
+ return ` ${pc13.yellow("↑")} ${label} ${pc13.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
3572
3730
  }
3573
3731
  if (behind > 0 && ahead === 0) {
3574
- return ` ${pc12.red("↓")} ${label} ${pc12.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
3732
+ return ` ${pc13.red("↓")} ${label} ${pc13.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
3575
3733
  }
3576
- return ` ${pc12.red("⚡")} ${label} ${pc12.yellow(`${ahead} ahead`)}${pc12.dim(", ")}${pc12.red(`${behind} behind`)} ${pc12.dim(base)}`;
3734
+ return ` ${pc13.red("⚡")} ${label} ${pc13.yellow(`${ahead} ahead`)}${pc13.dim(", ")}${pc13.red(`${behind} behind`)} ${pc13.dim(base)}`;
3577
3735
  }
3578
3736
  var STALE_THRESHOLD_DAYS = 14;
3579
3737
  async function detectBranchStatus(branch, baseBranch) {
@@ -3619,16 +3777,16 @@ async function detectBranchStatus(branch, baseBranch) {
3619
3777
  }
3620
3778
 
3621
3779
  // src/commands/submit.ts
3622
- import { defineCommand as defineCommand10 } from "citty";
3623
- import pc13 from "picocolors";
3780
+ import { defineCommand as defineCommand11 } from "citty";
3781
+ import pc14 from "picocolors";
3624
3782
  async function performSquashMerge(origin, baseBranch, featureBranch, options) {
3625
- info(`Checking out ${pc13.bold(baseBranch)}...`);
3783
+ info(`Checking out ${pc14.bold(baseBranch)}...`);
3626
3784
  const coResult = await checkoutBranch(baseBranch);
3627
3785
  if (coResult.exitCode !== 0) {
3628
3786
  error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
3629
3787
  process.exit(1);
3630
3788
  }
3631
- info(`Squash merging ${pc13.bold(featureBranch)} into ${pc13.bold(baseBranch)}...`);
3789
+ info(`Squash merging ${pc14.bold(featureBranch)} into ${pc14.bold(baseBranch)}...`);
3632
3790
  const mergeResult = await mergeSquash(featureBranch);
3633
3791
  if (mergeResult.exitCode !== 0) {
3634
3792
  error(`Squash merge failed: ${mergeResult.stderr}`);
@@ -3645,7 +3803,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
3645
3803
  message = aiMsg;
3646
3804
  spinner.success("AI commit message generated.");
3647
3805
  console.log(`
3648
- ${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(message))}`);
3806
+ ${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(message))}`);
3649
3807
  } else {
3650
3808
  spinner.fail("AI did not return a commit message.");
3651
3809
  }
@@ -3674,7 +3832,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
3674
3832
  message = regen;
3675
3833
  spinner.success("Commit message regenerated.");
3676
3834
  console.log(`
3677
- ${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(regen))}`);
3835
+ ${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(regen))}`);
3678
3836
  } else {
3679
3837
  spinner.fail("Regeneration failed.");
3680
3838
  finalMsg = await inputPrompt("Enter commit message");
@@ -3691,13 +3849,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
3691
3849
  error(`Commit failed: ${commitResult.stderr}`);
3692
3850
  process.exit(1);
3693
3851
  }
3694
- info(`Pushing ${pc13.bold(baseBranch)} to ${origin}...`);
3852
+ info(`Pushing ${pc14.bold(baseBranch)} to ${origin}...`);
3695
3853
  const pushResult = await pushBranch(origin, baseBranch);
3696
3854
  if (pushResult.exitCode !== 0) {
3697
3855
  error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
3698
3856
  process.exit(1);
3699
3857
  }
3700
- info(`Deleting local branch ${pc13.bold(featureBranch)}...`);
3858
+ info(`Deleting local branch ${pc14.bold(featureBranch)}...`);
3701
3859
  const delLocal = await forceDeleteBranch(featureBranch);
3702
3860
  if (delLocal.exitCode !== 0) {
3703
3861
  warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
@@ -3705,16 +3863,16 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
3705
3863
  const remoteBranchRef = `${origin}/${featureBranch}`;
3706
3864
  const remoteExists = await branchExists(remoteBranchRef);
3707
3865
  if (remoteExists) {
3708
- info(`Deleting remote branch ${pc13.bold(featureBranch)}...`);
3866
+ info(`Deleting remote branch ${pc14.bold(featureBranch)}...`);
3709
3867
  const delRemote = await deleteRemoteBranch(origin, featureBranch);
3710
3868
  if (delRemote.exitCode !== 0) {
3711
3869
  warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
3712
3870
  }
3713
3871
  }
3714
- success(`✅ Squash merged ${pc13.bold(featureBranch)} into ${pc13.bold(baseBranch)} and pushed.`);
3715
- info(`Run ${pc13.bold("contrib start")} to begin a new feature.`);
3872
+ success(`✅ Squash merged ${pc14.bold(featureBranch)} into ${pc14.bold(baseBranch)} and pushed.`);
3873
+ info(`Run ${pc14.bold("contrib start")} to begin a new feature.`);
3716
3874
  }
3717
- var submit_default = defineCommand10({
3875
+ var submit_default = defineCommand11({
3718
3876
  meta: {
3719
3877
  name: "submit",
3720
3878
  description: "Push current branch and create a pull request"
@@ -3756,7 +3914,7 @@ var submit_default = defineCommand10({
3756
3914
  }
3757
3915
  if (protectedBranches.includes(currentBranch)) {
3758
3916
  heading("\uD83D\uDE80 contrib submit");
3759
- warn(`You're on ${pc13.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
3917
+ warn(`You're on ${pc14.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
3760
3918
  await fetchAll();
3761
3919
  const remoteRef = `${origin}/${currentBranch}`;
3762
3920
  const localWork = await hasLocalWork(origin, currentBranch);
@@ -3765,11 +3923,11 @@ var submit_default = defineCommand10({
3765
3923
  const hasAnything = hasCommits || dirty;
3766
3924
  if (!hasAnything) {
3767
3925
  error("No local changes or commits to move. Switch to a feature branch first.");
3768
- info(` Run ${pc13.bold("contrib start")} to create a new feature branch.`);
3926
+ info(` Run ${pc14.bold("contrib start")} to create a new feature branch.`);
3769
3927
  process.exit(1);
3770
3928
  }
3771
3929
  if (hasCommits) {
3772
- info(`Found ${pc13.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc13.bold(currentBranch)}.`);
3930
+ info(`Found ${pc14.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc14.bold(currentBranch)}.`);
3773
3931
  }
3774
3932
  if (dirty) {
3775
3933
  info("You also have uncommitted changes in the working tree.");
@@ -3785,7 +3943,7 @@ var submit_default = defineCommand10({
3785
3943
  info("No changes made. You are still on your current branch.");
3786
3944
  return;
3787
3945
  }
3788
- info(pc13.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
3946
+ info(pc14.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
3789
3947
  const description = await inputPrompt("What are you going to work on?");
3790
3948
  let newBranchName = description;
3791
3949
  if (looksLikeNaturalLanguage(description)) {
@@ -3796,8 +3954,8 @@ var submit_default = defineCommand10({
3796
3954
  if (suggested) {
3797
3955
  spinner.success("Branch name suggestion ready.");
3798
3956
  console.log(`
3799
- ${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(suggested))}`);
3800
- const accepted = await confirmPrompt(`Use ${pc13.bold(suggested)} as your branch name?`);
3957
+ ${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(suggested))}`);
3958
+ const accepted = await confirmPrompt(`Use ${pc14.bold(suggested)} as your branch name?`);
3801
3959
  newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
3802
3960
  } else {
3803
3961
  spinner.fail("AI did not return a suggestion.");
@@ -3806,7 +3964,7 @@ var submit_default = defineCommand10({
3806
3964
  }
3807
3965
  }
3808
3966
  if (!hasPrefix(newBranchName, config.branchPrefixes)) {
3809
- const prefix = await selectPrompt(`Choose a branch type for ${pc13.bold(newBranchName)}:`, config.branchPrefixes);
3967
+ const prefix = await selectPrompt(`Choose a branch type for ${pc14.bold(newBranchName)}:`, config.branchPrefixes);
3810
3968
  newBranchName = formatBranchName(prefix, newBranchName);
3811
3969
  }
3812
3970
  if (!isValidBranchName(newBranchName)) {
@@ -3814,7 +3972,7 @@ var submit_default = defineCommand10({
3814
3972
  process.exit(1);
3815
3973
  }
3816
3974
  if (await branchExists(newBranchName)) {
3817
- error(`Branch ${pc13.bold(newBranchName)} already exists. Choose a different name.`);
3975
+ error(`Branch ${pc14.bold(newBranchName)} already exists. Choose a different name.`);
3818
3976
  process.exit(1);
3819
3977
  }
3820
3978
  const branchResult = await createBranch(newBranchName);
@@ -3822,12 +3980,12 @@ var submit_default = defineCommand10({
3822
3980
  error(`Failed to create branch: ${branchResult.stderr}`);
3823
3981
  process.exit(1);
3824
3982
  }
3825
- success(`Created ${pc13.bold(newBranchName)} with your changes.`);
3983
+ success(`Created ${pc14.bold(newBranchName)} with your changes.`);
3826
3984
  await updateLocalBranch(currentBranch, remoteRef);
3827
- info(`Reset ${pc13.bold(currentBranch)} back to ${pc13.bold(remoteRef)} — no damage done.`);
3985
+ info(`Reset ${pc14.bold(currentBranch)} back to ${pc14.bold(remoteRef)} — no damage done.`);
3828
3986
  console.log();
3829
- success(`You're now on ${pc13.bold(newBranchName)} with all your work intact.`);
3830
- info(`Run ${pc13.bold("contrib submit")} again to push and create your PR.`);
3987
+ success(`You're now on ${pc14.bold(newBranchName)} with all your work intact.`);
3988
+ info(`Run ${pc14.bold("contrib submit")} again to push and create your PR.`);
3831
3989
  return;
3832
3990
  }
3833
3991
  heading("\uD83D\uDE80 contrib submit");
@@ -3836,7 +3994,7 @@ var submit_default = defineCommand10({
3836
3994
  if (ghInstalled && ghAuthed) {
3837
3995
  const mergedPR = await getMergedPRForBranch(currentBranch);
3838
3996
  if (mergedPR) {
3839
- warn(`PR #${mergedPR.number} (${pc13.bold(mergedPR.title)}) was already merged.`);
3997
+ warn(`PR #${mergedPR.number} (${pc14.bold(mergedPR.title)}) was already merged.`);
3840
3998
  const localWork = await hasLocalWork(origin, currentBranch);
3841
3999
  const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
3842
4000
  if (hasWork) {
@@ -3844,7 +4002,7 @@ var submit_default = defineCommand10({
3844
4002
  warn("You have uncommitted changes in your working tree.");
3845
4003
  }
3846
4004
  if (localWork.unpushedCommits > 0) {
3847
- warn(`You have ${pc13.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
4005
+ warn(`You have ${pc14.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
3848
4006
  }
3849
4007
  const SAVE_NEW_BRANCH = "Save changes to a new branch";
3850
4008
  const DISCARD = "Discard all changes and clean up";
@@ -3855,7 +4013,7 @@ var submit_default = defineCommand10({
3855
4013
  return;
3856
4014
  }
3857
4015
  if (action === SAVE_NEW_BRANCH) {
3858
- info(pc13.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4016
+ info(pc14.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
3859
4017
  const description = await inputPrompt("What are you going to work on?");
3860
4018
  let newBranchName = description;
3861
4019
  if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
@@ -3864,8 +4022,8 @@ var submit_default = defineCommand10({
3864
4022
  if (suggested) {
3865
4023
  spinner.success("Branch name suggestion ready.");
3866
4024
  console.log(`
3867
- ${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(suggested))}`);
3868
- const accepted = await confirmPrompt(`Use ${pc13.bold(suggested)} as your branch name?`);
4025
+ ${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(suggested))}`);
4026
+ const accepted = await confirmPrompt(`Use ${pc14.bold(suggested)} as your branch name?`);
3869
4027
  newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
3870
4028
  } else {
3871
4029
  spinner.fail("AI did not return a suggestion.");
@@ -3873,7 +4031,7 @@ var submit_default = defineCommand10({
3873
4031
  }
3874
4032
  }
3875
4033
  if (!hasPrefix(newBranchName, config.branchPrefixes)) {
3876
- const prefix = await selectPrompt(`Choose a branch type for ${pc13.bold(newBranchName)}:`, config.branchPrefixes);
4034
+ const prefix = await selectPrompt(`Choose a branch type for ${pc14.bold(newBranchName)}:`, config.branchPrefixes);
3877
4035
  newBranchName = formatBranchName(prefix, newBranchName);
3878
4036
  }
3879
4037
  if (!isValidBranchName(newBranchName)) {
@@ -3883,7 +4041,7 @@ var submit_default = defineCommand10({
3883
4041
  const staleUpstream = await getUpstreamRef();
3884
4042
  const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
3885
4043
  if (await branchExists(newBranchName)) {
3886
- error(`Branch ${pc13.bold(newBranchName)} already exists. Choose a different name.`);
4044
+ error(`Branch ${pc14.bold(newBranchName)} already exists. Choose a different name.`);
3887
4045
  process.exit(1);
3888
4046
  }
3889
4047
  const renameResult = await renameBranch(currentBranch, newBranchName);
@@ -3891,10 +4049,10 @@ var submit_default = defineCommand10({
3891
4049
  error(`Failed to rename branch: ${renameResult.stderr}`);
3892
4050
  process.exit(1);
3893
4051
  }
3894
- success(`Renamed ${pc13.bold(currentBranch)} → ${pc13.bold(newBranchName)}`);
4052
+ success(`Renamed ${pc14.bold(currentBranch)} → ${pc14.bold(newBranchName)}`);
3895
4053
  await unsetUpstream();
3896
4054
  const syncSource2 = getSyncSource(config);
3897
- info(`Syncing ${pc13.bold(newBranchName)} with latest ${pc13.bold(baseBranch)}...`);
4055
+ info(`Syncing ${pc14.bold(newBranchName)} with latest ${pc14.bold(baseBranch)}...`);
3898
4056
  await fetchRemote(syncSource2.remote);
3899
4057
  let rebaseResult;
3900
4058
  if (staleUpstreamHash) {
@@ -3905,17 +4063,17 @@ var submit_default = defineCommand10({
3905
4063
  }
3906
4064
  if (rebaseResult.exitCode !== 0) {
3907
4065
  warn("Rebase encountered conflicts. Resolve them manually, then run:");
3908
- info(` ${pc13.bold("git rebase --continue")}`);
4066
+ info(` ${pc14.bold("git rebase --continue")}`);
3909
4067
  } else {
3910
- success(`Rebased ${pc13.bold(newBranchName)} onto ${pc13.bold(syncSource2.ref)}.`);
4068
+ success(`Rebased ${pc14.bold(newBranchName)} onto ${pc14.bold(syncSource2.ref)}.`);
3911
4069
  }
3912
- info(`All your changes are preserved. Run ${pc13.bold("contrib submit")} when ready to create a new PR.`);
4070
+ info(`All your changes are preserved. Run ${pc14.bold("contrib submit")} when ready to create a new PR.`);
3913
4071
  return;
3914
4072
  }
3915
4073
  warn("Discarding local changes...");
3916
4074
  }
3917
4075
  const syncSource = getSyncSource(config);
3918
- info(`Switching to ${pc13.bold(baseBranch)} and syncing...`);
4076
+ info(`Switching to ${pc14.bold(baseBranch)} and syncing...`);
3919
4077
  await fetchRemote(syncSource.remote);
3920
4078
  await resetHard("HEAD");
3921
4079
  const coResult = await checkoutBranch(baseBranch);
@@ -3924,23 +4082,23 @@ var submit_default = defineCommand10({
3924
4082
  process.exit(1);
3925
4083
  }
3926
4084
  await updateLocalBranch(baseBranch, syncSource.ref);
3927
- success(`Synced ${pc13.bold(baseBranch)} with ${pc13.bold(syncSource.ref)}.`);
3928
- info(`Deleting stale branch ${pc13.bold(currentBranch)}...`);
4085
+ success(`Synced ${pc14.bold(baseBranch)} with ${pc14.bold(syncSource.ref)}.`);
4086
+ info(`Deleting stale branch ${pc14.bold(currentBranch)}...`);
3929
4087
  const delResult = await forceDeleteBranch(currentBranch);
3930
4088
  if (delResult.exitCode === 0) {
3931
- success(`Deleted ${pc13.bold(currentBranch)}.`);
4089
+ success(`Deleted ${pc14.bold(currentBranch)}.`);
3932
4090
  } else {
3933
4091
  warn(`Could not delete branch: ${delResult.stderr.trim()}`);
3934
4092
  }
3935
4093
  console.log();
3936
- info(`You're now on ${pc13.bold(baseBranch)}. Run ${pc13.bold("contrib start")} to begin a new feature.`);
4094
+ info(`You're now on ${pc14.bold(baseBranch)}. Run ${pc14.bold("contrib start")} to begin a new feature.`);
3937
4095
  return;
3938
4096
  }
3939
4097
  }
3940
4098
  if (ghInstalled && ghAuthed) {
3941
4099
  const existingPR = await getPRForBranch(currentBranch);
3942
4100
  if (existingPR) {
3943
- info(`Pushing ${pc13.bold(currentBranch)} to ${origin}...`);
4101
+ info(`Pushing ${pc14.bold(currentBranch)} to ${origin}...`);
3944
4102
  const pushResult2 = await pushSetUpstream(origin, currentBranch);
3945
4103
  if (pushResult2.exitCode !== 0) {
3946
4104
  error(`Failed to push: ${pushResult2.stderr}`);
@@ -3951,8 +4109,8 @@ var submit_default = defineCommand10({
3951
4109
  }
3952
4110
  process.exit(1);
3953
4111
  }
3954
- success(`Pushed changes to existing PR #${existingPR.number}: ${pc13.bold(existingPR.title)}`);
3955
- console.log(` ${pc13.cyan(existingPR.url)}`);
4112
+ success(`Pushed changes to existing PR #${existingPR.number}: ${pc14.bold(existingPR.title)}`);
4113
+ console.log(` ${pc14.cyan(existingPR.url)}`);
3956
4114
  return;
3957
4115
  }
3958
4116
  }
@@ -3972,10 +4130,10 @@ var submit_default = defineCommand10({
3972
4130
  prBody = result.body;
3973
4131
  spinner.success("PR description generated.");
3974
4132
  console.log(`
3975
- ${pc13.dim("AI title:")} ${pc13.bold(pc13.cyan(prTitle))}`);
4133
+ ${pc14.dim("AI title:")} ${pc14.bold(pc14.cyan(prTitle))}`);
3976
4134
  console.log(`
3977
- ${pc13.dim("AI body preview:")}`);
3978
- console.log(pc13.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
4135
+ ${pc14.dim("AI body preview:")}`);
4136
+ console.log(pc14.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
3979
4137
  } else {
3980
4138
  spinner.fail("AI did not return a PR description.");
3981
4139
  }
@@ -4063,7 +4221,7 @@ ${pc13.dim("AI body preview:")}`);
4063
4221
  });
4064
4222
  return;
4065
4223
  }
4066
- info(`Pushing ${pc13.bold(currentBranch)} to ${origin}...`);
4224
+ info(`Pushing ${pc14.bold(currentBranch)} to ${origin}...`);
4067
4225
  const pushResult = await pushSetUpstream(origin, currentBranch);
4068
4226
  if (pushResult.exitCode !== 0) {
4069
4227
  error(`Failed to push: ${pushResult.stderr}`);
@@ -4082,7 +4240,7 @@ ${pc13.dim("AI body preview:")}`);
4082
4240
  const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
4083
4241
  console.log();
4084
4242
  info("Create your PR manually:");
4085
- console.log(` ${pc13.cyan(prUrl)}`);
4243
+ console.log(` ${pc14.cyan(prUrl)}`);
4086
4244
  } else {
4087
4245
  info("gh CLI not available. Create your PR manually on GitHub.");
4088
4246
  }
@@ -4115,10 +4273,109 @@ ${pc13.dim("AI body preview:")}`);
4115
4273
  }
4116
4274
  });
4117
4275
 
4276
+ // src/commands/switch.ts
4277
+ import { defineCommand as defineCommand12 } from "citty";
4278
+ import pc15 from "picocolors";
4279
+ var switch_default = defineCommand12({
4280
+ meta: {
4281
+ name: "switch",
4282
+ description: "Switch to another branch with stash protection for uncommitted changes"
4283
+ },
4284
+ args: {
4285
+ name: {
4286
+ type: "positional",
4287
+ description: "Branch name to switch to (interactive picker if omitted)",
4288
+ required: false
4289
+ }
4290
+ },
4291
+ async run({ args }) {
4292
+ if (!await isGitRepo()) {
4293
+ error("Not inside a git repository.");
4294
+ process.exit(1);
4295
+ }
4296
+ const config = readConfig();
4297
+ const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
4298
+ const currentBranch = await getCurrentBranch();
4299
+ heading("\uD83D\uDD00 contrib switch");
4300
+ let targetBranch = args.name;
4301
+ if (!targetBranch) {
4302
+ const localBranches = await getLocalBranches();
4303
+ if (localBranches.length === 0) {
4304
+ error("No local branches found.");
4305
+ process.exit(1);
4306
+ }
4307
+ const choices = localBranches.filter((b) => b.name !== currentBranch).map((b) => {
4308
+ const labels = [];
4309
+ if (protectedBranches.includes(b.name))
4310
+ labels.push(pc15.red("protected"));
4311
+ if (b.upstream)
4312
+ labels.push(pc15.dim(`→ ${b.upstream}`));
4313
+ if (b.gone)
4314
+ labels.push(pc15.red("remote gone"));
4315
+ const suffix = labels.length > 0 ? ` ${labels.join(" · ")}` : "";
4316
+ return `${b.name}${suffix}`;
4317
+ });
4318
+ if (choices.length === 0) {
4319
+ info("You are already on the only local branch.");
4320
+ process.exit(0);
4321
+ }
4322
+ const selected = await selectPrompt("Switch to which branch?", choices);
4323
+ targetBranch = selected.split(/\s{2,}/)[0].trim();
4324
+ }
4325
+ if (targetBranch === currentBranch) {
4326
+ info(`Already on ${pc15.bold(targetBranch)}.`);
4327
+ return;
4328
+ }
4329
+ if (await hasUncommittedChanges()) {
4330
+ warn("You have uncommitted changes.");
4331
+ const action = await selectPrompt("How would you like to handle them?", [
4332
+ "Save changes and switch",
4333
+ "Cancel"
4334
+ ]);
4335
+ if (action === "Cancel") {
4336
+ info("Switch cancelled.");
4337
+ return;
4338
+ }
4339
+ const { execFile } = await import("node:child_process");
4340
+ const { promisify } = await import("node:util");
4341
+ const exec = promisify(execFile);
4342
+ const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
4343
+ try {
4344
+ await exec("git", ["stash", "push", "-m", stashMsg]);
4345
+ info(`Saved changes: ${pc15.dim(stashMsg)}`);
4346
+ } catch {
4347
+ error("Failed to save changes. Please commit or save manually.");
4348
+ process.exit(1);
4349
+ }
4350
+ const result2 = await checkoutBranch(targetBranch);
4351
+ if (result2.exitCode !== 0) {
4352
+ error(`Failed to switch to ${targetBranch}: ${result2.stderr}`);
4353
+ try {
4354
+ await exec("git", ["stash", "pop"]);
4355
+ info("Restored saved changes.");
4356
+ } catch {
4357
+ warn("Could not restore save automatically. Use `contrib save --restore` to recover.");
4358
+ }
4359
+ process.exit(1);
4360
+ }
4361
+ success(`Switched to ${pc15.bold(targetBranch)}`);
4362
+ info(`Your changes from ${pc15.bold(currentBranch ?? "previous branch")} are saved.`);
4363
+ info(`Use ${pc15.bold("contrib save --restore")} to bring them back.`);
4364
+ return;
4365
+ }
4366
+ const result = await checkoutBranch(targetBranch);
4367
+ if (result.exitCode !== 0) {
4368
+ error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
4369
+ process.exit(1);
4370
+ }
4371
+ success(`Switched to ${pc15.bold(targetBranch)}`);
4372
+ }
4373
+ });
4374
+
4118
4375
  // src/commands/sync.ts
4119
- import { defineCommand as defineCommand11 } from "citty";
4120
- import pc14 from "picocolors";
4121
- var sync_default = defineCommand11({
4376
+ import { defineCommand as defineCommand13 } from "citty";
4377
+ import pc16 from "picocolors";
4378
+ var sync_default = defineCommand13({
4122
4379
  meta: {
4123
4380
  name: "sync",
4124
4381
  description: "Sync your local branches with the remote"
@@ -4169,24 +4426,24 @@ var sync_default = defineCommand11({
4169
4426
  await fetchRemote(origin);
4170
4427
  }
4171
4428
  if (!await refExists(syncSource.ref)) {
4172
- error(`Remote ref ${pc14.bold(syncSource.ref)} does not exist.`);
4429
+ error(`Remote ref ${pc16.bold(syncSource.ref)} does not exist.`);
4173
4430
  info("This can happen if the branch was renamed or deleted on the remote.");
4174
- info(`Check your config: the base branch may need updating via ${pc14.bold("contrib setup")}.`);
4431
+ info(`Check your config: the base branch may need updating via ${pc16.bold("contrib setup")}.`);
4175
4432
  process.exit(1);
4176
4433
  }
4177
4434
  let allowMergeCommit = false;
4178
4435
  const div = await getDivergence(baseBranch, syncSource.ref);
4179
4436
  if (div.ahead > 0 || div.behind > 0) {
4180
- info(`${pc14.bold(baseBranch)} is ${pc14.yellow(`${div.ahead} ahead`)} and ${pc14.red(`${div.behind} behind`)} ${syncSource.ref}`);
4437
+ info(`${pc16.bold(baseBranch)} is ${pc16.yellow(`${div.ahead} ahead`)} and ${pc16.red(`${div.behind} behind`)} ${syncSource.ref}`);
4181
4438
  } else {
4182
- info(`${pc14.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
4439
+ info(`${pc16.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
4183
4440
  }
4184
4441
  if (div.ahead > 0) {
4185
4442
  const currentBranch = await getCurrentBranch();
4186
4443
  const protectedBranches = getProtectedBranches(config);
4187
4444
  const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
4188
4445
  if (isOnProtected) {
4189
- warn(`You have ${pc14.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${pc14.bold(baseBranch)} that aren't on the remote.`);
4446
+ warn(`You have ${pc16.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${pc16.bold(baseBranch)} that aren't on the remote.`);
4190
4447
  info("Pulling now could create a merge commit, which breaks clean history.");
4191
4448
  console.log();
4192
4449
  const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
@@ -4202,7 +4459,7 @@ var sync_default = defineCommand11({
4202
4459
  return;
4203
4460
  }
4204
4461
  if (action === MOVE_BRANCH) {
4205
- info(pc14.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4462
+ info(pc16.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4206
4463
  const description = await inputPrompt("What are you going to work on?");
4207
4464
  let newBranchName = description;
4208
4465
  if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
@@ -4213,8 +4470,8 @@ var sync_default = defineCommand11({
4213
4470
  if (suggested) {
4214
4471
  spinner.success("Branch name suggestion ready.");
4215
4472
  console.log(`
4216
- ${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(suggested))}`);
4217
- const accepted = await confirmPrompt(`Use ${pc14.bold(suggested)} as your branch name?`);
4473
+ ${pc16.dim("AI suggestion:")} ${pc16.bold(pc16.cyan(suggested))}`);
4474
+ const accepted = await confirmPrompt(`Use ${pc16.bold(suggested)} as your branch name?`);
4218
4475
  newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
4219
4476
  } else {
4220
4477
  spinner.fail("AI did not return a suggestion.");
@@ -4223,7 +4480,7 @@ var sync_default = defineCommand11({
4223
4480
  }
4224
4481
  }
4225
4482
  if (!hasPrefix(newBranchName, config.branchPrefixes)) {
4226
- const prefix = await selectPrompt(`Choose a branch type for ${pc14.bold(newBranchName)}:`, config.branchPrefixes);
4483
+ const prefix = await selectPrompt(`Choose a branch type for ${pc16.bold(newBranchName)}:`, config.branchPrefixes);
4227
4484
  newBranchName = formatBranchName(prefix, newBranchName);
4228
4485
  }
4229
4486
  if (!isValidBranchName(newBranchName)) {
@@ -4231,7 +4488,7 @@ var sync_default = defineCommand11({
4231
4488
  process.exit(1);
4232
4489
  }
4233
4490
  if (await branchExists(newBranchName)) {
4234
- error(`Branch ${pc14.bold(newBranchName)} already exists. Choose a different name.`);
4491
+ error(`Branch ${pc16.bold(newBranchName)} already exists. Choose a different name.`);
4235
4492
  process.exit(1);
4236
4493
  }
4237
4494
  const branchResult = await createBranch(newBranchName);
@@ -4239,7 +4496,7 @@ var sync_default = defineCommand11({
4239
4496
  error(`Failed to create branch: ${branchResult.stderr}`);
4240
4497
  process.exit(1);
4241
4498
  }
4242
- success(`Created ${pc14.bold(newBranchName)} with your commits.`);
4499
+ success(`Created ${pc16.bold(newBranchName)} with your commits.`);
4243
4500
  const coResult2 = await checkoutBranch(baseBranch);
4244
4501
  if (coResult2.exitCode !== 0) {
4245
4502
  error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
@@ -4247,11 +4504,11 @@ var sync_default = defineCommand11({
4247
4504
  }
4248
4505
  const remoteRef = syncSource.ref;
4249
4506
  await updateLocalBranch(baseBranch, remoteRef);
4250
- success(`Reset ${pc14.bold(baseBranch)} to ${pc14.bold(remoteRef)}.`);
4251
- success(`✅ ${pc14.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
4507
+ success(`Reset ${pc16.bold(baseBranch)} to ${pc16.bold(remoteRef)}.`);
4508
+ success(`✅ ${pc16.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
4252
4509
  console.log();
4253
- info(`Your commits are safe on ${pc14.bold(newBranchName)}.`);
4254
- info(`Run ${pc14.bold(`git checkout ${newBranchName}`)} then ${pc14.bold("contrib update")} to rebase onto the synced ${pc14.bold(baseBranch)}.`);
4510
+ info(`Your commits are safe on ${pc16.bold(newBranchName)}.`);
4511
+ info(`Run ${pc16.bold(`git checkout ${newBranchName}`)} then ${pc16.bold("contrib update")} to rebase onto the synced ${pc16.bold(baseBranch)}.`);
4255
4512
  return;
4256
4513
  }
4257
4514
  allowMergeCommit = true;
@@ -4259,7 +4516,7 @@ var sync_default = defineCommand11({
4259
4516
  }
4260
4517
  }
4261
4518
  if (!args.yes) {
4262
- const ok = await confirmPrompt(`This will pull ${pc14.bold(syncSource.ref)} into local ${pc14.bold(baseBranch)}.`);
4519
+ const ok = await confirmPrompt(`This will pull ${pc16.bold(syncSource.ref)} into local ${pc16.bold(baseBranch)}.`);
4263
4520
  if (!ok)
4264
4521
  process.exit(0);
4265
4522
  }
@@ -4273,8 +4530,8 @@ var sync_default = defineCommand11({
4273
4530
  if (allowMergeCommit) {
4274
4531
  error(`Pull failed: ${pullResult.stderr.trim()}`);
4275
4532
  } else {
4276
- error(`Fast-forward pull failed. Your local ${pc14.bold(baseBranch)} may have diverged.`);
4277
- info(`Use ${pc14.bold("contrib sync")} again and choose "Move my commits to a new feature branch" to fix this.`);
4533
+ error(`Fast-forward pull failed. Your local ${pc16.bold(baseBranch)} may have diverged.`);
4534
+ info(`Use ${pc16.bold("contrib sync")} again and choose "Move my commits to a new feature branch" to fix this.`);
4278
4535
  }
4279
4536
  process.exit(1);
4280
4537
  }
@@ -4282,7 +4539,7 @@ var sync_default = defineCommand11({
4282
4539
  if (hasDevBranch(workflow) && role === "maintainer") {
4283
4540
  const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
4284
4541
  if (mainDiv.behind > 0) {
4285
- info(`Also syncing ${pc14.bold(config.mainBranch)}...`);
4542
+ info(`Also syncing ${pc16.bold(config.mainBranch)}...`);
4286
4543
  const mainCoResult = await checkoutBranch(config.mainBranch);
4287
4544
  if (mainCoResult.exitCode === 0) {
4288
4545
  const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
@@ -4298,9 +4555,9 @@ var sync_default = defineCommand11({
4298
4555
 
4299
4556
  // src/commands/update.ts
4300
4557
  import { readFileSync as readFileSync4 } from "node:fs";
4301
- import { defineCommand as defineCommand12 } from "citty";
4302
- import pc15 from "picocolors";
4303
- var update_default = defineCommand12({
4558
+ import { defineCommand as defineCommand14 } from "citty";
4559
+ import pc17 from "picocolors";
4560
+ var update_default = defineCommand14({
4304
4561
  meta: {
4305
4562
  name: "update",
4306
4563
  description: "Rebase current branch onto the latest base branch"
@@ -4337,7 +4594,7 @@ var update_default = defineCommand12({
4337
4594
  }
4338
4595
  if (protectedBranches.includes(currentBranch)) {
4339
4596
  heading("\uD83D\uDD03 contrib update");
4340
- warn(`You're on ${pc15.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
4597
+ warn(`You're on ${pc17.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
4341
4598
  await fetchAll();
4342
4599
  const { origin } = config;
4343
4600
  const remoteRef = `${origin}/${currentBranch}`;
@@ -4346,12 +4603,12 @@ var update_default = defineCommand12({
4346
4603
  const hasCommits = localWork.unpushedCommits > 0;
4347
4604
  const hasAnything = hasCommits || dirty;
4348
4605
  if (!hasAnything) {
4349
- info(`No local changes found on ${pc15.bold(currentBranch)}.`);
4350
- info(`Use ${pc15.bold("contrib sync")} to sync protected branches, or ${pc15.bold("contrib start")} to create a feature branch.`);
4606
+ info(`No local changes found on ${pc17.bold(currentBranch)}.`);
4607
+ info(`Use ${pc17.bold("contrib sync")} to sync protected branches, or ${pc17.bold("contrib start")} to create a feature branch.`);
4351
4608
  process.exit(1);
4352
4609
  }
4353
4610
  if (hasCommits) {
4354
- info(`Found ${pc15.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc15.bold(currentBranch)}.`);
4611
+ info(`Found ${pc17.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc17.bold(currentBranch)}.`);
4355
4612
  }
4356
4613
  if (dirty) {
4357
4614
  info("You also have uncommitted changes in the working tree.");
@@ -4367,7 +4624,7 @@ var update_default = defineCommand12({
4367
4624
  info("No changes made. You are still on your current branch.");
4368
4625
  return;
4369
4626
  }
4370
- info(pc15.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4627
+ info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4371
4628
  const description = await inputPrompt("What are you going to work on?");
4372
4629
  let newBranchName = description;
4373
4630
  if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
@@ -4378,8 +4635,8 @@ var update_default = defineCommand12({
4378
4635
  if (suggested) {
4379
4636
  spinner.success("Branch name suggestion ready.");
4380
4637
  console.log(`
4381
- ${pc15.dim("AI suggestion:")} ${pc15.bold(pc15.cyan(suggested))}`);
4382
- const accepted = await confirmPrompt(`Use ${pc15.bold(suggested)} as your branch name?`);
4638
+ ${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
4639
+ const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
4383
4640
  newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
4384
4641
  } else {
4385
4642
  spinner.fail("AI did not return a suggestion.");
@@ -4388,7 +4645,7 @@ var update_default = defineCommand12({
4388
4645
  }
4389
4646
  }
4390
4647
  if (!hasPrefix(newBranchName, config.branchPrefixes)) {
4391
- const prefix = await selectPrompt(`Choose a branch type for ${pc15.bold(newBranchName)}:`, config.branchPrefixes);
4648
+ const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
4392
4649
  newBranchName = formatBranchName(prefix, newBranchName);
4393
4650
  }
4394
4651
  if (!isValidBranchName(newBranchName)) {
@@ -4400,12 +4657,12 @@ var update_default = defineCommand12({
4400
4657
  error(`Failed to create branch: ${branchResult.stderr}`);
4401
4658
  process.exit(1);
4402
4659
  }
4403
- success(`Created ${pc15.bold(newBranchName)} with your changes.`);
4660
+ success(`Created ${pc17.bold(newBranchName)} with your changes.`);
4404
4661
  await updateLocalBranch(currentBranch, remoteRef);
4405
- info(`Reset ${pc15.bold(currentBranch)} back to ${pc15.bold(remoteRef)} — no damage done.`);
4662
+ info(`Reset ${pc17.bold(currentBranch)} back to ${pc17.bold(remoteRef)} — no damage done.`);
4406
4663
  console.log();
4407
- success(`You're now on ${pc15.bold(newBranchName)} with all your work intact.`);
4408
- info(`Run ${pc15.bold("contrib update")} again to rebase onto latest ${pc15.bold(baseBranch)}.`);
4664
+ success(`You're now on ${pc17.bold(newBranchName)} with all your work intact.`);
4665
+ info(`Run ${pc17.bold("contrib update")} again to rebase onto latest ${pc17.bold(baseBranch)}.`);
4409
4666
  return;
4410
4667
  }
4411
4668
  if (await hasUncommittedChanges()) {
@@ -4415,8 +4672,8 @@ var update_default = defineCommand12({
4415
4672
  heading("\uD83D\uDD03 contrib update");
4416
4673
  const mergedPR = await getMergedPRForBranch(currentBranch);
4417
4674
  if (mergedPR) {
4418
- warn(`PR #${mergedPR.number} (${pc15.bold(mergedPR.title)}) has already been merged.`);
4419
- info(`Link: ${pc15.underline(mergedPR.url)}`);
4675
+ warn(`PR #${mergedPR.number} (${pc17.bold(mergedPR.title)}) has already been merged.`);
4676
+ info(`Link: ${pc17.underline(mergedPR.url)}`);
4420
4677
  const localWork = await hasLocalWork(syncSource.remote, currentBranch);
4421
4678
  const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
4422
4679
  if (hasWork) {
@@ -4429,13 +4686,13 @@ var update_default = defineCommand12({
4429
4686
  const SAVE_NEW_BRANCH = "Save changes to a new branch";
4430
4687
  const DISCARD = "Discard all changes and clean up";
4431
4688
  const CANCEL = "Cancel";
4432
- const action = await selectPrompt(`${pc15.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
4689
+ const action = await selectPrompt(`${pc17.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
4433
4690
  if (action === CANCEL) {
4434
4691
  info("No changes made. You are still on your current branch.");
4435
4692
  return;
4436
4693
  }
4437
4694
  if (action === SAVE_NEW_BRANCH) {
4438
- info(pc15.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4695
+ info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
4439
4696
  const description = await inputPrompt("What are you going to work on?");
4440
4697
  let newBranchName = description;
4441
4698
  if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
@@ -4444,8 +4701,8 @@ var update_default = defineCommand12({
4444
4701
  if (suggested) {
4445
4702
  spinner.success("Branch name suggestion ready.");
4446
4703
  console.log(`
4447
- ${pc15.dim("AI suggestion:")} ${pc15.bold(pc15.cyan(suggested))}`);
4448
- const accepted = await confirmPrompt(`Use ${pc15.bold(suggested)} as your branch name?`);
4704
+ ${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
4705
+ const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
4449
4706
  newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
4450
4707
  } else {
4451
4708
  spinner.fail("AI did not return a suggestion.");
@@ -4453,7 +4710,7 @@ var update_default = defineCommand12({
4453
4710
  }
4454
4711
  }
4455
4712
  if (!hasPrefix(newBranchName, config.branchPrefixes)) {
4456
- const prefix = await selectPrompt(`Choose a branch type for ${pc15.bold(newBranchName)}:`, config.branchPrefixes);
4713
+ const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
4457
4714
  newBranchName = formatBranchName(prefix, newBranchName);
4458
4715
  }
4459
4716
  if (!isValidBranchName(newBranchName)) {
@@ -4463,7 +4720,7 @@ var update_default = defineCommand12({
4463
4720
  const staleUpstream = await getUpstreamRef();
4464
4721
  const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
4465
4722
  if (await branchExists(newBranchName)) {
4466
- error(`Branch ${pc15.bold(newBranchName)} already exists. Choose a different name.`);
4723
+ error(`Branch ${pc17.bold(newBranchName)} already exists. Choose a different name.`);
4467
4724
  process.exit(1);
4468
4725
  }
4469
4726
  const renameResult = await renameBranch(currentBranch, newBranchName);
@@ -4471,7 +4728,7 @@ var update_default = defineCommand12({
4471
4728
  error(`Failed to rename branch: ${renameResult.stderr}`);
4472
4729
  process.exit(1);
4473
4730
  }
4474
- success(`Renamed ${pc15.bold(currentBranch)} → ${pc15.bold(newBranchName)}`);
4731
+ success(`Renamed ${pc17.bold(currentBranch)} → ${pc17.bold(newBranchName)}`);
4475
4732
  await unsetUpstream();
4476
4733
  await fetchRemote(syncSource.remote);
4477
4734
  let rebaseResult2;
@@ -4483,11 +4740,11 @@ var update_default = defineCommand12({
4483
4740
  }
4484
4741
  if (rebaseResult2.exitCode !== 0) {
4485
4742
  warn("Rebase encountered conflicts. Resolve them manually, then run:");
4486
- info(` ${pc15.bold("git rebase --continue")}`);
4743
+ info(` ${pc17.bold("git rebase --continue")}`);
4487
4744
  } else {
4488
- success(`Rebased ${pc15.bold(newBranchName)} onto ${pc15.bold(syncSource.ref)}.`);
4745
+ success(`Rebased ${pc17.bold(newBranchName)} onto ${pc17.bold(syncSource.ref)}.`);
4489
4746
  }
4490
- info(`All your changes are preserved. Run ${pc15.bold("contrib submit")} when ready to create a new PR.`);
4747
+ info(`All your changes are preserved. Run ${pc17.bold("contrib submit")} when ready to create a new PR.`);
4491
4748
  return;
4492
4749
  }
4493
4750
  warn("Discarding local changes...");
@@ -4500,24 +4757,24 @@ var update_default = defineCommand12({
4500
4757
  process.exit(1);
4501
4758
  }
4502
4759
  await updateLocalBranch(baseBranch, syncSource.ref);
4503
- success(`Synced ${pc15.bold(baseBranch)} with ${pc15.bold(syncSource.ref)}.`);
4504
- info(`Deleting stale branch ${pc15.bold(currentBranch)}...`);
4760
+ success(`Synced ${pc17.bold(baseBranch)} with ${pc17.bold(syncSource.ref)}.`);
4761
+ info(`Deleting stale branch ${pc17.bold(currentBranch)}...`);
4505
4762
  await forceDeleteBranch(currentBranch);
4506
- success(`Deleted ${pc15.bold(currentBranch)}.`);
4507
- info(`Run ${pc15.bold("contrib start")} to begin a new feature branch.`);
4763
+ success(`Deleted ${pc17.bold(currentBranch)}.`);
4764
+ info(`Run ${pc17.bold("contrib start")} to begin a new feature branch.`);
4508
4765
  return;
4509
4766
  }
4510
- info(`Updating ${pc15.bold(currentBranch)} with latest ${pc15.bold(baseBranch)}...`);
4767
+ info(`Updating ${pc17.bold(currentBranch)} with latest ${pc17.bold(baseBranch)}...`);
4511
4768
  await fetchRemote(syncSource.remote);
4512
4769
  if (!await refExists(syncSource.ref)) {
4513
- error(`Remote ref ${pc15.bold(syncSource.ref)} does not exist.`);
4770
+ error(`Remote ref ${pc17.bold(syncSource.ref)} does not exist.`);
4514
4771
  error("Run `git fetch --all` and verify your remote configuration.");
4515
4772
  process.exit(1);
4516
4773
  }
4517
4774
  await updateLocalBranch(baseBranch, syncSource.ref);
4518
4775
  const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
4519
4776
  if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
4520
- info(pc15.dim(`Using --onto rebase (branch was based on a different ref)`));
4777
+ info(pc17.dim(`Using --onto rebase (branch was based on a different ref)`));
4521
4778
  }
4522
4779
  const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
4523
4780
  if (rebaseResult.exitCode !== 0) {
@@ -4546,10 +4803,10 @@ ${content.slice(0, 2000)}
4546
4803
  if (suggestion) {
4547
4804
  spinner.success("AI conflict guidance ready.");
4548
4805
  console.log(`
4549
- ${pc15.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
4550
- console.log(pc15.dim("─".repeat(60)));
4806
+ ${pc17.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
4807
+ console.log(pc17.dim("─".repeat(60)));
4551
4808
  console.log(suggestion);
4552
- console.log(pc15.dim("─".repeat(60)));
4809
+ console.log(pc17.dim("─".repeat(60)));
4553
4810
  console.log();
4554
4811
  } else {
4555
4812
  spinner.fail("AI could not analyze the conflicts.");
@@ -4557,22 +4814,22 @@ ${pc15.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
4557
4814
  }
4558
4815
  }
4559
4816
  }
4560
- console.log(pc15.bold("To resolve:"));
4817
+ console.log(pc17.bold("To resolve:"));
4561
4818
  console.log(` 1. Fix conflicts in the affected files`);
4562
- console.log(` 2. ${pc15.cyan("git add <resolved-files>")}`);
4563
- console.log(` 3. ${pc15.cyan("git rebase --continue")}`);
4819
+ console.log(` 2. ${pc17.cyan("git add <resolved-files>")}`);
4820
+ console.log(` 3. ${pc17.cyan("git rebase --continue")}`);
4564
4821
  console.log();
4565
- console.log(` Or abort: ${pc15.cyan("git rebase --abort")}`);
4822
+ console.log(` Or abort: ${pc17.cyan("git rebase --abort")}`);
4566
4823
  process.exit(1);
4567
4824
  }
4568
- success(`✅ ${pc15.bold(currentBranch)} has been rebased onto latest ${pc15.bold(baseBranch)}`);
4825
+ success(`✅ ${pc17.bold(currentBranch)} has been rebased onto latest ${pc17.bold(baseBranch)}`);
4569
4826
  }
4570
4827
  });
4571
4828
 
4572
4829
  // src/commands/validate.ts
4573
- import { defineCommand as defineCommand13 } from "citty";
4574
- import pc16 from "picocolors";
4575
- var validate_default = defineCommand13({
4830
+ import { defineCommand as defineCommand15 } from "citty";
4831
+ import pc18 from "picocolors";
4832
+ var validate_default = defineCommand15({
4576
4833
  meta: {
4577
4834
  name: "validate",
4578
4835
  description: "Validate a commit message against the configured convention"
@@ -4602,7 +4859,7 @@ var validate_default = defineCommand13({
4602
4859
  }
4603
4860
  const errors = getValidationError(convention);
4604
4861
  for (const line of errors) {
4605
- console.error(pc16.red(` ✗ ${line}`));
4862
+ console.error(pc18.red(` ✗ ${line}`));
4606
4863
  }
4607
4864
  process.exit(1);
4608
4865
  }
@@ -4610,7 +4867,7 @@ var validate_default = defineCommand13({
4610
4867
 
4611
4868
  // src/ui/banner.ts
4612
4869
  import figlet from "figlet";
4613
- import pc17 from "picocolors";
4870
+ import pc19 from "picocolors";
4614
4871
  var LOGO_BIG;
4615
4872
  try {
4616
4873
  LOGO_BIG = figlet.textSync(`Contribute
@@ -4632,14 +4889,14 @@ function getAuthor() {
4632
4889
  }
4633
4890
  function showBanner(variant = "small") {
4634
4891
  const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
4635
- console.log(pc17.cyan(`
4892
+ console.log(pc19.cyan(`
4636
4893
  ${logo}`));
4637
- console.log(` ${pc17.dim(`v${getVersion()}`)} ${pc17.dim("—")} ${pc17.dim(`Built by ${getAuthor()}`)}`);
4894
+ console.log(` ${pc19.dim(`v${getVersion()}`)} ${pc19.dim("—")} ${pc19.dim(`Built by ${getAuthor()}`)}`);
4638
4895
  if (variant === "big") {
4639
4896
  console.log();
4640
- console.log(` ${pc17.yellow("Star")} ${pc17.cyan("https://github.com/warengonzaga/contribute-now")}`);
4641
- console.log(` ${pc17.green("Contribute")} ${pc17.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
4642
- console.log(` ${pc17.magenta("Sponsor")} ${pc17.cyan("https://warengonzaga.com/sponsor")}`);
4897
+ console.log(` ${pc19.yellow("Star")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now")}`);
4898
+ console.log(` ${pc19.green("Contribute")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
4899
+ console.log(` ${pc19.magenta("Sponsor")} ${pc19.cyan("https://warengonzaga.com/sponsor")}`);
4643
4900
  }
4644
4901
  console.log();
4645
4902
  }
@@ -4654,6 +4911,8 @@ if (!isVersion) {
4654
4911
  "commit",
4655
4912
  "update",
4656
4913
  "submit",
4914
+ "switch",
4915
+ "save",
4657
4916
  "clean",
4658
4917
  "status",
4659
4918
  "log",
@@ -4667,7 +4926,7 @@ if (!isVersion) {
4667
4926
  const useBigBanner = isHelp || !hasSubCommand;
4668
4927
  showBanner(useBigBanner ? "big" : "small");
4669
4928
  }
4670
- var main = defineCommand14({
4929
+ var main = defineCommand16({
4671
4930
  meta: {
4672
4931
  name: "contrib",
4673
4932
  version: getVersion(),
@@ -4687,6 +4946,8 @@ var main = defineCommand14({
4687
4946
  commit: commit_default,
4688
4947
  update: update_default,
4689
4948
  submit: submit_default,
4949
+ switch: switch_default,
4950
+ save: save_default,
4690
4951
  branch: branch_default,
4691
4952
  clean: clean_default,
4692
4953
  status: status_default,