codebyplan 1.4.2 → 1.4.3

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 +2 -0
  2. package/dist/cli.js +221 -145
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,6 +34,8 @@ Bidirectional sync of `.claude/` infrastructure files between your local project
34
34
  | `--force` | Skip confirmation and conflict prompts |
35
35
  | `--fix` | Auto-create missing port allocations |
36
36
 
37
+ **Side effects:** writes `worktree_id` (UUID) at the top level of `.codebyplan.json`, used as a cached scope for workflow skills. See `.claude/rules/worktree-tagging.md`.
38
+
37
39
  ### `codebyplan help`
38
40
 
39
41
  Show help message.
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.4.2";
17
+ VERSION = "1.4.3";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -143,6 +143,68 @@ var init_api = __esm({
143
143
  }
144
144
  });
145
145
 
146
+ // src/lib/resolve-worktree.ts
147
+ import { readFile, writeFile } from "node:fs/promises";
148
+ import { join } from "node:path";
149
+ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
150
+ let worktreeId;
151
+ try {
152
+ const worktreesRes = await apiGet(`/worktrees?repo_id=${repoId}`);
153
+ const match = worktreesRes.data.find((wt) => {
154
+ const wtPath = wt.path.endsWith("/") ? wt.path.slice(0, -1) : wt.path;
155
+ return projectPath === wtPath || projectPath.startsWith(wtPath + "/");
156
+ });
157
+ if (match) worktreeId = match.id;
158
+ } catch (err) {
159
+ console.error(
160
+ `Worktree lookup failed: ${err instanceof Error ? err.message : String(err)}`
161
+ );
162
+ return void 0;
163
+ }
164
+ if (!worktreeId) {
165
+ return void 0;
166
+ }
167
+ if (options?.skipWrite) {
168
+ return worktreeId;
169
+ }
170
+ const codebyplanPath = join(projectPath, ".codebyplan.json");
171
+ let currentConfig = {};
172
+ try {
173
+ const raw = await readFile(codebyplanPath, "utf-8");
174
+ const parsed = JSON.parse(raw);
175
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
176
+ currentConfig = parsed;
177
+ }
178
+ } catch {
179
+ }
180
+ if (currentConfig.worktree_id === worktreeId) {
181
+ return worktreeId;
182
+ }
183
+ const merged = {
184
+ ...currentConfig,
185
+ worktree_id: worktreeId
186
+ };
187
+ try {
188
+ await writeFile(
189
+ codebyplanPath,
190
+ JSON.stringify(merged, null, 2) + "\n",
191
+ "utf-8"
192
+ );
193
+ } catch (err) {
194
+ console.error(
195
+ `Failed to cache worktree_id in ${codebyplanPath}: ${err instanceof Error ? err.message : String(err)}`
196
+ );
197
+ throw err;
198
+ }
199
+ return worktreeId;
200
+ }
201
+ var init_resolve_worktree = __esm({
202
+ "src/lib/resolve-worktree.ts"() {
203
+ "use strict";
204
+ init_api();
205
+ }
206
+ });
207
+
146
208
  // src/lib/settings-merge.ts
