codebyplan 1.13.8 → 1.13.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- VERSION = "1.13.8";
17
+ VERSION = "1.13.10";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -148,6 +148,7 @@ var init_gitignore_block = __esm({
148
148
  ".claude/scheduled_tasks.lock",
149
149
  ".codebyplan/device.local.json",
150
150
  ".codebyplan/statusline.local.json",
151
+ ".codebyplan/worktree.local.json",
151
152
  ".codebyplan.local.json"
152
153
  ];
153
154
  GITIGNORE_BLOCK_START = "# >>> codebyplan (managed) >>>";
@@ -1443,8 +1444,8 @@ async function runSetup() {
1443
1444
  const deviceId = await getOrCreateDeviceId(projectPath);
1444
1445
  let branch = "main";
1445
1446
  try {
1446
- const { execSync: execSync7 } = await import("node:child_process");
1447
- branch = execSync7("git symbolic-ref --short HEAD", {
1447
+ const { execSync: execSync8 } = await import("node:child_process");
1448
+ branch = execSync8("git symbolic-ref --short HEAD", {
1448
1449
  cwd: projectPath,
1449
1450
  encoding: "utf-8"
1450
1451
  }).trim();
@@ -3064,9 +3065,9 @@ async function eslintInit(repoId, projectPath) {
3064
3065
  Install ${missingPkgs.length} missing packages? [Y/n] `
3065
3066
  );
3066
3067
  if (confirmed) {
3067
- const { execSync: execSync7 } = await import("node:child_process");
3068
+ const { execSync: execSync8 } = await import("node:child_process");
3068
3069
  try {
3069
- execSync7(installCmd, { cwd: projectPath, stdio: "inherit" });
3070
+ execSync8(installCmd, { cwd: projectPath, stdio: "inherit" });
3070
3071
  console.log(" Packages installed.\n");
3071
3072
  } catch (err) {
3072
3073
  console.error(
@@ -3409,6 +3410,81 @@ var init_sync_approvals = __esm({
3409
3410
  }
3410
3411
  });
3411
3412
 
3413
+ // src/lib/worktree-cache.ts
3414
+ import { mkdir as mkdir5, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
3415
+ import { dirname as dirname4, join as join11 } from "node:path";
3416
+ function worktreeCachePath(repoRoot) {
3417
+ return join11(repoRoot, ".codebyplan", "worktree.local.json");
3418
+ }
3419
+ async function readCachedWorktreeId(repoRoot, currentBranch) {
3420
+ const cachePath = worktreeCachePath(repoRoot);
3421
+ let raw;
3422
+ try {
3423
+ raw = await readFile11(cachePath, "utf-8");
3424
+ } catch (err) {
3425
+ const code = err.code;
3426
+ if (code === "ENOENT") {
3427
+ return null;
3428
+ }
3429
+ console.warn(
3430
+ `worktree-cache: read failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`
3431
+ );
3432
+ return null;
3433
+ }
3434
+ let parsed;
3435
+ try {
3436
+ parsed = JSON.parse(raw);
3437
+ } catch {
3438
+ console.warn(
3439
+ "worktree-cache: JSON parse failed (non-fatal): cache entry will be ignored"
3440
+ );
3441
+ return null;
3442
+ }
3443
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.worktree_id !== "string" || typeof parsed.branch !== "string" || typeof parsed.device_id !== "string" || typeof parsed.resolved_at !== "string") {
3444
+ return null;
3445
+ }
3446
+ const data = parsed;
3447
+ if (!data.branch || data.branch !== currentBranch) {
3448
+ return null;
3449
+ }
3450
+ return data.worktree_id;
3451
+ }
3452
+ async function writeWorktreeCache(repoRoot, data) {
3453
+ if (!data.branch) {
3454
+ console.warn(
3455
+ "worktree-cache: skipping write for empty branch (detached HEAD or non-repo)"
3456
+ );
3457
+ return false;
3458
+ }
3459
+ const cachePath = worktreeCachePath(repoRoot);
3460
+ const dirPath = dirname4(cachePath);
3461
+ const payload = {
3462
+ worktree_id: data.worktree_id,
3463
+ branch: data.branch,
3464
+ device_id: data.device_id,
3465
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString()
3466
+ };
3467
+ try {
3468
+ await mkdir5(dirPath, { recursive: true });
3469
+ await writeFile9(
3470
+ cachePath,
3471
+ JSON.stringify(payload, null, 2) + "\n",
3472
+ "utf-8"
3473
+ );
3474
+ return true;
3475
+ } catch (err) {
3476
+ console.warn(
3477
+ `worktree-cache: write failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`
3478
+ );
3479
+ return false;
3480
+ }
3481
+ }
3482
+ var init_worktree_cache = __esm({
3483
+ "src/lib/worktree-cache.ts"() {
3484
+ "use strict";
3485
+ }
3486
+ });
3487
+
3412
3488
  // src/cli/round.ts
3413
3489
  var round_exports = {};
3414
3490
  __export(round_exports, {
@@ -3420,7 +3496,7 @@ __export(round_exports, {
3420
3496
  setRetryDelayMs: () => setRetryDelayMs
3421
3497
  });
3422
3498
  import { access as access3 } from "node:fs/promises";
3423
- import { join as join11 } from "node:path";
3499
+ import { join as join12 } from "node:path";
3424
3500
  import { execSync as execSync2 } from "node:child_process";
3425
3501
  function setRetryDelayMs(ms) {
3426
3502
  RETRY_DELAY_MS = ms;
@@ -3480,6 +3556,34 @@ function printRoundHelp() {
3480
3556
  "\n codebyplan round <subcommand>\n\n Subcommands:\n sync-approvals Sync git diff and approvals with round/task state\n\n sync-approvals flags:\n --round-id <uuid> Round UUID (required)\n --task-id <uuid> Task UUID (required)\n --dry-run Print merged payload to stdout without writing\n\n"
3481
3557
  );
3482
3558
  }
3559
+ async function resolveCallerWorktreeId(repoRoot, currentBranch, repoId, overrideId) {
3560
+ if (overrideId) {
3561
+ return overrideId;
3562
+ }
3563
+ const cached = await readCachedWorktreeId(repoRoot, currentBranch);
3564
+ if (cached) {
3565
+ return cached;
3566
+ }
3567
+ if (!repoId || !currentBranch) {
3568
+ return null;
3569
+ }
3570
+ const deviceId = await getOrCreateDeviceId(repoRoot);
3571
+ const wid = await resolveWorktreeId({
3572
+ repoId,
3573
+ repoPath: repoRoot,
3574
+ branch: currentBranch,
3575
+ deviceId
3576
+ });
3577
+ if (wid) {
3578
+ await writeWorktreeCache(repoRoot, {
3579
+ worktree_id: wid,
3580
+ branch: currentBranch,
3581
+ device_id: deviceId
3582
+ });
3583
+ return wid;
3584
+ }
3585
+ return null;
3586
+ }
3483
3587
  function parseFlagsFromArgs(args) {
3484
3588
  const flags = {};
3485
3589
  const booleans = /* @__PURE__ */ new Set();
@@ -3503,6 +3607,7 @@ async function runRoundSyncApprovals(args) {
3503
3607
  const roundId = flags["round-id"];
3504
3608
  const taskId = flags["task-id"];
3505
3609
  const dryRun = booleans.has("dry-run");
3610
+ const overrideWorktreeId = flags["caller-worktree-id"];
3506
3611
  if (!roundId) {
3507
3612
  process.stderr.write(
3508
3613
  "sync-approvals: --round-id is required\nUsage: codebyplan round sync-approvals --round-id <uuid> --task-id <uuid>\n"
@@ -3523,6 +3628,14 @@ async function runRoundSyncApprovals(args) {
3523
3628
  // Walk up to the directory containing .codebyplan/ or .codebyplan.json
3524
3629
  found.path.replace(/\/.codebyplan(\.json|\/repo\.json)$/, "")
3525
3630
  ) : process.cwd();
3631
+ let currentBranch = "";
3632
+ try {
3633
+ currentBranch = execSync2("git symbolic-ref --short HEAD", {
3634
+ cwd: repoRoot,
3635
+ encoding: "utf-8"
3636
+ }).trim();
3637
+ } catch {
3638
+ }
3526
3639
  let rounds;
3527
3640
  try {
3528
3641
  rounds = await fetchRoundsWithRetry(taskId);
@@ -3542,6 +3655,17 @@ async function runRoundSyncApprovals(args) {
3542
3655
  }
3543
3656
  const taskResponse = await apiGet(`/tasks/${taskId}`);
3544
3657
  const currentTask = taskResponse.data;
3658
+ const callerWorktreeId = await resolveCallerWorktreeId(
3659
+ repoRoot,
3660
+ currentBranch,
3661
+ found?.contents.repo_id,
3662
+ overrideWorktreeId
3663
+ );
3664
+ if (!dryRun && !callerWorktreeId) {
3665
+ throw new Error(
3666
+ "could not resolve caller_worktree_id for this worktree.\n Run: codebyplan resolve-worktree --cache\n If this worktree is not registered, run: npx codebyplan setup\n Then re-run /cbp-round-update (sync-approvals)."
3667
+ );
3668
+ }
3545
3669
  let gitStatusOutput = "";
3546
3670
  try {
3547
3671
  gitStatusOutput = execSync2("git status --short --porcelain -z", {
@@ -3553,7 +3677,7 @@ async function runRoundSyncApprovals(args) {
3553
3677
  "sync-approvals: git status failed; proceeding with empty diff\n"
3554
3678
  );
3555
3679
  }
3556
- const hookPath = join11(
3680
+ const hookPath = join12(
3557
3681
  repoRoot,
3558
3682
  ".claude",
3559
3683
  "hooks",
@@ -3586,12 +3710,14 @@ async function runRoundSyncApprovals(args) {
3586
3710
  } else {
3587
3711
  await mcpCall("update_round", {
3588
3712
  round_id: roundId,
3589
- files_changed: result.merged_files_changed
3713
+ files_changed: result.merged_files_changed,
3714
+ caller_worktree_id: callerWorktreeId
3590
3715
  });
3591
3716
  await mcpCall("update_task", {
3592
3717
  task_id: taskId,
3593
3718
  files_changed: result.merged_files_changed,
3594
- app_file_approval_by_user: false
3719
+ app_file_approval_by_user: false,
3720
+ caller_worktree_id: callerWorktreeId
3595
3721
  });
3596
3722
  stdoutPayload = JSON.stringify(
3597
3723
  {
@@ -3634,13 +3760,16 @@ var init_round = __esm({
3634
3760
  init_mcp_client();
3635
3761
  init_flags();
3636
3762
  init_sync_approvals();
3763
+ init_worktree_cache();
3764
+ init_resolve_worktree();
3765
+ init_local_config();
3637
3766
  RETRY_DELAY_MS = 1e3;
3638
3767
  }
3639
3768
  });
3640
3769
 
3641
3770
  // src/lib/migrate-branch-model.ts
3642
- import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
3643
- import { join as join12 } from "node:path";
3771
+ import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
3772
+ import { join as join13 } from "node:path";
3644
3773
  import { execSync as execSync3 } from "node:child_process";
3645
3774
  function assertValidBranchName(branch) {
3646
3775
  if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
@@ -3650,7 +3779,7 @@ function assertValidBranchName(branch) {
3650
3779
  }
3651
3780
  }
3652
3781
  async function readJsonFile(filePath) {
3653
- const raw = await readFile11(filePath, "utf-8");
3782
+ const raw = await readFile12(filePath, "utf-8");
3654
3783
  const parsed = JSON.parse(raw);
3655
3784
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3656
3785
  throw new Error(`${filePath} does not contain a JSON object`);
@@ -3719,12 +3848,12 @@ async function runBranchMigration(opts) {
3719
3848
  if (found) {
3720
3849
  if (found.path.endsWith("/repo.json")) {
3721
3850
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
3722
- configPath = join12(dir, "git.json");
3851
+ configPath = join13(dir, "git.json");
3723
3852
  } else {
3724
3853
  configPath = found.path;
3725
3854
  }
3726
3855
  } else {
3727
- configPath = join12(cwd, ".codebyplan", "git.json");
3856
+ configPath = join13(cwd, ".codebyplan", "git.json");
3728
3857
  }
3729
3858
  let fileRaw;
3730
3859
  let fileParsed;
@@ -3798,7 +3927,7 @@ async function runBranchMigration(opts) {
3798
3927
  const updatedParsed = { ...fileParsed, branch_config: after };
3799
3928
  const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
3800
3929
  if (newJson !== fileRaw) {
3801
- await writeFile9(configPath, newJson, "utf-8");
3930
+ await writeFile10(configPath, newJson, "utf-8");
3802
3931
  }
3803
3932
  }
3804
3933
  return {
@@ -3904,8 +4033,8 @@ var init_branch = __esm({
3904
4033
  });
3905
4034
 
3906
4035
  // src/lib/git-utils.ts
3907
- import { readFile as readFile12 } from "node:fs/promises";
3908
- import { join as join13 } from "node:path";
4036
+ import { readFile as readFile13 } from "node:fs/promises";
4037
+ import { join as join14 } from "node:path";
3909
4038
  import { spawnSync as spawnSync2 } from "node:child_process";
3910
4039
  async function readBaseBranch(cwd) {
3911
4040
  const found = await findCodebyplanConfig(cwd);
@@ -3913,15 +4042,15 @@ async function readBaseBranch(cwd) {
3913
4042
  if (found) {
3914
4043
  if (found.path.endsWith("/repo.json")) {
3915
4044
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
3916
- gitJsonPath = join13(dir, "git.json");
4045
+ gitJsonPath = join14(dir, "git.json");
3917
4046
  } else {
3918
4047
  gitJsonPath = found.path;
3919
4048
  }
3920
4049
  } else {
3921
- gitJsonPath = join13(cwd, ".codebyplan", "git.json");
4050
+ gitJsonPath = join14(cwd, ".codebyplan", "git.json");
3922
4051
  }
3923
4052
  try {
3924
- const raw = await readFile12(gitJsonPath, "utf-8");
4053
+ const raw = await readFile13(gitJsonPath, "utf-8");
3925
4054
  const parsed = JSON.parse(raw);
3926
4055
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3927
4056
  return "main";
@@ -3959,8 +4088,8 @@ var init_git_utils = __esm({
3959
4088
  });
3960
4089
 
3961
4090
  // src/lib/bump.ts
3962
- import { readFile as readFile13, writeFile as writeFile10, access as access4, readdir as readdir3 } from "node:fs/promises";
3963
- import { join as join14, relative as relative3, resolve as resolve2 } from "node:path";
4091
+ import { readFile as readFile14, writeFile as writeFile11, access as access4, readdir as readdir3 } from "node:fs/promises";
4092
+ import { join as join15, relative as relative3, resolve as resolve2 } from "node:path";
3964
4093
  import { spawnSync as spawnSync3 } from "node:child_process";
3965
4094
  function parsePnpmWorkspaceGlobs(raw) {
3966
4095
  const lines = raw.split("\n");
@@ -3990,18 +4119,18 @@ async function expandGlob(cwd, glob) {
3990
4119
  if (parts.length !== 2 || parts[1] !== "*") {
3991
4120
  return [];
3992
4121
  }
3993
- const parentDir = join14(cwd, parts[0]);
4122
+ const parentDir = join15(cwd, parts[0]);
3994
4123
  let dirs;
3995
4124
  try {
3996
4125
  const entries = await readdir3(parentDir, { withFileTypes: true });
3997
- dirs = entries.filter((e) => e.isDirectory()).map((e) => join14(parentDir, e.name));
4126
+ dirs = entries.filter((e) => e.isDirectory()).map((e) => join15(parentDir, e.name));
3998
4127
  } catch {
3999
4128
  return [];
4000
4129
  }
4001
4130
  const results = [];
4002
4131
  for (const dir of dirs) {
4003
4132
  try {
4004
- await access4(join14(dir, "package.json"));
4133
+ await access4(join15(dir, "package.json"));
4005
4134
  results.push(dir);
4006
4135
  } catch {
4007
4136
  }
@@ -4012,7 +4141,7 @@ async function buildPackageMap(cwd) {
4012
4141
  const map = /* @__PURE__ */ new Map();
4013
4142
  let globs = [];
4014
4143
  try {
4015
- const raw = await readFile13(join14(cwd, "pnpm-workspace.yaml"), "utf-8");
4144
+ const raw = await readFile14(join15(cwd, "pnpm-workspace.yaml"), "utf-8");
4016
4145
  globs = parsePnpmWorkspaceGlobs(raw);
4017
4146
  } catch {
4018
4147
  }
@@ -4020,7 +4149,7 @@ async function buildPackageMap(cwd) {
4020
4149
  const dirs = await expandGlob(cwd, glob);
4021
4150
  for (const dir of dirs) {
4022
4151
  try {
4023
- const pkgRaw = await readFile13(join14(dir, "package.json"), "utf-8");
4152
+ const pkgRaw = await readFile14(join15(dir, "package.json"), "utf-8");
4024
4153
  const pkg = JSON.parse(pkgRaw);
4025
4154
  const name = pkg.name ?? relative3(cwd, dir);
4026
4155
  map.set(dir, { name, dir });
@@ -4110,7 +4239,7 @@ function injectVersion(raw, nextVersion, filePath) {
4110
4239
  async function prependChangelog(changelogPath, packageName, nextVersion, now, dryRun) {
4111
4240
  let existing;
4112
4241
  try {
4113
- existing = await readFile13(changelogPath, "utf-8");
4242
+ existing = await readFile14(changelogPath, "utf-8");
4114
4243
  } catch {
4115
4244
  return false;
4116
4245
  }
@@ -4123,7 +4252,7 @@ async function prependChangelog(changelogPath, packageName, nextVersion, now, dr
4123
4252
  `;
4124
4253
  const updated = entry + existing;
4125
4254
  if (!dryRun) {
4126
- await writeFile10(changelogPath, updated, "utf-8");
4255
+ await writeFile11(changelogPath, updated, "utf-8");
4127
4256
  }
4128
4257
  return true;
4129
4258
  }
@@ -4149,7 +4278,7 @@ async function runBump(opts) {
4149
4278
  const changedFiles = (diffResult.stdout ?? "").trim().split("\n").filter(Boolean).map((f) => resolve2(cwd, f));
4150
4279
  const packageMap = await buildPackageMap(cwd);
4151
4280
  const packageDirs = Array.from(packageMap.keys());
4152
- const rootPkgPath = join14(cwd, "package.json");
4281
+ const rootPkgPath = join15(cwd, "package.json");
4153
4282
  const changedPackageDirs = /* @__PURE__ */ new Set();
4154
4283
  for (const absFile of changedFiles) {
4155
4284
  const owner = findOwningPackage(absFile, packageDirs);
@@ -4160,19 +4289,19 @@ async function runBump(opts) {
4160
4289
  const entries = [];
4161
4290
  for (const pkgDir of changedPackageDirs) {
4162
4291
  const pkgInfo = packageMap.get(pkgDir);
4163
- const pkgJsonPath = join14(pkgDir, "package.json");
4292
+ const pkgJsonPath = join15(pkgDir, "package.json");
4164
4293
  if (pkgJsonPath === rootPkgPath) continue;
4165
4294
  const versionFileCandidates = [
4166
4295
  { abs: pkgJsonPath, rel: relative3(cwd, pkgJsonPath).replace(/\\/g, "/") }
4167
4296
  ];
4168
- const tauriConfPath = join14(pkgDir, "src-tauri", "tauri.conf.json");
4297
+ const tauriConfPath = join15(pkgDir, "src-tauri", "tauri.conf.json");
4169
4298
  const tauriRelPath = relative3(cwd, tauriConfPath).replace(/\\/g, "/");
4170
4299
  try {
4171
4300
  await access4(tauriConfPath);
4172
4301
  versionFileCandidates.push({ abs: tauriConfPath, rel: tauriRelPath });
4173
4302
  } catch {
4174
4303
  }
4175
- const appJsonPath = join14(pkgDir, "app.json");
4304
+ const appJsonPath = join15(pkgDir, "app.json");
4176
4305
  const appJsonRelPath = relative3(cwd, appJsonPath).replace(/\\/g, "/");
4177
4306
  try {
4178
4307
  await access4(appJsonPath);
@@ -4181,7 +4310,7 @@ async function runBump(opts) {
4181
4310
  }
4182
4311
  let currentPkgJsonRaw;
4183
4312
  try {
4184
- currentPkgJsonRaw = await readFile13(pkgJsonPath, "utf-8");
4313
+ currentPkgJsonRaw = await readFile14(pkgJsonPath, "utf-8");
4185
4314
  } catch (err) {
4186
4315
  console.warn(
4187
4316
  `runBump: could not read ${pkgJsonPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -4226,7 +4355,7 @@ async function runBump(opts) {
4226
4355
  for (const { abs, rel } of versionFileCandidates) {
4227
4356
  let raw;
4228
4357
  try {
4229
- raw = await readFile13(abs, "utf-8");
4358
+ raw = await readFile14(abs, "utf-8");
4230
4359
  } catch {
4231
4360
  continue;
4232
4361
  }
@@ -4240,11 +4369,11 @@ async function runBump(opts) {
4240
4369
  }
4241
4370
  const updated = injectVersion(raw, nextVersion, abs);
4242
4371
  if (!dryRun) {
4243
- await writeFile10(abs, updated, "utf-8");
4372
+ await writeFile11(abs, updated, "utf-8");
4244
4373
  }
4245
4374
  updatedVersionFiles.push(rel);
4246
4375
  }
4247
- const changelogPath = join14(pkgDir, "CHANGELOG.md");
4376
+ const changelogPath = join15(pkgDir, "CHANGELOG.md");
4248
4377
  const changelogUpdated = await prependChangelog(
4249
4378
  changelogPath,
4250
4379
  pkgInfo.name,
@@ -5612,6 +5741,7 @@ function distress(kind, message, jsonMode) {
5612
5741
  }
5613
5742
  async function runResolveWorktree() {
5614
5743
  const jsonMode = hasFlag("json", 3);
5744
+ const cacheMode = hasFlag("cache", 3);
5615
5745
  let errorContext = null;
5616
5746
  const migrationNoticeCallback = (legacyPath, primaryPath) => {
5617
5747
  if (!jsonMode) {
@@ -5625,7 +5755,7 @@ async function runResolveWorktree() {
5625
5755
  const projectPath = process.cwd();
5626
5756
  const found = await findCodebyplanConfig(projectPath);
5627
5757
  if (!found?.contents.repo_id) {
5628
- emitAndExit(null, null, jsonMode);
5758
+ emitAndExit(null, null, jsonMode, cacheMode ? false : void 0);
5629
5759
  }
5630
5760
  const repoId = found.contents.repo_id;
5631
5761
  try {
@@ -5656,7 +5786,7 @@ async function runResolveWorktree() {
5656
5786
  message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
5657
5787
  };
5658
5788
  }
5659
- emitAndExit(null, errorContext, jsonMode);
5789
+ emitAndExit(null, errorContext, jsonMode, cacheMode ? false : void 0);
5660
5790
  }
5661
5791
  let branch = "";
5662
5792
  try {
@@ -5685,7 +5815,12 @@ async function runResolveWorktree() {
5685
5815
  onError: onResolverError
5686
5816
  });
5687
5817
  if (worktreeId) {
5688
- emitAndExit(worktreeId, errorContext, jsonMode);
5818
+ const cacheWritten = cacheMode ? await writeWorktreeCache(projectPath, {
5819
+ worktree_id: worktreeId,
5820
+ branch,
5821
+ device_id: deviceId
5822
+ }) : void 0;
5823
+ emitAndExit(worktreeId, errorContext, jsonMode, cacheWritten);
5689
5824
  }
5690
5825
  const useFallback = hasFlag("fallback-from-branch", 3);
5691
5826
  if (useFallback) {
@@ -5696,23 +5831,33 @@ async function runResolveWorktree() {
5696
5831
  onError: onResolverError
5697
5832
  });
5698
5833
  if (fallbackId) {
5699
- emitAndExit(fallbackId, errorContext, jsonMode);
5834
+ const cacheWritten = cacheMode ? await writeWorktreeCache(projectPath, {
5835
+ worktree_id: fallbackId,
5836
+ branch,
5837
+ device_id: deviceId
5838
+ }) : void 0;
5839
+ emitAndExit(fallbackId, errorContext, jsonMode, cacheWritten);
5700
5840
  }
5701
5841
  }
5702
- emitAndExit(null, errorContext, jsonMode);
5842
+ emitAndExit(null, errorContext, jsonMode, cacheMode ? false : void 0);
5703
5843
  } catch (err) {
5704
5844
  if (err instanceof ProcessExitSignal) throw err;
5705
5845
  const msg = err instanceof Error ? err.message : String(err);
5706
5846
  errorContext = { kind: "unhandled", message: msg };
5707
- emitAndExit(null, errorContext, jsonMode);
5847
+ emitAndExit(null, errorContext, jsonMode, cacheMode ? false : void 0);
5708
5848
  }
5709
5849
  }
5710
- function emitAndExit(worktreeId, errorContext, jsonMode) {
5850
+ function emitAndExit(worktreeId, errorContext, jsonMode, cacheWritten) {
5711
5851
  if (jsonMode) {
5712
5852
  const errorKind = errorContext?.kind ?? (worktreeId === null ? "tuple_miss" : null);
5713
- process.stdout.write(
5714
- JSON.stringify({ worktree_id: worktreeId, error_kind: errorKind }) + "\n"
5715
- );
5853
+ const payload = {
5854
+ worktree_id: worktreeId,
5855
+ error_kind: errorKind
5856
+ };
5857
+ if (cacheWritten !== void 0) {
5858
+ payload.cache_written = cacheWritten;
5859
+ }
5860
+ process.stdout.write(JSON.stringify(payload) + "\n");
5716
5861
  } else {
5717
5862
  if (worktreeId !== null) {
5718
5863
  process.stdout.write(worktreeId);
@@ -5731,7 +5876,177 @@ var init_resolve_worktree2 = __esm({
5731
5876
  init_flags();
5732
5877
  init_local_config();
5733
5878
  init_resolve_worktree();
5879
+ init_worktree_cache();
5880
+ init_process_exit_signal();
5881
+ }
5882
+ });
5883
+
5884
+ // src/cli/version-status.ts
5885
+ var version_status_exports = {};
5886
+ __export(version_status_exports, {
5887
+ runVersionStatus: () => runVersionStatus
5888
+ });
5889
+ import { execFileSync, execSync as execSync6 } from "node:child_process";
5890
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
5891
+ import { dirname as dirname8, join as join20 } from "node:path";
5892
+ function fetchLatestVersion() {
5893
+ try {
5894
+ return execFileSync("npm", ["view", "codebyplan", "version"], {
5895
+ encoding: "utf-8",
5896
+ stdio: ["pipe", "pipe", "pipe"]
5897
+ }).trim() || null;
5898
+ } catch {
5899
+ return null;
5900
+ }
5901
+ }
5902
+ function resolveGitRoot() {
5903
+ try {
5904
+ return execSync6("git rev-parse --show-toplevel", {
5905
+ encoding: "utf-8"
5906
+ }).trim();
5907
+ } catch {
5908
+ return null;
5909
+ }
5910
+ }
5911
+ function resolveCurrentBranch() {
5912
+ try {
5913
+ return execSync6("git symbolic-ref --short HEAD", {
5914
+ encoding: "utf-8"
5915
+ }).trim();
5916
+ } catch {
5917
+ try {
5918
+ return execSync6("git rev-parse --abbrev-ref HEAD", {
5919
+ encoding: "utf-8"
5920
+ }).trim();
5921
+ } catch {
5922
+ return "";
5923
+ }
5924
+ }
5925
+ }
5926
+ function detectPackageManager2(gitRoot) {
5927
+ let dir = process.cwd();
5928
+ const stopAt = gitRoot ?? null;
5929
+ while (true) {
5930
+ if (existsSync4(join20(dir, "pnpm-lock.yaml"))) return "pnpm";
5931
+ if (existsSync4(join20(dir, "yarn.lock"))) return "yarn";
5932
+ if (existsSync4(join20(dir, "package-lock.json"))) return "npm";
5933
+ if (stopAt !== null && dir === stopAt) break;
5934
+ const parent = dirname8(dir);
5935
+ if (parent === dir) break;
5936
+ dir = parent;
5937
+ }
5938
+ return "npm";
5939
+ }
5940
+ function buildInstallCommand2(pm) {
5941
+ switch (pm) {
5942
+ case "pnpm":
5943
+ return "pnpm add codebyplan@latest";
5944
+ case "yarn":
5945
+ return "yarn add codebyplan@latest";
5946
+ case "npm":
5947
+ return "npm install codebyplan@latest";
5948
+ }
5949
+ }
5950
+ async function resolveGuard(gitRoot, currentBranch) {
5951
+ if (gitRoot !== null) {
5952
+ const canonicalPkgPath = join20(
5953
+ gitRoot,
5954
+ "packages",
5955
+ "codebyplan-package",
5956
+ "package.json"
5957
+ );
5958
+ try {
5959
+ if (existsSync4(canonicalPkgPath)) {
5960
+ const raw = readFileSync5(canonicalPkgPath, "utf-8");
5961
+ const parsed = JSON.parse(raw);
5962
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && parsed["name"] === "codebyplan") {
5963
+ return { guarded: true, guardReason: "canonical_source" };
5964
+ }
5965
+ }
5966
+ } catch {
5967
+ }
5968
+ }
5969
+ let protectedBranches = [];
5970
+ try {
5971
+ const found = await findCodebyplanConfig(process.cwd());
5972
+ if (found) {
5973
+ let gitJsonPath;
5974
+ if (found.path.endsWith("/repo.json")) {
5975
+ const dir = found.path.slice(0, found.path.lastIndexOf("/"));
5976
+ gitJsonPath = join20(dir, "git.json");
5977
+ } else {
5978
+ const legacyDir = found.path.slice(0, found.path.lastIndexOf("/"));
5979
+ gitJsonPath = join20(legacyDir, ".codebyplan", "git.json");
5980
+ }
5981
+ const raw = readFileSync5(gitJsonPath, "utf-8");
5982
+ const parsed = JSON.parse(raw);
5983
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
5984
+ const bc = parsed["branch_config"];
5985
+ if (typeof bc === "object" && bc !== null && !Array.isArray(bc)) {
5986
+ const protectedField = bc["protected"];
5987
+ if (Array.isArray(protectedField)) {
5988
+ protectedBranches = protectedField.filter(
5989
+ (v) => typeof v === "string"
5990
+ );
5991
+ }
5992
+ }
5993
+ }
5994
+ }
5995
+ } catch {
5996
+ protectedBranches = [];
5997
+ }
5998
+ const isProtected = currentBranch === "main" || currentBranch !== "" && protectedBranches.includes(currentBranch);
5999
+ if (isProtected) {
6000
+ return { guarded: true, guardReason: "protected_branch" };
6001
+ }
6002
+ return { guarded: false, guardReason: null };
6003
+ }
6004
+ async function runVersionStatus() {
6005
+ try {
6006
+ const installed = VERSION;
6007
+ const latest = fetchLatestVersion();
6008
+ const newer = latest !== null && compareSemver(latest, installed) > 0;
6009
+ const gitRoot = resolveGitRoot();
6010
+ const pm = detectPackageManager2(gitRoot);
6011
+ const installCommand = buildInstallCommand2(pm);
6012
+ const currentBranch = resolveCurrentBranch();
6013
+ const { guarded, guardReason } = await resolveGuard(gitRoot, currentBranch);
6014
+ const result = {
6015
+ installed,
6016
+ latest,
6017
+ newer,
6018
+ packageManager: pm,
6019
+ installCommand,
6020
+ guarded,
6021
+ guardReason
6022
+ };
6023
+ process.stdout.write(JSON.stringify(result) + "\n");
6024
+ process.exit(0);
6025
+ } catch (err) {
6026
+ if (err instanceof ProcessExitSignal) throw err;
6027
+ process.stdout.write(JSON.stringify(FAIL_SAFE) + "\n");
6028
+ process.exit(0);
6029
+ }
6030
+ }
6031
+ var FAIL_SAFE;
6032
+ var init_version_status = __esm({
6033
+ "src/cli/version-status.ts"() {
6034
+ "use strict";
6035
+ init_version();
6036
+ init_bump();
6037
+ init_flags();
5734
6038
  init_process_exit_signal();
6039
+ FAIL_SAFE = {
6040
+ installed: VERSION,
6041
+ latest: null,
6042
+ newer: false,
6043
+ packageManager: "npm",
6044
+ installCommand: "npm install codebyplan@latest",
6045
+ guarded: true,
6046
+ // Detection failed entirely — guard conservatively with an explicit reason
6047
+ // so consumers never see guarded:true paired with a null reason.
6048
+ guardReason: "unknown"
6049
+ };
5735
6050
  }
5736
6051
  });
5737
6052
 
@@ -5740,7 +6055,7 @@ var cmux_sync_exports = {};
5740
6055
  __export(cmux_sync_exports, {
5741
6056
  runCmuxSync: () => runCmuxSync
5742
6057
  });
5743
- import { execSync as execSync6, execFileSync } from "node:child_process";
6058
+ import { execSync as execSync7, execFileSync as execFileSync2 } from "node:child_process";
5744
6059
  import { basename } from "node:path";
5745
6060
  async function runCmuxSync() {
5746
6061
  try {
@@ -5750,14 +6065,14 @@ async function runCmuxSync() {
5750
6065
  const bin = process.env.CMUX_BUNDLED_CLI_PATH || process.env.CMUX_CLAUDE_HOOK_CMUX_BIN || "cmux";
5751
6066
  let branch = "";
5752
6067
  try {
5753
- branch = execSync6("git rev-parse --abbrev-ref HEAD", {
6068
+ branch = execSync7("git rev-parse --abbrev-ref HEAD", {
5754
6069
  encoding: "utf8"
5755
6070
  }).trim();
5756
6071
  } catch {
5757
6072
  }
5758
6073
  let folder = "";
5759
6074
  try {
5760
- const toplevel = execSync6("git rev-parse --show-toplevel", {
6075
+ const toplevel = execSync7("git rev-parse --show-toplevel", {
5761
6076
  encoding: "utf8"
5762
6077
  }).trim();
5763
6078
  folder = basename(toplevel);
@@ -5765,7 +6080,7 @@ async function runCmuxSync() {
5765
6080
  }
5766
6081
  if (branch) {
5767
6082
  try {
5768
- execFileSync(bin, [
6083
+ execFileSync2(bin, [
5769
6084
  "workspace-action",
5770
6085
  "--action",
5771
6086
  "rename",
@@ -5777,7 +6092,7 @@ async function runCmuxSync() {
5777
6092
  }
5778
6093
  if (folder) {
5779
6094
  try {
5780
- execFileSync(bin, [
6095
+ execFileSync2(bin, [
5781
6096
  "workspace-action",
5782
6097
  "--action",
5783
6098
  "set-description",
@@ -5801,19 +6116,19 @@ var init_cmux_sync = __esm({
5801
6116
  });
5802
6117
 
5803
6118
  // src/lib/migrate-local-config.ts
5804
- import { mkdir as mkdir5, readFile as readFile14, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
5805
- import { join as join19 } from "node:path";
6119
+ import { mkdir as mkdir6, readFile as readFile15, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
6120
+ import { join as join21 } from "node:path";
5806
6121
  function legacySharedPath(projectPath) {
5807
- return join19(projectPath, ".codebyplan.json");
6122
+ return join21(projectPath, ".codebyplan.json");
5808
6123
  }
5809
6124
  function legacyLocalPath(projectPath) {
5810
- return join19(projectPath, ".codebyplan.local.json");
6125
+ return join21(projectPath, ".codebyplan.local.json");
5811
6126
  }
5812
6127
  function newDirPath(projectPath) {
5813
- return join19(projectPath, ".codebyplan");
6128
+ return join21(projectPath, ".codebyplan");
5814
6129
  }
5815
6130
  function sentinelPath(projectPath) {
5816
- return join19(projectPath, ".codebyplan", "repo.json");
6131
+ return join21(projectPath, ".codebyplan", "repo.json");
5817
6132
  }
5818
6133
  async function statSafe(p) {
5819
6134
  const { stat: stat2 } = await import("node:fs/promises");
@@ -5852,7 +6167,7 @@ async function runLocalMigration(projectPath) {
5852
6167
  }
5853
6168
  let legacyRaw;
5854
6169
  try {
5855
- legacyRaw = await readFile14(legacySharedPath(projectPath), "utf-8");
6170
+ legacyRaw = await readFile15(legacySharedPath(projectPath), "utf-8");
5856
6171
  } catch {
5857
6172
  return {
5858
6173
  migrated: true,
@@ -5879,7 +6194,7 @@ async function runLocalMigration(projectPath) {
5879
6194
  let deviceId;
5880
6195
  let deviceWrittenByHelper = false;
5881
6196
  try {
5882
- const localRaw = await readFile14(legacyLocalPath(projectPath), "utf-8");
6197
+ const localRaw = await readFile15(legacyLocalPath(projectPath), "utf-8");
5883
6198
  const localParsed = JSON.parse(localRaw);
5884
6199
  if (typeof localParsed.device_id === "string") {
5885
6200
  deviceId = localParsed.device_id;
@@ -5887,7 +6202,7 @@ async function runLocalMigration(projectPath) {
5887
6202
  } catch {
5888
6203
  }
5889
6204
  try {
5890
- await mkdir5(newDirPath(projectPath), { recursive: true });
6205
+ await mkdir6(newDirPath(projectPath), { recursive: true });
5891
6206
  } catch (err) {
5892
6207
  const code = err.code;
5893
6208
  if (code === "ENOTDIR" || code === "EEXIST") {
@@ -5906,8 +6221,8 @@ async function runLocalMigration(projectPath) {
5906
6221
  if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
5907
6222
  if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
5908
6223
  if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
5909
- await writeFile11(
5910
- join19(projectPath, ".codebyplan", "repo.json"),
6224
+ await writeFile12(
6225
+ join21(projectPath, ".codebyplan", "repo.json"),
5911
6226
  JSON.stringify(repoJson, null, 2) + "\n",
5912
6227
  "utf-8"
5913
6228
  );
@@ -5919,8 +6234,8 @@ async function runLocalMigration(projectPath) {
5919
6234
  serverJson.auto_push_enabled = cfg.auto_push_enabled;
5920
6235
  if ("port_allocations" in cfg)
5921
6236
  serverJson.port_allocations = cfg.port_allocations;
5922
- await writeFile11(
5923
- join19(projectPath, ".codebyplan", "server.json"),
6237
+ await writeFile12(
6238
+ join21(projectPath, ".codebyplan", "server.json"),
5924
6239
  JSON.stringify(serverJson, null, 2) + "\n",
5925
6240
  "utf-8"
5926
6241
  );
@@ -5928,44 +6243,44 @@ async function runLocalMigration(projectPath) {
5928
6243
  const gitJson = {};
5929
6244
  if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
5930
6245
  if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
5931
- await writeFile11(
5932
- join19(projectPath, ".codebyplan", "git.json"),
6246
+ await writeFile12(
6247
+ join21(projectPath, ".codebyplan", "git.json"),
5933
6248
  JSON.stringify(gitJson, null, 2) + "\n",
5934
6249
  "utf-8"
5935
6250
  );
5936
6251
  filesChanged.push(".codebyplan/git.json");
5937
6252
  const shipmentJson = {};
5938
6253
  if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
5939
- await writeFile11(
5940
- join19(projectPath, ".codebyplan", "shipment.json"),
6254
+ await writeFile12(
6255
+ join21(projectPath, ".codebyplan", "shipment.json"),
5941
6256
  JSON.stringify(shipmentJson, null, 2) + "\n",
5942
6257
  "utf-8"
5943
6258
  );
5944
6259
  filesChanged.push(".codebyplan/shipment.json");
5945
6260
  const vendorJson = {};
5946
- await writeFile11(
5947
- join19(projectPath, ".codebyplan", "vendor.json"),
6261
+ await writeFile12(
6262
+ join21(projectPath, ".codebyplan", "vendor.json"),
5948
6263
  JSON.stringify(vendorJson, null, 2) + "\n",
5949
6264
  "utf-8"
5950
6265
  );
5951
6266
  filesChanged.push(".codebyplan/vendor.json");
5952
6267
  const e2eJson = {};
5953
- await writeFile11(
5954
- join19(projectPath, ".codebyplan", "e2e.json"),
6268
+ await writeFile12(
6269
+ join21(projectPath, ".codebyplan", "e2e.json"),
5955
6270
  JSON.stringify(e2eJson, null, 2) + "\n",
5956
6271
  "utf-8"
5957
6272
  );
5958
6273
  filesChanged.push(".codebyplan/e2e.json");
5959
6274
  const eslintJson = {};
5960
- await writeFile11(
5961
- join19(projectPath, ".codebyplan", "eslint.json"),
6275
+ await writeFile12(
6276
+ join21(projectPath, ".codebyplan", "eslint.json"),
5962
6277
  JSON.stringify(eslintJson, null, 2) + "\n",
5963
6278
  "utf-8"
5964
6279
  );
5965
6280
  filesChanged.push(".codebyplan/eslint.json");
5966
6281
  if (!deviceWrittenByHelper) {
5967
- await writeFile11(
5968
- join19(projectPath, ".codebyplan", "device.local.json"),
6282
+ await writeFile12(
6283
+ join21(projectPath, ".codebyplan", "device.local.json"),
5969
6284
  JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
5970
6285
  "utf-8"
5971
6286
  );
@@ -5977,9 +6292,9 @@ async function runLocalMigration(projectPath) {
5977
6292
  "Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
5978
6293
  );
5979
6294
  }
5980
- const gitignorePath = join19(projectPath, ".gitignore");
6295
+ const gitignorePath = join21(projectPath, ".gitignore");
5981
6296
  try {
5982
- const gitignoreContent = await readFile14(gitignorePath, "utf-8");
6297
+ const gitignoreContent = await readFile15(gitignorePath, "utf-8");
5983
6298
  const legacyLine = ".codebyplan.local.json";
5984
6299
  const newLine = ".codebyplan/device.local.json";
5985
6300
  const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
@@ -5998,7 +6313,7 @@ async function runLocalMigration(projectPath) {
5998
6313
  updated = gitignoreContent;
5999
6314
  }
6000
6315
  if (updated !== gitignoreContent) {
6001
- await writeFile11(gitignorePath, updated, "utf-8");
6316
+ await writeFile12(gitignorePath, updated, "utf-8");
6002
6317
  filesChanged.push(".gitignore");
6003
6318
  }
6004
6319
  } catch {
@@ -6038,8 +6353,8 @@ __export(config_exports, {
6038
6353
  readVendorConfig: () => readVendorConfig,
6039
6354
  runConfig: () => runConfig
6040
6355
  });
6041
- import { mkdir as mkdir6, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
6042
- import { join as join20 } from "node:path";
6356
+ import { mkdir as mkdir7, readFile as readFile16, writeFile as writeFile13 } from "node:fs/promises";
6357
+ import { join as join22 } from "node:path";
6043
6358
  async function runConfig() {
6044
6359
  const flags = parseFlags(3);
6045
6360
  const dryRun = hasFlag("dry-run", 3);
@@ -6072,14 +6387,14 @@ async function runConfig() {
6072
6387
  console.log("\n Config complete.\n");
6073
6388
  }
6074
6389
  async function syncConfigToFile(repoId, projectPath, dryRun) {
6075
- const codebyplanDir = join20(projectPath, ".codebyplan");
6390
+ const codebyplanDir = join22(projectPath, ".codebyplan");
6076
6391
  let resolvedWorktreeId;
6077
6392
  try {
6078
6393
  const deviceId = await getOrCreateDeviceId(projectPath);
6079
6394
  let branch = "main";
6080
6395
  try {
6081
- const { execSync: execSync7 } = await import("node:child_process");
6082
- branch = execSync7("git symbolic-ref --short HEAD", {
6396
+ const { execSync: execSync8 } = await import("node:child_process");
6397
+ branch = execSync8("git symbolic-ref --short HEAD", {
6083
6398
  cwd: projectPath,
6084
6399
  encoding: "utf-8"
6085
6400
  }).trim();
@@ -6203,7 +6518,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
6203
6518
  console.log(" Config would be updated (dry-run).");
6204
6519
  return;
6205
6520
  }
6206
- await mkdir6(codebyplanDir, { recursive: true });
6521
+ await mkdir7(codebyplanDir, { recursive: true });
6207
6522
  const files = [
6208
6523
  { name: "repo.json", payload: repoPayload },
6209
6524
  { name: "server.json", payload: serverPayload },
@@ -6215,16 +6530,16 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
6215
6530
  ];
6216
6531
  let anyUpdated = false;
6217
6532
  for (const { name, payload, createOnly } of files) {
6218
- const filePath = join20(codebyplanDir, name);
6533
+ const filePath = join22(codebyplanDir, name);
6219
6534
  const newJson = JSON.stringify(payload, null, 2) + "\n";
6220
6535
  let currentJson = "";
6221
6536
  try {
6222
- currentJson = await readFile15(filePath, "utf-8");
6537
+ currentJson = await readFile16(filePath, "utf-8");
6223
6538
  } catch {
6224
6539
  }
6225
6540
  if (createOnly && currentJson !== "") continue;
6226
6541
  if (currentJson === newJson) continue;
6227
- await writeFile12(filePath, newJson, "utf-8");
6542
+ await writeFile13(filePath, newJson, "utf-8");
6228
6543
  console.log(` Updated .codebyplan/${name}`);
6229
6544
  anyUpdated = true;
6230
6545
  }
@@ -6234,8 +6549,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
6234
6549
  }
6235
6550
  async function readRepoConfig(projectPath) {
6236
6551
  try {
6237
- const raw = await readFile15(
6238
- join20(projectPath, ".codebyplan", "repo.json"),
6552
+ const raw = await readFile16(
6553
+ join22(projectPath, ".codebyplan", "repo.json"),
6239
6554
  "utf-8"
6240
6555
  );
6241
6556
  return JSON.parse(raw);
@@ -6245,8 +6560,8 @@ async function readRepoConfig(projectPath) {
6245
6560
  }
6246
6561
  async function readServerConfig(projectPath) {
6247
6562
  try {
6248
- const raw = await readFile15(
6249
- join20(projectPath, ".codebyplan", "server.json"),
6563
+ const raw = await readFile16(
6564
+ join22(projectPath, ".codebyplan", "server.json"),
6250
6565
  "utf-8"
6251
6566
  );
6252
6567
  return JSON.parse(raw);
@@ -6256,8 +6571,8 @@ async function readServerConfig(projectPath) {
6256
6571
  }
6257
6572
  async function readGitConfig(projectPath) {
6258
6573
  try {
6259
- const raw = await readFile15(
6260
- join20(projectPath, ".codebyplan", "git.json"),
6574
+ const raw = await readFile16(
6575
+ join22(projectPath, ".codebyplan", "git.json"),
6261
6576
  "utf-8"
6262
6577
  );
6263
6578
  return JSON.parse(raw);
@@ -6267,8 +6582,8 @@ async function readGitConfig(projectPath) {
6267
6582
  }
6268
6583
  async function readShipmentConfig(projectPath) {
6269
6584
  try {
6270
- const raw = await readFile15(
6271
- join20(projectPath, ".codebyplan", "shipment.json"),
6585
+ const raw = await readFile16(
6586
+ join22(projectPath, ".codebyplan", "shipment.json"),
6272
6587
  "utf-8"
6273
6588
  );
6274
6589
  return JSON.parse(raw);
@@ -6278,8 +6593,8 @@ async function readShipmentConfig(projectPath) {
6278
6593
  }
6279
6594
  async function readVendorConfig(projectPath) {
6280
6595
  try {
6281
- const raw = await readFile15(
6282
- join20(projectPath, ".codebyplan", "vendor.json"),
6596
+ const raw = await readFile16(
6597
+ join22(projectPath, ".codebyplan", "vendor.json"),
6283
6598
  "utf-8"
6284
6599
  );
6285
6600
  return JSON.parse(raw);
@@ -6289,8 +6604,8 @@ async function readVendorConfig(projectPath) {
6289
6604
  }
6290
6605
  async function readE2eConfig(projectPath) {
6291
6606
  try {
6292
- const raw = await readFile15(
6293
- join20(projectPath, ".codebyplan", "e2e.json"),
6607
+ const raw = await readFile16(
6608
+ join22(projectPath, ".codebyplan", "e2e.json"),
6294
6609
  "utf-8"
6295
6610
  );
6296
6611
  return JSON.parse(raw);
@@ -6346,14 +6661,14 @@ var init_server_detect = __esm({
6346
6661
  });
6347
6662
 
6348
6663
  // src/lib/port-verify.ts
6349
- import { readFile as readFile16 } from "node:fs/promises";
6664
+ import { readFile as readFile17 } from "node:fs/promises";
6350
6665
  async function verifyPorts(projectPath, portAllocations) {
6351
6666
  const mismatches = [];
6352
6667
  const allocatedPorts = new Set(portAllocations.map((a) => a.port));
6353
6668
  const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
6354
6669
  for (const pkgPath of packageJsonPaths) {
6355
6670
  try {
6356
- const raw = await readFile16(pkgPath, "utf-8");
6671
+ const raw = await readFile17(pkgPath, "utf-8");
6357
6672
  const pkg = JSON.parse(raw);
6358
6673
  const scriptPort = detectPortFromScripts(pkg);
6359
6674
  if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
@@ -6416,7 +6731,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
6416
6731
  }
6417
6732
  let pkg;
6418
6733
  try {
6419
- const raw = await readFile16(`${app.absPath}/package.json`, "utf-8");
6734
+ const raw = await readFile17(`${app.absPath}/package.json`, "utf-8");
6420
6735
  pkg = JSON.parse(raw);
6421
6736
  } catch {
6422
6737
  continue;
@@ -6562,7 +6877,7 @@ __export(tech_stack_exports, {
6562
6877
  runFullTechStack: () => runFullTechStack,
6563
6878
  runTechStack: () => runTechStack
6564
6879
  });
6565
- import { existsSync as existsSync4 } from "node:fs";
6880
+ import { existsSync as existsSync5 } from "node:fs";
6566
6881
  async function runTechStack() {
6567
6882
  const flags = parseFlags(3);
6568
6883
  const dryRun = hasFlag("dry-run", 3);
@@ -6624,10 +6939,10 @@ async function runTechStack() {
6624
6939
  );
6625
6940
  }
6626
6941
  try {
6627
- const { execSync: execSync7 } = await import("node:child_process");
6942
+ const { execSync: execSync8 } = await import("node:child_process");
6628
6943
  let branch = "main";
6629
6944
  try {
6630
- branch = execSync7("git symbolic-ref --short HEAD", {
6945
+ branch = execSync8("git symbolic-ref --short HEAD", {
6631
6946
  cwd: projectPath,
6632
6947
  encoding: "utf-8"
6633
6948
  }).trim();
@@ -6735,7 +7050,7 @@ async function runFullTechStack(dryRun) {
6735
7050
  continue;
6736
7051
  }
6737
7052
  const localWorktrees = worktrees.filter(
6738
- (wt) => wt.path ? existsSync4(wt.path) : false
7053
+ (wt) => wt.path ? existsSync5(wt.path) : false
6739
7054
  );
6740
7055
  if (localWorktrees.length === 0) {
6741
7056
  console.log(` skipping ${repo.name} \u2014 no local worktree on this device`);
@@ -7418,13 +7733,13 @@ var init_uninstall = __esm({
7418
7733
 
7419
7734
  // src/index.ts
7420
7735
  init_version();
7421
- import { readFileSync as readFileSync7 } from "node:fs";
7736
+ import { readFileSync as readFileSync8 } from "node:fs";
7422
7737
  import { resolve as resolve6 } from "node:path";
7423
7738
  void (async () => {
7424
7739
  if (!process.env.CODEBYPLAN_API_KEY) {
7425
7740
  try {
7426
7741
  const envPath = resolve6(process.cwd(), ".env.local");
7427
- const content = readFileSync7(envPath, "utf-8");
7742
+ const content = readFileSync8(envPath, "utf-8");
7428
7743
  for (const line of content.split("\n")) {
7429
7744
  const trimmed = line.trim();
7430
7745
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -7536,6 +7851,11 @@ void (async () => {
7536
7851
  await runResolveWorktree2();
7537
7852
  process.exit(0);
7538
7853
  }
7854
+ if (arg === "version-status") {
7855
+ const { runVersionStatus: runVersionStatus2 } = await Promise.resolve().then(() => (init_version_status(), version_status_exports));
7856
+ await runVersionStatus2();
7857
+ process.exit(0);
7858
+ }
7539
7859
  if (arg === "cmux-sync") {
7540
7860
  const { runCmuxSync: runCmuxSync2 } = await Promise.resolve().then(() => (init_cmux_sync(), cmux_sync_exports));
7541
7861
  await runCmuxSync2();
@@ -7639,6 +7959,7 @@ void (async () => {
7639
7959
  codebyplan claude Claude asset management (install/update/uninstall)
7640
7960
  codebyplan statusline Show or set the statusline renderer (bash/node/python)
7641
7961
  codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
7962
+ codebyplan version-status Report installed vs latest version + update guard (JSON)
7642
7963
  codebyplan cmux-sync Sync cmux workspace title/description to current git branch and repo folder
7643
7964
  codebyplan help Show this help message
7644
7965
  codebyplan --version Print version
@@ -7677,6 +7998,11 @@ void (async () => {
7677
7998
  --node Set statusline renderer to node after install/update
7678
7999
  --python Set statusline renderer to python after install/update
7679
8000
 
8001
+ Resolve-worktree options:
8002
+ --json Structured JSON output instead of bare UUID
8003
+ --fallback-from-branch Fallback resolver: filter by (device_id, branch) client-side
8004
+ --cache Resolve and write worktree id to .codebyplan/worktree.local.json
8005
+
7680
8006
  MCP Server:
7681
8007
  Claude Code connects to CodeByPlan via remote MCP:
7682
8008
  URL: https://mcp.codebyplan.com/mcp