contribute-now 0.5.0 → 0.6.0-dev.ec5e617

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