147
209
  function mergeSettings(template, local) {
148
210
  const merged = { ...local };
@@ -208,8 +270,8 @@ var init_settings_merge = __esm({
208
270
  });
209
271
 
210
272
  // src/lib/hook-registry.ts
211
- import { readdir, readFile } from "node:fs/promises";
212
- import { join } from "node:path";
273
+ import { readdir, readFile as readFile2 } from "node:fs/promises";
274
+ import { join as join2 } from "node:path";
213
275
  function parseHookMeta(content) {
214
276
  const lineMatch = content.match(/^#\s*@hook:(.*)$/m);
215
277
  if (!lineMatch) return null;
@@ -231,7 +293,7 @@ async function discoverHooks(hooksDir) {
231
293
  return discovered;
232
294
  }
233
295
  for (const filename of filenames) {
234
- const content = await readFile(join(hooksDir, filename), "utf-8");
296
+ const content = await readFile2(join2(hooksDir, filename), "utf-8");
235
297
  const meta = parseHookMeta(content);
236
298
  if (meta) {
237
299
  discovered.set(filename.replace(/\.sh$/, ""), meta);
@@ -378,45 +440,45 @@ __export(sync_engine_exports, {
378
440
  });
379
441
  import {
380
442
  readdir as readdir2,
381
- readFile as readFile2,
382
- writeFile,
443
+ readFile as readFile3,
444
+ writeFile as writeFile2,
383
445
  unlink,
384
446
  mkdir,
385
447
  rmdir,
386
448
  chmod,
387
449
  stat
388
450
  } from "node:fs/promises";
389
- import { join as join2, dirname } from "node:path";
451
+ import { join as join3, dirname } from "node:path";
390
452
  function getTypeDir(claudeDir, dir) {
391
- if (dir === "commands") return join2(claudeDir, dir, "cbp");
392
- return join2(claudeDir, dir);
453
+ if (dir === "commands") return join3(claudeDir, dir, "cbp");
454
+ return join3(claudeDir, dir);
393
455
  }
394
456
  function getFilePath(claudeDir, typeName, file) {
395
457
  const cfg = typeConfig[typeName];
396
458
  const typeDir = getTypeDir(claudeDir, cfg.dir);
397
459
  if (cfg.subfolder) {
398
- return join2(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
460
+ return join3(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
399
461
  }
400
462
  if (typeName === "command" && file.category) {
401
- return join2(typeDir, file.category, `${file.name}${cfg.ext}`);
463
+ return join3(typeDir, file.category, `${file.name}${cfg.ext}`);
402
464
  }
403
465
  if (typeName === "template") {
404
- return join2(typeDir, file.name);
466
+ return join3(typeDir, file.name);
405
467
  }
406
- return join2(typeDir, `${file.name}${cfg.ext}`);
468
+ return join3(typeDir, `${file.name}${cfg.ext}`);
407
469
  }
408
470
  async function readDirRecursive(dir, base = dir) {
409
471
  const result = /* @__PURE__ */ new Map();
410
472
  try {
411
473
  const entries = await readdir2(dir, { withFileTypes: true });
412
474
  for (const entry of entries) {
413
- const fullPath = join2(dir, entry.name);
475
+ const fullPath = join3(dir, entry.name);
414
476
  if (entry.isDirectory()) {
415
477
  const sub = await readDirRecursive(fullPath, base);
416
478
  for (const [k, v] of sub) result.set(k, v);
417
479
  } else {
418
480
  const relPath = fullPath.slice(base.length + 1);
419
- const fileContent = await readFile2(fullPath, "utf-8");
481
+ const fileContent = await readFile3(fullPath, "utf-8");
420
482
  result.set(relPath, fileContent);
421
483
  }
422
484
  }
@@ -426,7 +488,7 @@ async function readDirRecursive(dir, base = dir) {
426
488
  }
427
489
  async function isGitWorktree(projectPath) {
428
490
  try {
429
- const gitPath = join2(projectPath, ".git");
491
+ const gitPath = join3(projectPath, ".git");
430
492
  const info = await stat(gitPath);
431
493
  return info.isFile();
432
494
  } catch {
@@ -453,7 +515,7 @@ async function executeSyncToLocal(options) {
453
515
  const syncData = syncRes.data;
454
516
  const repoData = repoRes.data;
455
517
  syncData.claude_md = [];
456
- const claudeDir = join2(projectPath, ".claude");
518
+ const claudeDir = join3(projectPath, ".claude");
457
519
  const worktree = await isGitWorktree(projectPath);
458
520
  const byType = {};
459
521
  const totals = { created: 0, updated: 0, deleted: 0, unchanged: 0 };
@@ -489,7 +551,7 @@ async function executeSyncToLocal(options) {
489
551
  remotePathMap.set(relPath, { content: substituted, name: remote.name });
490
552
  }
491
553
  for (const [relPath, { content, name }] of remotePathMap) {
492
- const fullPath = join2(targetDir, relPath);
554
+ const fullPath = join3(targetDir, relPath);
493
555
  const localContent = localFiles.get(relPath);
494
556
  if (localContent === void 0) {
495
557
  const remoteFile = remoteFiles.find((f) => f.name === name);
@@ -501,14 +563,14 @@ async function executeSyncToLocal(options) {
501
563
  });
502
564
  if (!dryRun) {
503
565
  await mkdir(dirname(fullPath), { recursive: true });
504
- await writeFile(fullPath, content, "utf-8");
566
+ await writeFile2(fullPath, content, "utf-8");
505
567
  if (typeName === "hook") await chmod(fullPath, 493);
506
568
  }
507
569
  result.created.push(name);
508
570
  totals.created++;
509
571
  } else if (localContent !== content) {
510
572
  if (!dryRun) {
511
- await writeFile(fullPath, content, "utf-8");
573
+ await writeFile2(fullPath, content, "utf-8");
512
574
  if (typeName === "hook") await chmod(fullPath, 493);
513
575
  }
514
576
  result.updated.push(name);
@@ -520,7 +582,7 @@ async function executeSyncToLocal(options) {
520
582
  }
521
583
  for (const [relPath] of localFiles) {
522
584
  if (!remotePathMap.has(relPath)) {
523
- const fullPath = join2(targetDir, relPath);
585
+ const fullPath = join3(targetDir, relPath);
524
586
  if (!dryRun) {
525
587
  await unlink(fullPath);
526
588
  await removeEmptyParents(fullPath, targetDir);
@@ -535,7 +597,7 @@ async function executeSyncToLocal(options) {
535
597
  {
536
598
  const typeName = "docs_stack";
537
599
  const syncKey = "docs_stack";
538
- const targetDir = join2(projectPath, "docs", "stack");
600
+ const targetDir = join3(projectPath, "docs", "stack");
539
601
  const remoteFiles = syncData[syncKey] ?? [];
540
602
  const result = {
541
603
  created: [],
@@ -549,7 +611,7 @@ async function executeSyncToLocal(options) {
549
611
  const localFiles = await readDirRecursive(targetDir);
550
612
  const remotePathMap = /* @__PURE__ */ new Map();
551
613
  for (const remote of remoteFiles) {
552
- const relPath = remote.category ? join2(remote.category, remote.name) : remote.name;
614
+ const relPath = remote.category ? join3(remote.category, remote.name) : remote.name;
553
615
  const substituted = substituteVariables(remote.content, repoData);
554
616
  remotePathMap.set(relPath, {
555
617
  content: substituted,
@@ -557,18 +619,18 @@ async function executeSyncToLocal(options) {
557
619
  });
558
620
  }
559
621
  for (const [relPath, { content, name }] of remotePathMap) {
560
- const fullPath = join2(targetDir, relPath);
622
+ const fullPath = join3(targetDir, relPath);
561
623
  const localContent = localFiles.get(relPath);
562
624
  if (localContent === void 0) {
563
625
  if (!dryRun) {
564
626
  await mkdir(dirname(fullPath), { recursive: true });
565
- await writeFile(fullPath, content, "utf-8");
627
+ await writeFile2(fullPath, content, "utf-8");
566
628
  }
567
629
  result.created.push(name);
568
630
  totals.created++;
569
631
  } else if (localContent !== content) {
570
632
  if (!dryRun) {
571
- await writeFile(fullPath, content, "utf-8");
633
+ await writeFile2(fullPath, content, "utf-8");
572
634
  }
573
635
  result.updated.push(name);
574
636
  totals.updated++;
@@ -579,7 +641,7 @@ async function executeSyncToLocal(options) {
579
641
  }
580
642
  for (const [relPath] of localFiles) {
581
643
  if (!remotePathMap.has(relPath)) {
582
- const fullPath = join2(targetDir, relPath);
644
+ const fullPath = join3(targetDir, relPath);
583
645
  if (!dryRun) {
584
646
  await unlink(fullPath);
585
647
  await removeEmptyParents(fullPath, targetDir);
@@ -599,8 +661,8 @@ async function executeSyncToLocal(options) {
599
661
  globalSettings = { ...globalSettings, ...parsed };
600
662
  }
601
663
  const specialTypes = {
602
- claude_md: () => join2(projectPath, "CLAUDE.md"),
603
- settings: () => join2(projectPath, ".claude", "settings.json")
664
+ claude_md: () => join3(projectPath, "CLAUDE.md"),
665
+ settings: () => join3(projectPath, ".claude", "settings.json")
604
666
  };
605
667
  for (const [typeName, getPath] of Object.entries(specialTypes)) {
606
668
  const remoteFiles = syncData[typeName] ?? [];
@@ -615,7 +677,7 @@ async function executeSyncToLocal(options) {
615
677
  const remoteContent = substituteVariables(remote.content, repoData);
616
678
  let localContent;
617
679
  try {
618
- localContent = await readFile2(targetPath, "utf-8");
680
+ localContent = await readFile3(targetPath, "utf-8");
619
681
  } catch {
620
682
  }
621
683
  if (typeName === "settings") {
@@ -624,7 +686,7 @@ async function executeSyncToLocal(options) {
624
686
  globalSettings,
625
687
  repoSettings
626
688
  );
627
- const hooksDir = join2(projectPath, ".claude", "hooks");
689
+ const hooksDir = join3(projectPath, ".claude", "hooks");
628
690
  const discovered = await discoverHooks(hooksDir);
629
691
  if (localContent === void 0) {
630
692
  const finalSettings = stripPermissionsAllow(combinedTemplate);
@@ -636,7 +698,7 @@ async function executeSyncToLocal(options) {
636
698
  }
637
699
  if (!dryRun) {
638
700
  await mkdir(dirname(targetPath), { recursive: true });
639
- await writeFile(
701
+ await writeFile2(
640
702
  targetPath,
641
703
  JSON.stringify(finalSettings, null, 2) + "\n",
642
704
  "utf-8"
@@ -657,7 +719,7 @@ async function executeSyncToLocal(options) {
657
719
  const mergedContent = JSON.stringify(merged, null, 2) + "\n";
658
720
  if (localContent !== mergedContent) {
659
721
  if (!dryRun) {
660
- await writeFile(targetPath, mergedContent, "utf-8");
722
+ await writeFile2(targetPath, mergedContent, "utf-8");
661
723
  }
662
724
  result.updated.push(remote.name);
663
725
  totals.updated++;
@@ -670,13 +732,13 @@ async function executeSyncToLocal(options) {
670
732
  if (localContent === void 0) {
671
733
  if (!dryRun) {
672
734
  await mkdir(dirname(targetPath), { recursive: true });
673
- await writeFile(targetPath, remoteContent, "utf-8");
735
+ await writeFile2(targetPath, remoteContent, "utf-8");
674
736
  }
675
737
  result.created.push(remote.name);
676
738
  totals.created++;
677
739
  } else if (localContent !== remoteContent) {
678
740
  if (!dryRun) {
679
- await writeFile(targetPath, remoteContent, "utf-8");
741
+ await writeFile2(targetPath, remoteContent, "utf-8");
680
742
  }
681
743
  result.updated.push(remote.name);
682
744
  totals.updated++;
@@ -777,15 +839,15 @@ __export(setup_exports, {
777
839
  });
778
840
  import { createInterface } from "node:readline/promises";
779
841
  import { stdin, stdout } from "node:process";
780
- import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
842
+ import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
781
843
  import { homedir } from "node:os";
782
- import { join as join3 } from "node:path";
844
+ import { join as join4 } from "node:path";
783
845
  function getConfigPath(scope) {
784
- return scope === "user" ? join3(homedir(), ".claude.json") : join3(process.cwd(), ".mcp.json");
846
+ return scope === "user" ? join4(homedir(), ".claude.json") : join4(process.cwd(), ".mcp.json");
785
847
  }
786
848
  async function readConfig(path) {
787
849
  try {
788
- const raw = await readFile3(path, "utf-8");
850
+ const raw = await readFile4(path, "utf-8");
789
851
  const parsed = JSON.parse(raw);
790
852
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
791
853
  return parsed;
@@ -809,7 +871,7 @@ async function writeMcpConfig(scope, apiKey) {
809
871
  config.mcpServers = {};
810
872
  }
811
873
  config.mcpServers.codebyplan = buildMcpEntry(apiKey);
812
- await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
874
+ await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
813
875
  return configPath;
814
876
  }
815
877
  async function verifyMcpConfig(scope, apiKey) {
@@ -913,27 +975,28 @@ async function runSetup() {
913
975
  console.log(`
914
976
  Selected: ${selectedRepo.name}
915
977
  `);
916
- let worktreeId;
917
978
  const projectPath = process.cwd();
918
- try {
919
- const worktreesRes = await apiGet(`/worktrees?repo_id=${selectedRepo.id}`);
920
- const match = worktreesRes.data.find(
921
- (wt) => projectPath === wt.path || projectPath.startsWith(wt.path + "/")
922
- );
923
- if (match) worktreeId = match.id;
924
- } catch {
925
- }
926
- const codebyplanPath = join3(projectPath, ".codebyplan.json");
979
+ const worktreeId = await resolveAndCacheWorktreeId(
980
+ selectedRepo.id,
981
+ projectPath,
982
+ { skipWrite: true }
983
+ );
984
+ const codebyplanPath = join4(projectPath, ".codebyplan.json");
927
985
  const codebyplanConfig = {
928
986
  repo_id: selectedRepo.id
929
987
  };
930
988
  if (worktreeId) codebyplanConfig.worktree_id = worktreeId;
931
- await writeFile2(
989
+ await writeFile3(
932
990
  codebyplanPath,
933
991
  JSON.stringify(codebyplanConfig, null, 2) + "\n",
934
992
  "utf-8"
935
993
  );
936
994
  console.log(` Created ${codebyplanPath}`);
995
+ if (worktreeId) {
996
+ console.log(
997
+ ` Worktree id set (${worktreeId}) \u2014 this worktree is now identified for hard-lock enforcement.`
998
+ );
999
+ }
937
1000
  console.log("\n Running initial sync...\n");
938
1001
  try {
939
1002
  const { executeSyncToLocal: executeSyncToLocal2 } = await Promise.resolve().then(() => (init_sync_engine(), sync_engine_exports));
@@ -968,19 +1031,19 @@ async function runSetup() {
968
1031
  var init_setup = __esm({
969
1032
  "src/cli/setup.ts"() {
970
1033
  "use strict";
971
- init_api();
1034
+ init_resolve_worktree();
972
1035
  }
973
1036
  });
974
1037
 
975
1038
  // src/cli/config.ts
976
- import { readFile as readFile4 } from "node:fs/promises";
977
- import { join as join4, resolve } from "node:path";
1039
+ import { readFile as readFile5 } from "node:fs/promises";
1040
+ import { join as join5, resolve } from "node:path";
978
1041
  async function findCodebyplanConfig(startDir, maxDepth = 20) {
979
1042
  let cursor = resolve(startDir);
980
1043
  for (let depth = 0; depth < maxDepth; depth++) {
981
- const configPath = join4(cursor, ".codebyplan.json");
1044
+ const configPath = join5(cursor, ".codebyplan.json");
982
1045
  try {
983
- const raw = await readFile4(configPath, "utf-8");
1046
+ const raw = await readFile5(configPath, "utf-8");
984
1047
  const parsed = JSON.parse(raw);
985
1048
  return { path: configPath, contents: parsed };
986
1049
  } catch {
@@ -1031,8 +1094,8 @@ var init_config = __esm({
1031
1094
  });
1032
1095
 
1033
1096
  // src/cli/fileMapper.ts
1034
- import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
1035
- import { join as join5, extname } from "node:path";
1097
+ import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
1098
+ import { join as join6, extname } from "node:path";
1036
1099
  function extractScope(content, type) {
1037
1100
  if (type === "hook") {
1038
1101
  const match = content.match(/^#\s*@scope:\s*(\S+)/m);
@@ -1062,29 +1125,29 @@ function compositeKey(type, name, category) {
1062
1125
  }
1063
1126
  async function scanLocalFiles(claudeDir, projectPath) {
1064
1127
  const result = /* @__PURE__ */ new Map();
1065
- await scanCommands(join5(claudeDir, "commands", "cbp"), result);
1128
+ await scanCommands(join6(claudeDir, "commands", "cbp"), result);
1066
1129
  await scanSubfolderType(
1067
- join5(claudeDir, "agents"),
1130
+ join6(claudeDir, "agents"),
1068
1131
  "agent",
1069
1132
  "AGENT.md",
1070
1133
  result
1071
1134
  );
1072
1135
  await scanSubfolderType(
1073
- join5(claudeDir, "skills"),
1136
+ join6(claudeDir, "skills"),
1074
1137
  "skill",
1075
1138
  "SKILL.md",
1076
1139
  result
1077
1140
  );
1078
- await scanFlatType(join5(claudeDir, "rules"), "rule", ".md", result);
1079
- await scanFlatType(join5(claudeDir, "hooks"), "hook", ".sh", result);
1080
- await scanTemplates(join5(claudeDir, "templates"), result);
1141
+ await scanFlatType(join6(claudeDir, "rules"), "rule", ".md", result);
1142
+ await scanFlatType(join6(claudeDir, "hooks"), "hook", ".sh", result);
1143
+ await scanTemplates(join6(claudeDir, "templates"), result);
1081
1144
  await scanCategorizedType(
1082
- join5(claudeDir, "context"),
1145
+ join6(claudeDir, "context"),
1083
1146
  "context",
1084
1147
  ".md",
1085
1148
  result
1086
1149
  );
1087
- await scanDocsRecursive(join5(claudeDir, "docs"), result);
1150
+ await scanDocsRecursive(join6(claudeDir, "docs"), result);
1088
1151
  await scanSettings(claudeDir, projectPath, result);
1089
1152
  return result;
1090
1153
  }
@@ -1102,12 +1165,12 @@ async function scanCommandsRecursive(baseDir, currentDir, result) {
1102
1165
  if (entry.isDirectory()) {
1103
1166
  await scanCommandsRecursive(
1104
1167
  baseDir,
1105
- join5(currentDir, entry.name),
1168
+ join6(currentDir, entry.name),
1106
1169
  result
1107
1170
  );
1108
1171
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
1109
1172
  const name = entry.name.slice(0, -3);
1110
- const content = await readFile5(join5(currentDir, entry.name), "utf-8");
1173
+ const content = await readFile6(join6(currentDir, entry.name), "utf-8");
1111
1174
  const relDir = currentDir.slice(baseDir.length + 1);
1112
1175
  const category = relDir || null;
1113
1176
  const scope = extractScope(content, "command");
@@ -1125,9 +1188,9 @@ async function scanSubfolderType(dir, type, fileName, result) {
1125
1188
  }
1126
1189
  for (const entry of entries) {
1127
1190
  if (entry.isDirectory()) {
1128
- const filePath = join5(dir, entry.name, fileName);
1191
+ const filePath = join6(dir, entry.name, fileName);
1129
1192
  try {
1130
- const content = await readFile5(filePath, "utf-8");
1193
+ const content = await readFile6(filePath, "utf-8");
1131
1194
  const scope = extractScope(content, type);
1132
1195
  const key = compositeKey(type, entry.name, null);
1133
1196
  result.set(key, {
@@ -1152,7 +1215,7 @@ async function scanFlatType(dir, type, ext, result) {
1152
1215
  for (const entry of entries) {
1153
1216
  if (entry.isFile() && entry.name.endsWith(ext)) {
1154
1217
  const name = entry.name.slice(0, -ext.length);
1155
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1218
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1156
1219
  const scope = extractScope(content, type);
1157
1220
  const key = compositeKey(type, name, null);
1158
1221
  result.set(key, { type, name, category: null, content, scope });
@@ -1171,7 +1234,7 @@ async function scanCategorizedType(dir, type, ext, result) {
1171
1234
  const category = entry.name;
1172
1235
  let subEntries;
1173
1236
  try {
1174
- subEntries = await readdir3(join5(dir, category), {
1237
+ subEntries = await readdir3(join6(dir, category), {
1175
1238
  withFileTypes: true
1176
1239
  });
1177
1240
  } catch {
@@ -1180,8 +1243,8 @@ async function scanCategorizedType(dir, type, ext, result) {
1180
1243
  for (const sub of subEntries) {
1181
1244
  if (sub.isFile() && sub.name.endsWith(ext)) {
1182
1245
  const name = sub.name.slice(0, -ext.length);
1183
- const content = await readFile5(
1184
- join5(dir, category, sub.name),
1246
+ const content = await readFile6(
1247
+ join6(dir, category, sub.name),
1185
1248
  "utf-8"
1186
1249
  );
1187
1250
  const scope = extractScope(content, type);
@@ -1191,7 +1254,7 @@ async function scanCategorizedType(dir, type, ext, result) {
1191
1254
  }
1192
1255
  } else if (entry.isFile() && entry.name.endsWith(ext)) {
1193
1256
  const name = entry.name.slice(0, -ext.length);
1194
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1257
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1195
1258
  const scope = extractScope(content, type);
1196
1259
  const key = compositeKey(type, name, null);
1197
1260
  result.set(key, { type, name, category: null, content, scope });
@@ -1210,10 +1273,10 @@ async function scanDocsDir(baseDir, currentDir, result) {
1210
1273
  }
1211
1274
  for (const entry of entries) {
1212
1275
  if (entry.isDirectory()) {
1213
- await scanDocsDir(baseDir, join5(currentDir, entry.name), result);
1276
+ await scanDocsDir(baseDir, join6(currentDir, entry.name), result);
1214
1277
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
1215
1278
  const name = entry.name.slice(0, -3);
1216
- const content = await readFile5(join5(currentDir, entry.name), "utf-8");
1279
+ const content = await readFile6(join6(currentDir, entry.name), "utf-8");
1217
1280
  const scope = extractScope(content, "docs");
1218
1281
  const relDir = currentDir.slice(baseDir.length + 1);
1219
1282
  const category = relDir || null;
@@ -1231,7 +1294,7 @@ async function scanTemplates(dir, result) {
1231
1294
  }
1232
1295
  for (const entry of entries) {
1233
1296
  if (entry.isFile() && extname(entry.name)) {
1234
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1297
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1235
1298
  const scope = extractScope(content, "template");
1236
1299
  const key = compositeKey("template", entry.name, null);
1237
1300
  result.set(key, {
@@ -1245,10 +1308,10 @@ async function scanTemplates(dir, result) {
1245
1308
  }
1246
1309
  }
1247
1310
  async function scanSettings(claudeDir, projectPath, result) {
1248
- const settingsPath = join5(claudeDir, "settings.json");
1311
+ const settingsPath = join6(claudeDir, "settings.json");
1249
1312
  let raw;
1250
1313
  try {
1251
- raw = await readFile5(settingsPath, "utf-8");
1314
+ raw = await readFile6(settingsPath, "utf-8");
1252
1315
  } catch {
1253
1316
  return;
1254
1317
  }
@@ -1260,7 +1323,7 @@ async function scanSettings(claudeDir, projectPath, result) {
1260
1323
  }
1261
1324
  parsed = stripPermissionsAllow(parsed);
1262
1325
  if (parsed.hooks && typeof parsed.hooks === "object") {
1263
- const hooksDir = projectPath ? join5(projectPath, ".claude", "hooks") : join5(claudeDir, "hooks");
1326
+ const hooksDir = projectPath ? join6(projectPath, ".claude", "hooks") : join6(claudeDir, "hooks");
1264
1327
  const discovered = await discoverHooks(hooksDir);
1265
1328
  if (discovered.size > 0) {
1266
1329
  parsed.hooks = stripDiscoveredHooks(
@@ -1592,8 +1655,8 @@ var init_confirm = __esm({
1592
1655
  });
1593
1656
 
1594
1657
  // src/lib/tech-detect.ts
1595
- import { readFile as readFile6, access, readdir as readdir4 } from "node:fs/promises";
1596
- import { join as join6, relative } from "node:path";
1658
+ import { readFile as readFile7, access, readdir as readdir4 } from "node:fs/promises";
1659
+ import { join as join7, relative } from "node:path";
1597
1660
  async function fileExists(filePath) {
1598
1661
  try {
1599
1662
  await access(filePath);
@@ -1606,8 +1669,8 @@ async function discoverMonorepoApps(projectPath) {
1606
1669
  const apps = [];
1607
1670
  const patterns = [];
1608
1671
  try {
1609
- const raw = await readFile6(
1610
- join6(projectPath, "pnpm-workspace.yaml"),
1672
+ const raw = await readFile7(
1673
+ join7(projectPath, "pnpm-workspace.yaml"),
1611
1674
  "utf-8"
1612
1675
  );
1613
1676
  const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
@@ -1621,7 +1684,7 @@ async function discoverMonorepoApps(projectPath) {
1621
1684
  }
1622
1685
  if (patterns.length === 0) {
1623
1686
  try {
1624
- const raw = await readFile6(join6(projectPath, "package.json"), "utf-8");
1687
+ const raw = await readFile7(join7(projectPath, "package.json"), "utf-8");
1625
1688
  const pkg = JSON.parse(raw);
1626
1689
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
1627
1690
  if (ws) patterns.push(...ws);
@@ -1631,14 +1694,14 @@ async function discoverMonorepoApps(projectPath) {
1631
1694
  for (const pattern of patterns) {
1632
1695
  if (pattern.endsWith("/*")) {
1633
1696
  const dir = pattern.slice(0, -2);
1634
- const absDir = join6(projectPath, dir);
1697
+ const absDir = join7(projectPath, dir);
1635
1698
  try {
1636
1699
  const entries = await readdir4(absDir, { withFileTypes: true });
1637
1700
  for (const entry of entries) {
1638
1701
  if (entry.isDirectory()) {
1639
- const relPath = join6(dir, entry.name);
1640
- const absPath = join6(absDir, entry.name);
1641
- if (await fileExists(join6(absPath, "package.json"))) {
1702
+ const relPath = join7(dir, entry.name);
1703
+ const absPath = join7(absDir, entry.name);
1704
+ if (await fileExists(join7(absPath, "package.json"))) {
1642
1705
  apps.push({ name: entry.name, path: relPath, absPath });
1643
1706
  }
1644
1707
  }
@@ -1657,7 +1720,7 @@ async function hasJsxFile(dir, depth = 0) {
1657
1720
  const name = entry.name;
1658
1721
  if (entry.isDirectory()) {
1659
1722
  if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
1660
- if (await hasJsxFile(join6(dir, name), depth + 1)) return true;
1723
+ if (await hasJsxFile(join7(dir, name), depth + 1)) return true;
1661
1724
  } else if (entry.isFile()) {
1662
1725
  if (JSX_TEST_PATTERN.test(name)) continue;
1663
1726
  if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
@@ -1676,7 +1739,7 @@ async function hasJsxFile(dir, depth = 0) {
1676
1739
  async function detectCapabilities(dirPath, pkgJson) {
1677
1740
  const caps = /* @__PURE__ */ new Set();
1678
1741
  for (const sub of JSX_SCAN_DIRS) {
1679
- if (await hasJsxFile(join6(dirPath, sub))) {
1742
+ if (await hasJsxFile(join7(dirPath, sub))) {
1680
1743
  caps.add("jsx");
1681
1744
  break;
1682
1745
  }
@@ -1698,7 +1761,7 @@ async function detectCapabilities(dirPath, pkgJson) {
1698
1761
  }
1699
1762
  }
1700
1763
  }
1701
- if (!caps.has("node-server") && await fileExists(join6(dirPath, "src", "main.ts"))) {
1764
+ if (!caps.has("node-server") && await fileExists(join7(dirPath, "src", "main.ts"))) {
1702
1765
  caps.add("node-server");
1703
1766
  }
1704
1767
  if (pkgJson && pkgJson.bin) {
@@ -1714,7 +1777,7 @@ async function detectFromDirectory(dirPath) {
1714
1777
  const seen = /* @__PURE__ */ new Map();
1715
1778
  let pkgJson = null;
1716
1779
  try {
1717
- const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
1780
+ const raw = await readFile7(join7(dirPath, "package.json"), "utf-8");
1718
1781
  pkgJson = JSON.parse(raw);
1719
1782
  const allDeps = {
1720
1783
  ...pkgJson.dependencies ?? {},
@@ -1746,7 +1809,7 @@ async function detectFromDirectory(dirPath) {
1746
1809
  }
1747
1810
  for (const { file, rule } of CONFIG_FILE_MAP) {
1748
1811
  const key = rule.name.toLowerCase();
1749
- if (!seen.has(key) && await fileExists(join6(dirPath, file))) {
1812
+ if (!seen.has(key) && await fileExists(join7(dirPath, file))) {
1750
1813
  seen.set(key, { name: rule.name, category: rule.category });
1751
1814
  }
1752
1815
  }
@@ -1924,7 +1987,7 @@ function categorizeDependency(depName) {
1924
1987
  async function findPackageJsonFiles(dir, projectPath, depth = 0) {
1925
1988
  if (depth > 4) return [];
1926
1989
  const results = [];
1927
- const pkgPath = join6(dir, "package.json");
1990
+ const pkgPath = join7(dir, "package.json");
1928
1991
  if (await fileExists(pkgPath)) {
1929
1992
  results.push(pkgPath);
1930
1993
  }
@@ -1933,7 +1996,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
1933
1996
  for (const entry of entries) {
1934
1997
  if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
1935
1998
  const subResults = await findPackageJsonFiles(
1936
- join6(dir, entry.name),
1999
+ join7(dir, entry.name),
1937
2000
  projectPath,
1938
2001
  depth + 1
1939
2002
  );
@@ -1948,7 +2011,7 @@ async function scanAllDependencies(projectPath) {
1948
2011
  const dependencies = [];
1949
2012
  for (const pkgPath of packageJsonPaths) {
1950
2013
  try {
1951
- const raw = await readFile6(pkgPath, "utf-8");
2014
+ const raw = await readFile7(pkgPath, "utf-8");
1952
2015
  const pkg = JSON.parse(raw);
1953
2016
  const sourcePath = relative(projectPath, pkgPath);
1954
2017
  const depSections = [
@@ -2197,14 +2260,14 @@ var init_server_detect = __esm({
2197
2260
  });
2198
2261
 
2199
2262
  // src/lib/port-verify.ts
2200
- import { readFile as readFile7 } from "node:fs/promises";
2263
+ import { readFile as readFile8 } from "node:fs/promises";
2201
2264
  async function verifyPorts(projectPath, portAllocations) {
2202
2265
  const mismatches = [];
2203
2266
  const allocatedPorts = new Set(portAllocations.map((a) => a.port));
2204
2267
  const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
2205
2268
  for (const pkgPath of packageJsonPaths) {
2206
2269
  try {
2207
- const raw = await readFile7(pkgPath, "utf-8");
2270
+ const raw = await readFile8(pkgPath, "utf-8");
2208
2271
  const pkg = JSON.parse(raw);
2209
2272
  const scriptPort = detectPortFromScripts(pkg);
2210
2273
  if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
@@ -2267,7 +2330,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
2267
2330
  }
2268
2331
  let pkg;
2269
2332
  try {
2270
- const raw = await readFile7(`${app.absPath}/package.json`, "utf-8");
2333
+ const raw = await readFile8(`${app.absPath}/package.json`, "utf-8");
2271
2334
  pkg = JSON.parse(raw);
2272
2335
  } catch {
2273
2336
  continue;
@@ -2700,8 +2763,8 @@ __export(eslint_exports, {
2700
2763
  eslintSync: () => eslintSync,
2701
2764
  runEslint: () => runEslint
2702
2765
  });
2703
- import { readFile as readFile8, writeFile as writeFile3, access as access2, readdir as readdir5 } from "node:fs/promises";
2704
- import { join as join7, relative as relative2 } from "node:path";
2766
+ import { readFile as readFile9, writeFile as writeFile4, access as access2, readdir as readdir5 } from "node:fs/promises";
2767
+ import { join as join8, relative as relative2 } from "node:path";
2705
2768
  async function fileExists2(filePath) {
2706
2769
  try {
2707
2770
  await access2(filePath);
@@ -2712,7 +2775,7 @@ async function fileExists2(filePath) {
2712
2775
  }
2713
2776
  async function autoDetectIgnorePatterns(absPath) {
2714
2777
  const patterns = [];
2715
- if (await fileExists2(join7(absPath, "esbuild.js"))) {
2778
+ if (await fileExists2(join8(absPath, "esbuild.js"))) {
2716
2779
  patterns.push("esbuild.js");
2717
2780
  }
2718
2781
  let entries = [];
@@ -2732,19 +2795,19 @@ async function autoDetectIgnorePatterns(absPath) {
2732
2795
  }
2733
2796
  for (const ext of ["ts", "mts", "js", "mjs"]) {
2734
2797
  const candidate = `vitest.config.${ext}`;
2735
- if (await fileExists2(join7(absPath, candidate))) {
2798
+ if (await fileExists2(join8(absPath, candidate))) {
2736
2799
  patterns.push(candidate);
2737
2800
  break;
2738
2801
  }
2739
2802
  }
2740
2803
  for (const ext of ["ts", "mts", "js", "mjs"]) {
2741
2804
  const candidate = `vite.config.${ext}`;
2742
- if (await fileExists2(join7(absPath, candidate))) {
2805
+ if (await fileExists2(join8(absPath, candidate))) {
2743
2806
  patterns.push(candidate);
2744
2807
  break;
2745
2808
  }
2746
2809
  }
2747
- if (await fileExists2(join7(absPath, "tauri.conf.json"))) {
2810
+ if (await fileExists2(join8(absPath, "tauri.conf.json"))) {
2748
2811
  patterns.push("src-tauri/**");
2749
2812
  patterns.push("**/*.d.ts");
2750
2813
  }
@@ -2752,14 +2815,14 @@ async function autoDetectIgnorePatterns(absPath) {
2752
2815
  }
2753
2816
  function detectPackageManager(projectPath) {
2754
2817
  return (async () => {
2755
- if (await fileExists2(join7(projectPath, "pnpm-lock.yaml"))) return "pnpm";
2756
- if (await fileExists2(join7(projectPath, "yarn.lock"))) return "yarn";
2818
+ if (await fileExists2(join8(projectPath, "pnpm-lock.yaml"))) return "pnpm";
2819
+ if (await fileExists2(join8(projectPath, "yarn.lock"))) return "yarn";
2757
2820
  return "npm";
2758
2821
  })();
2759
2822
  }
2760
2823
  async function getInstalledDeps(pkgJsonPath) {
2761
2824
  try {
2762
- const raw = await readFile8(pkgJsonPath, "utf-8");
2825
+ const raw = await readFile9(pkgJsonPath, "utf-8");
2763
2826
  const pkg = JSON.parse(raw);
2764
2827
  const all = /* @__PURE__ */ new Set();
2765
2828
  for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
@@ -2872,7 +2935,7 @@ async function eslintInit(repoId, projectPath) {
2872
2935
  ignorePatterns: detectedIgnores
2873
2936
  });
2874
2937
  const hash = hashConfig(content);
2875
- const configPath = join7(target.absPath, "eslint.config.mjs");
2938
+ const configPath = join8(target.absPath, "eslint.config.mjs");
2876
2939
  configsToWrite.push({
2877
2940
  target,
2878
2941
  presets,
@@ -2894,11 +2957,11 @@ async function eslintInit(repoId, projectPath) {
2894
2957
  return;
2895
2958
  }
2896
2959
  const pm = await detectPackageManager(projectPath);
2897
- const rootPkgJsonPath = join7(projectPath, "package.json");
2960
+ const rootPkgJsonPath = join8(projectPath, "package.json");
2898
2961
  const installed = await getInstalledDeps(rootPkgJsonPath);
2899
2962
  if (isMonorepo) {
2900
2963
  for (const { target } of configsToWrite) {
2901
- const appPkgJson = join7(target.absPath, "package.json");
2964
+ const appPkgJson = join8(target.absPath, "package.json");
2902
2965
  const appDeps = await getInstalledDeps(appPkgJson);
2903
2966
  for (const dep of appDeps) {
2904
2967
  installed.add(dep);
@@ -2950,7 +3013,7 @@ async function eslintInit(repoId, projectPath) {
2950
3013
  } of configsToWrite) {
2951
3014
  if (await fileExists2(configPath)) {
2952
3015
  try {
2953
- const existing = await readFile8(configPath, "utf-8");
3016
+ const existing = await readFile9(configPath, "utf-8");
2954
3017
  const existingHash = hashConfig(existing);
2955
3018
  if (existingHash === hash) {
2956
3019
  console.log(
@@ -2970,7 +3033,7 @@ async function eslintInit(repoId, projectPath) {
2970
3033
  }
2971
3034
  }
2972
3035
  try {
2973
- await writeFile3(configPath, content, "utf-8");
3036
+ await writeFile4(configPath, content, "utf-8");
2974
3037
  } catch (err) {
2975
3038
  console.error(
2976
3039
  ` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
@@ -3020,8 +3083,8 @@ async function eslintSync(repoId, projectPath) {
3020
3083
  let skippedCount = 0;
3021
3084
  let driftCount = 0;
3022
3085
  for (const config of configs) {
3023
- const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
3024
- const configPath = join7(absPath, "eslint.config.mjs");
3086
+ const absPath = config.source_path === "." ? projectPath : join8(projectPath, config.source_path);
3087
+ const configPath = join8(absPath, "eslint.config.mjs");
3025
3088
  const detected = await detectTechStack(absPath);
3026
3089
  const techNames = detected.flat.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
3027
3090
  const capabilities = collectCapabilities(detected.flat);
@@ -3035,7 +3098,7 @@ async function eslintSync(repoId, projectPath) {
3035
3098
  if (!presetsChanged) {
3036
3099
  if (await fileExists2(configPath)) {
3037
3100
  try {
3038
- const currentContent = await readFile8(configPath, "utf-8");
3101
+ const currentContent = await readFile9(configPath, "utf-8");
3039
3102
  const currentHash = hashConfig(currentContent);
3040
3103
  if (config.generated_hash && currentHash !== config.generated_hash) {
3041
3104
  console.log(
@@ -3068,7 +3131,7 @@ async function eslintSync(repoId, projectPath) {
3068
3131
  ignorePatterns: detectedIgnores
3069
3132
  });
3070
3133
  try {
3071
- await writeFile3(configPath, content, "utf-8");
3134
+ await writeFile4(configPath, content, "utf-8");
3072
3135
  } catch (err) {
3073
3136
  console.error(
3074
3137
  ` ${config.source_path}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
@@ -3104,11 +3167,11 @@ async function checkEslintDrift(repoId, projectPath) {
3104
3167
  const configs = res.data ?? [];
3105
3168
  for (const config of configs) {
3106
3169
  if (!config.generated_hash) continue;
3107
- const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
3108
- const configPath = join7(absPath, "eslint.config.mjs");
3170
+ const absPath = config.source_path === "." ? projectPath : join8(projectPath, config.source_path);
3171
+ const configPath = join8(absPath, "eslint.config.mjs");
3109
3172
  if (!await fileExists2(configPath)) continue;
3110
3173
  try {
3111
- const content = await readFile8(configPath, "utf-8");
3174
+ const content = await readFile9(configPath, "utf-8");
3112
3175
  const currentHash = hashConfig(content);
3113
3176
  if (currentHash !== config.generated_hash) {
3114
3177
  return true;
@@ -3160,8 +3223,8 @@ __export(sync_exports, {
3160
3223
  runSync: () => runSync
3161
3224
  });
3162
3225
  import { createHash as createHash2 } from "node:crypto";
3163
- import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
3164
- import { join as join8, dirname as dirname2 } from "node:path";
3226
+ import { readFile as readFile10, writeFile as writeFile5, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
3227
+ import { join as join9, dirname as dirname2 } from "node:path";
3165
3228
  function contentHash(content) {
3166
3229
  return createHash2("sha256").update(content).digest("hex");
3167
3230
  }
@@ -3229,7 +3292,7 @@ async function runSync() {
3229
3292
  }
3230
3293
  async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3231
3294
  console.log(" Reading local and remote state...");
3232
- const claudeDir = join8(projectPath, ".claude");
3295
+ const claudeDir = join9(projectPath, ".claude");
3233
3296
  let localFiles = /* @__PURE__ */ new Map();
3234
3297
  try {
3235
3298
  localFiles = await scanLocalFiles(claudeDir, projectPath);
@@ -3464,7 +3527,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3464
3527
  for (const p of toPull) {
3465
3528
  if (p.filePath && p.remoteContent !== null) {
3466
3529
  await mkdir2(dirname2(p.filePath), { recursive: true });
3467
- await writeFile4(p.filePath, p.remoteContent, "utf-8");
3530
+ await writeFile5(p.filePath, p.remoteContent, "utf-8");
3468
3531
  if (p.isHook) await chmod2(p.filePath, 493);
3469
3532
  }
3470
3533
  }
@@ -3618,7 +3681,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3618
3681
  console.log("\n Sync complete.\n");
3619
3682
  }
3620
3683
  async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
3621
- const settingsPath = join8(claudeDir, "settings.json");
3684
+ const settingsPath = join9(claudeDir, "settings.json");
3622
3685
  const globalSettingsFiles = syncData.global_settings ?? [];
3623
3686
  let globalSettings = {};
3624
3687
  for (const gf of globalSettingsFiles) {
@@ -3638,11 +3701,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3638
3701
  globalSettings,
3639
3702
  repoSettings
3640
3703
  );
3641
- const hooksDir = join8(projectPath, ".claude", "hooks");
3704
+ const hooksDir = join9(projectPath, ".claude", "hooks");
3642
3705
  const discovered = await discoverHooks(hooksDir);
3643
3706
  let localSettings = {};
3644
3707
  try {
3645
- const raw = await readFile9(settingsPath, "utf-8");
3708
+ const raw = await readFile10(settingsPath, "utf-8");
3646
3709
  localSettings = JSON.parse(raw);
3647
3710
  } catch {
3648
3711
  }
@@ -3657,7 +3720,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3657
3720
  const mergedContent = JSON.stringify(merged, null, 2) + "\n";
3658
3721
  let currentContent = "";
3659
3722
  try {
3660
- currentContent = await readFile9(settingsPath, "utf-8");
3723
+ currentContent = await readFile10(settingsPath, "utf-8");
3661
3724
  } catch {
3662
3725
  }
3663
3726
  if (currentContent === mergedContent) {
@@ -3669,18 +3732,30 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3669
3732
  return;
3670
3733
  }
3671
3734
  await mkdir2(dirname2(settingsPath), { recursive: true });
3672
- await writeFile4(settingsPath, mergedContent, "utf-8");
3735
+ await writeFile5(settingsPath, mergedContent, "utf-8");
3673
3736
  console.log(" Updated settings.json");
3674
3737
  }
3675
3738
  async function syncConfig(repoId, projectPath, dryRun) {
3676
- const configPath = join8(projectPath, ".codebyplan.json");
3739
+ const configPath = join9(projectPath, ".codebyplan.json");
3677
3740
  let currentConfig = {};
3678
3741
  try {
3679
- const raw = await readFile9(configPath, "utf-8");
3742
+ const raw = await readFile10(configPath, "utf-8");
3680
3743
  currentConfig = JSON.parse(raw);
3681
3744
  } catch {
3682
3745
  currentConfig = { repo_id: repoId };
3683
3746
  }
3747
+ let resolvedWorktreeId;
3748
+ try {
3749
+ resolvedWorktreeId = await resolveAndCacheWorktreeId(repoId, projectPath);
3750
+ } catch (err) {
3751
+ const msg = err instanceof Error ? err.message : String(err);
3752
+ console.warn(
3753
+ ` Warning: failed to cache worktree_id (self-heal skipped): ${msg}`
3754
+ );
3755
+ }
3756
+ if (resolvedWorktreeId && currentConfig.worktree_id !== resolvedWorktreeId) {
3757
+ currentConfig = { ...currentConfig, worktree_id: resolvedWorktreeId };
3758
+ }
3684
3759
  const repoRes = await apiGet(`/repos/${repoId}`);
3685
3760
  const repo = repoRes.data;
3686
3761
  let portAllocations = [];
@@ -3748,7 +3823,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
3748
3823
  console.log(" Config would be updated (dry-run).");
3749
3824
  return;
3750
3825
  }
3751
- await writeFile4(configPath, newJson + "\n", "utf-8");
3826
+ await writeFile5(configPath, newJson + "\n", "utf-8");
3752
3827
  console.log(" Updated .codebyplan.json");
3753
3828
  }
3754
3829
  async function syncTechStack(repoId, projectPath, dryRun) {
@@ -3900,28 +3975,28 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
3900
3975
  hook: { dir: "hooks", ext: ".sh" },
3901
3976
  template: { dir: "templates", ext: "" },
3902
3977
  context: { dir: "context", ext: ".md" },
3903
- docs_stack: { dir: join8("docs", "stack"), ext: ".md" },
3978
+ docs_stack: { dir: join9("docs", "stack"), ext: ".md" },
3904
3979
  docs: { dir: "docs", ext: ".md" },
3905
3980
  claude_md: { dir: "", ext: "" },
3906
3981
  settings: { dir: "", ext: "" }
3907
3982
  };
3908
- if (remote.type === "claude_md") return join8(projectPath, "CLAUDE.md");
3909
- if (remote.type === "settings") return join8(claudeDir, "settings.json");
3983
+ if (remote.type === "claude_md") return join9(projectPath, "CLAUDE.md");
3984
+ if (remote.type === "settings") return join9(claudeDir, "settings.json");
3910
3985
  const cfg = typeConfig2[remote.type];
3911
- if (!cfg) return join8(claudeDir, remote.name);
3912
- const typeDir = remote.type === "command" ? join8(claudeDir, cfg.dir, "cbp") : join8(claudeDir, cfg.dir);
3986
+ if (!cfg) return join9(claudeDir, remote.name);
3987
+ const typeDir = remote.type === "command" ? join9(claudeDir, cfg.dir, "cbp") : join9(claudeDir, cfg.dir);
3913
3988
  if (cfg.subfolder)
3914
- return join8(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
3989
+ return join9(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
3915
3990
  if (remote.type === "command" && remote.category)
3916
- return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3917
- if (remote.type === "template") return join8(typeDir, remote.name);
3991
+ return join9(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3992
+ if (remote.type === "template") return join9(typeDir, remote.name);
3918
3993
  if (remote.category && (remote.type === "context" || remote.type === "docs_stack" || remote.type === "docs"))
3919
- return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3920
- return join8(typeDir, `${remote.name}${cfg.ext}`);
3994
+ return join9(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3995
+ return join9(typeDir, `${remote.name}${cfg.ext}`);
3921
3996
  }
3922
3997
  function getSyncVersion() {
3923
3998
  try {
3924
- return "1.4.2";
3999
+ return "1.4.3";
3925
4000
  } catch {
3926
4001
  return "unknown";
3927
4002
  }
@@ -3970,6 +4045,7 @@ var init_sync = __esm({
3970
4045
  init_settings_merge();
3971
4046
  init_hook_registry();
3972
4047
  init_port_verify();
4048
+ init_resolve_worktree();
3973
4049
  init_eslint();
3974
4050
  }
3975
4051
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {