codebyplan 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.js +509 -157
- package/package.json +2 -1
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.
|
|
17
|
+
VERSION = "1.5.0";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -143,6 +143,159 @@ 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
|
+
async function resolveWorktreeId({
|
|
202
|
+
repoId,
|
|
203
|
+
repoPath,
|
|
204
|
+
branch,
|
|
205
|
+
deviceId
|
|
206
|
+
}) {
|
|
207
|
+
try {
|
|
208
|
+
const res = await apiPost(
|
|
209
|
+
"/worktrees/resolve",
|
|
210
|
+
{ repo_id: repoId, device_id: deviceId, repo_path: repoPath, branch }
|
|
211
|
+
);
|
|
212
|
+
return res.worktree_id ?? null;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error(
|
|
215
|
+
`Tuple worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`
|
|
216
|
+
);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
var init_resolve_worktree = __esm({
|
|
221
|
+
"src/lib/resolve-worktree.ts"() {
|
|
222
|
+
"use strict";
|
|
223
|
+
init_api();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// src/lib/local-config.ts
|
|
228
|
+
import { execSync } from "node:child_process";
|
|
229
|
+
import { createHash } from "node:crypto";
|
|
230
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
231
|
+
import { hostname } from "node:os";
|
|
232
|
+
import { join as join2 } from "node:path";
|
|
233
|
+
function localConfigPath(projectPath) {
|
|
234
|
+
return join2(projectPath, ".codebyplan.local.json");
|
|
235
|
+
}
|
|
236
|
+
async function readLocalConfig(projectPath) {
|
|
237
|
+
try {
|
|
238
|
+
const raw = await readFile2(localConfigPath(projectPath), "utf-8");
|
|
239
|
+
const parsed = JSON.parse(raw);
|
|
240
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
241
|
+
return parsed;
|
|
242
|
+
}
|
|
243
|
+
console.error("Failed to read local config: invalid shape");
|
|
244
|
+
return null;
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error(
|
|
247
|
+
`Failed to read local config: ${err instanceof Error ? err.message : String(err)}`
|
|
248
|
+
);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function writeLocalConfig(projectPath, config) {
|
|
253
|
+
const content = { device_id: config.device_id };
|
|
254
|
+
try {
|
|
255
|
+
await writeFile2(
|
|
256
|
+
localConfigPath(projectPath),
|
|
257
|
+
JSON.stringify(content, null, 2) + "\n",
|
|
258
|
+
"utf-8"
|
|
259
|
+
);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error(
|
|
262
|
+
`Failed to write local config: ${err instanceof Error ? err.message : String(err)}`
|
|
263
|
+
);
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function resolveMachineSeed() {
|
|
268
|
+
try {
|
|
269
|
+
const raw = await readFile2("/etc/machine-id", "utf-8");
|
|
270
|
+
const trimmed = raw.trim();
|
|
271
|
+
if (trimmed) return trimmed;
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
if (process.platform === "darwin") {
|
|
275
|
+
try {
|
|
276
|
+
const out = execSync("sysctl -n kern.uuid", { encoding: "utf-8" }).trim();
|
|
277
|
+
if (out) return out;
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return hostname();
|
|
282
|
+
}
|
|
283
|
+
async function getOrCreateDeviceId(projectPath) {
|
|
284
|
+
const existing = await readLocalConfig(projectPath);
|
|
285
|
+
if (existing?.device_id) {
|
|
286
|
+
return existing.device_id;
|
|
287
|
+
}
|
|
288
|
+
const seed = await resolveMachineSeed();
|
|
289
|
+
const deviceId = createHash("sha256").update(seed).digest("hex").slice(0, 16);
|
|
290
|
+
await writeLocalConfig(projectPath, { device_id: deviceId });
|
|
291
|
+
return deviceId;
|
|
292
|
+
}
|
|
293
|
+
var init_local_config = __esm({
|
|
294
|
+
"src/lib/local-config.ts"() {
|
|
295
|
+
"use strict";
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
146
299
|
// src/lib/settings-merge.ts
|
|
147
300
|
function mergeSettings(template, local) {
|
|
148
301
|
const merged = { ...local };
|
|
@@ -208,8 +361,8 @@ var init_settings_merge = __esm({
|
|
|
208
361
|
});
|
|
209
362
|
|
|
210
363
|
// src/lib/hook-registry.ts
|
|
211
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
212
|
-
import { join } from "node:path";
|
|
364
|
+
import { readdir, readFile as readFile3 } from "node:fs/promises";
|
|
365
|
+
import { join as join3 } from "node:path";
|
|
213
366
|
function parseHookMeta(content) {
|
|
214
367
|
const lineMatch = content.match(/^#\s*@hook:(.*)$/m);
|
|
215
368
|
if (!lineMatch) return null;
|
|
@@ -231,7 +384,7 @@ async function discoverHooks(hooksDir) {
|
|
|
231
384
|
return discovered;
|
|
232
385
|
}
|
|
233
386
|
for (const filename of filenames) {
|
|
234
|
-
const content = await
|
|
387
|
+
const content = await readFile3(join3(hooksDir, filename), "utf-8");
|
|
235
388
|
const meta = parseHookMeta(content);
|
|
236
389
|
if (meta) {
|
|
237
390
|
discovered.set(filename.replace(/\.sh$/, ""), meta);
|
|
@@ -378,45 +531,45 @@ __export(sync_engine_exports, {
|
|
|
378
531
|
});
|
|
379
532
|
import {
|
|
380
533
|
readdir as readdir2,
|
|
381
|
-
readFile as
|
|
382
|
-
writeFile,
|
|
534
|
+
readFile as readFile4,
|
|
535
|
+
writeFile as writeFile3,
|
|
383
536
|
unlink,
|
|
384
537
|
mkdir,
|
|
385
538
|
rmdir,
|
|
386
539
|
chmod,
|
|
387
540
|
stat
|
|
388
541
|
} from "node:fs/promises";
|
|
389
|
-
import { join as
|
|
542
|
+
import { join as join4, dirname } from "node:path";
|
|
390
543
|
function getTypeDir(claudeDir, dir) {
|
|
391
|
-
if (dir === "commands") return
|
|
392
|
-
return
|
|
544
|
+
if (dir === "commands") return join4(claudeDir, dir, "cbp");
|
|
545
|
+
return join4(claudeDir, dir);
|
|
393
546
|
}
|
|
394
547
|
function getFilePath(claudeDir, typeName, file) {
|
|
395
548
|
const cfg = typeConfig[typeName];
|
|
396
549
|
const typeDir = getTypeDir(claudeDir, cfg.dir);
|
|
397
550
|
if (cfg.subfolder) {
|
|
398
|
-
return
|
|
551
|
+
return join4(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
|
|
399
552
|
}
|
|
400
553
|
if (typeName === "command" && file.category) {
|
|
401
|
-
return
|
|
554
|
+
return join4(typeDir, file.category, `${file.name}${cfg.ext}`);
|
|
402
555
|
}
|
|
403
556
|
if (typeName === "template") {
|
|
404
|
-
return
|
|
557
|
+
return join4(typeDir, file.name);
|
|
405
558
|
}
|
|
406
|
-
return
|
|
559
|
+
return join4(typeDir, `${file.name}${cfg.ext}`);
|
|
407
560
|
}
|
|
408
561
|
async function readDirRecursive(dir, base = dir) {
|
|
409
562
|
const result = /* @__PURE__ */ new Map();
|
|
410
563
|
try {
|
|
411
564
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
412
565
|
for (const entry of entries) {
|
|
413
|
-
const fullPath =
|
|
566
|
+
const fullPath = join4(dir, entry.name);
|
|
414
567
|
if (entry.isDirectory()) {
|
|
415
568
|
const sub = await readDirRecursive(fullPath, base);
|
|
416
569
|
for (const [k, v] of sub) result.set(k, v);
|
|
417
570
|
} else {
|
|
418
571
|
const relPath = fullPath.slice(base.length + 1);
|
|
419
|
-
const fileContent = await
|
|
572
|
+
const fileContent = await readFile4(fullPath, "utf-8");
|
|
420
573
|
result.set(relPath, fileContent);
|
|
421
574
|
}
|
|
422
575
|
}
|
|
@@ -426,7 +579,7 @@ async function readDirRecursive(dir, base = dir) {
|
|
|
426
579
|
}
|
|
427
580
|
async function isGitWorktree(projectPath) {
|
|
428
581
|
try {
|
|
429
|
-
const gitPath =
|
|
582
|
+
const gitPath = join4(projectPath, ".git");
|
|
430
583
|
const info = await stat(gitPath);
|
|
431
584
|
return info.isFile();
|
|
432
585
|
} catch {
|
|
@@ -453,7 +606,7 @@ async function executeSyncToLocal(options) {
|
|
|
453
606
|
const syncData = syncRes.data;
|
|
454
607
|
const repoData = repoRes.data;
|
|
455
608
|
syncData.claude_md = [];
|
|
456
|
-
const claudeDir =
|
|
609
|
+
const claudeDir = join4(projectPath, ".claude");
|
|
457
610
|
const worktree = await isGitWorktree(projectPath);
|
|
458
611
|
const byType = {};
|
|
459
612
|
const totals = { created: 0, updated: 0, deleted: 0, unchanged: 0 };
|
|
@@ -489,7 +642,7 @@ async function executeSyncToLocal(options) {
|
|
|
489
642
|
remotePathMap.set(relPath, { content: substituted, name: remote.name });
|
|
490
643
|
}
|
|
491
644
|
for (const [relPath, { content, name }] of remotePathMap) {
|
|
492
|
-
const fullPath =
|
|
645
|
+
const fullPath = join4(targetDir, relPath);
|
|
493
646
|
const localContent = localFiles.get(relPath);
|
|
494
647
|
if (localContent === void 0) {
|
|
495
648
|
const remoteFile = remoteFiles.find((f) => f.name === name);
|
|
@@ -501,14 +654,14 @@ async function executeSyncToLocal(options) {
|
|
|
501
654
|
});
|
|
502
655
|
if (!dryRun) {
|
|
503
656
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
504
|
-
await
|
|
657
|
+
await writeFile3(fullPath, content, "utf-8");
|
|
505
658
|
if (typeName === "hook") await chmod(fullPath, 493);
|
|
506
659
|
}
|
|
507
660
|
result.created.push(name);
|
|
508
661
|
totals.created++;
|
|
509
662
|
} else if (localContent !== content) {
|
|
510
663
|
if (!dryRun) {
|
|
511
|
-
await
|
|
664
|
+
await writeFile3(fullPath, content, "utf-8");
|
|
512
665
|
if (typeName === "hook") await chmod(fullPath, 493);
|
|
513
666
|
}
|
|
514
667
|
result.updated.push(name);
|
|
@@ -520,7 +673,7 @@ async function executeSyncToLocal(options) {
|
|
|
520
673
|
}
|
|
521
674
|
for (const [relPath] of localFiles) {
|
|
522
675
|
if (!remotePathMap.has(relPath)) {
|
|
523
|
-
const fullPath =
|
|
676
|
+
const fullPath = join4(targetDir, relPath);
|
|
524
677
|
if (!dryRun) {
|
|
525
678
|
await unlink(fullPath);
|
|
526
679
|
await removeEmptyParents(fullPath, targetDir);
|
|
@@ -535,7 +688,7 @@ async function executeSyncToLocal(options) {
|
|
|
535
688
|
{
|
|
536
689
|
const typeName = "docs_stack";
|
|
537
690
|
const syncKey = "docs_stack";
|
|
538
|
-
const targetDir =
|
|
691
|
+
const targetDir = join4(projectPath, "docs", "stack");
|
|
539
692
|
const remoteFiles = syncData[syncKey] ?? [];
|
|
540
693
|
const result = {
|
|
541
694
|
created: [],
|
|
@@ -549,7 +702,7 @@ async function executeSyncToLocal(options) {
|
|
|
549
702
|
const localFiles = await readDirRecursive(targetDir);
|
|
550
703
|
const remotePathMap = /* @__PURE__ */ new Map();
|
|
551
704
|
for (const remote of remoteFiles) {
|
|
552
|
-
const relPath = remote.category ?
|
|
705
|
+
const relPath = remote.category ? join4(remote.category, remote.name) : remote.name;
|
|
553
706
|
const substituted = substituteVariables(remote.content, repoData);
|
|
554
707
|
remotePathMap.set(relPath, {
|
|
555
708
|
content: substituted,
|
|
@@ -557,18 +710,18 @@ async function executeSyncToLocal(options) {
|
|
|
557
710
|
});
|
|
558
711
|
}
|
|
559
712
|
for (const [relPath, { content, name }] of remotePathMap) {
|
|
560
|
-
const fullPath =
|
|
713
|
+
const fullPath = join4(targetDir, relPath);
|
|
561
714
|
const localContent = localFiles.get(relPath);
|
|
562
715
|
if (localContent === void 0) {
|
|
563
716
|
if (!dryRun) {
|
|
564
717
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
565
|
-
await
|
|
718
|
+
await writeFile3(fullPath, content, "utf-8");
|
|
566
719
|
}
|
|
567
720
|
result.created.push(name);
|
|
568
721
|
totals.created++;
|
|
569
722
|
} else if (localContent !== content) {
|
|
570
723
|
if (!dryRun) {
|
|
571
|
-
await
|
|
724
|
+
await writeFile3(fullPath, content, "utf-8");
|
|
572
725
|
}
|
|
573
726
|
result.updated.push(name);
|
|
574
727
|
totals.updated++;
|
|
@@ -579,7 +732,7 @@ async function executeSyncToLocal(options) {
|
|
|
579
732
|
}
|
|
580
733
|
for (const [relPath] of localFiles) {
|
|
581
734
|
if (!remotePathMap.has(relPath)) {
|
|
582
|
-
const fullPath =
|
|
735
|
+
const fullPath = join4(targetDir, relPath);
|
|
583
736
|
if (!dryRun) {
|
|
584
737
|
await unlink(fullPath);
|
|
585
738
|
await removeEmptyParents(fullPath, targetDir);
|
|
@@ -599,8 +752,8 @@ async function executeSyncToLocal(options) {
|
|
|
599
752
|
globalSettings = { ...globalSettings, ...parsed };
|
|
600
753
|
}
|
|
601
754
|
const specialTypes = {
|
|
602
|
-
claude_md: () =>
|
|
603
|
-
settings: () =>
|
|
755
|
+
claude_md: () => join4(projectPath, "CLAUDE.md"),
|
|
756
|
+
settings: () => join4(projectPath, ".claude", "settings.json")
|
|
604
757
|
};
|
|
605
758
|
for (const [typeName, getPath] of Object.entries(specialTypes)) {
|
|
606
759
|
const remoteFiles = syncData[typeName] ?? [];
|
|
@@ -615,7 +768,7 @@ async function executeSyncToLocal(options) {
|
|
|
615
768
|
const remoteContent = substituteVariables(remote.content, repoData);
|
|
616
769
|
let localContent;
|
|
617
770
|
try {
|
|
618
|
-
localContent = await
|
|
771
|
+
localContent = await readFile4(targetPath, "utf-8");
|
|
619
772
|
} catch {
|
|
620
773
|
}
|
|
621
774
|
if (typeName === "settings") {
|
|
@@ -624,7 +777,7 @@ async function executeSyncToLocal(options) {
|
|
|
624
777
|
globalSettings,
|
|
625
778
|
repoSettings
|
|
626
779
|
);
|
|
627
|
-
const hooksDir =
|
|
780
|
+
const hooksDir = join4(projectPath, ".claude", "hooks");
|
|
628
781
|
const discovered = await discoverHooks(hooksDir);
|
|
629
782
|
if (localContent === void 0) {
|
|
630
783
|
const finalSettings = stripPermissionsAllow(combinedTemplate);
|
|
@@ -636,7 +789,7 @@ async function executeSyncToLocal(options) {
|
|
|
636
789
|
}
|
|
637
790
|
if (!dryRun) {
|
|
638
791
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
639
|
-
await
|
|
792
|
+
await writeFile3(
|
|
640
793
|
targetPath,
|
|
641
794
|
JSON.stringify(finalSettings, null, 2) + "\n",
|
|
642
795
|
"utf-8"
|
|
@@ -657,7 +810,7 @@ async function executeSyncToLocal(options) {
|
|
|
657
810
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
658
811
|
if (localContent !== mergedContent) {
|
|
659
812
|
if (!dryRun) {
|
|
660
|
-
await
|
|
813
|
+
await writeFile3(targetPath, mergedContent, "utf-8");
|
|
661
814
|
}
|
|
662
815
|
result.updated.push(remote.name);
|
|
663
816
|
totals.updated++;
|
|
@@ -670,13 +823,13 @@ async function executeSyncToLocal(options) {
|
|
|
670
823
|
if (localContent === void 0) {
|
|
671
824
|
if (!dryRun) {
|
|
672
825
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
673
|
-
await
|
|
826
|
+
await writeFile3(targetPath, remoteContent, "utf-8");
|
|
674
827
|
}
|
|
675
828
|
result.created.push(remote.name);
|
|
676
829
|
totals.created++;
|
|
677
830
|
} else if (localContent !== remoteContent) {
|
|
678
831
|
if (!dryRun) {
|
|
679
|
-
await
|
|
832
|
+
await writeFile3(targetPath, remoteContent, "utf-8");
|
|
680
833
|
}
|
|
681
834
|
result.updated.push(remote.name);
|
|
682
835
|
totals.updated++;
|
|
@@ -777,15 +930,15 @@ __export(setup_exports, {
|
|
|
777
930
|
});
|
|
778
931
|
import { createInterface } from "node:readline/promises";
|
|
779
932
|
import { stdin, stdout } from "node:process";
|
|
780
|
-
import { readFile as
|
|
933
|
+
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
781
934
|
import { homedir } from "node:os";
|
|
782
|
-
import { join as
|
|
935
|
+
import { join as join5 } from "node:path";
|
|
783
936
|
function getConfigPath(scope) {
|
|
784
|
-
return scope === "user" ?
|
|
937
|
+
return scope === "user" ? join5(homedir(), ".claude.json") : join5(process.cwd(), ".mcp.json");
|
|
785
938
|
}
|
|
786
939
|
async function readConfig(path) {
|
|
787
940
|
try {
|
|
788
|
-
const raw = await
|
|
941
|
+
const raw = await readFile5(path, "utf-8");
|
|
789
942
|
const parsed = JSON.parse(raw);
|
|
790
943
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
791
944
|
return parsed;
|
|
@@ -809,7 +962,7 @@ async function writeMcpConfig(scope, apiKey) {
|
|
|
809
962
|
config.mcpServers = {};
|
|
810
963
|
}
|
|
811
964
|
config.mcpServers.codebyplan = buildMcpEntry(apiKey);
|
|
812
|
-
await
|
|
965
|
+
await writeFile4(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
813
966
|
return configPath;
|
|
814
967
|
}
|
|
815
968
|
async function verifyMcpConfig(scope, apiKey) {
|
|
@@ -913,27 +1066,45 @@ async function runSetup() {
|
|
|
913
1066
|
console.log(`
|
|
914
1067
|
Selected: ${selectedRepo.name}
|
|
915
1068
|
`);
|
|
916
|
-
let worktreeId;
|
|
917
1069
|
const projectPath = process.cwd();
|
|
1070
|
+
const pathBasedId = await resolveAndCacheWorktreeId(
|
|
1071
|
+
selectedRepo.id,
|
|
1072
|
+
projectPath,
|
|
1073
|
+
{ skipWrite: true }
|
|
1074
|
+
);
|
|
1075
|
+
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
1076
|
+
let branch = "main";
|
|
918
1077
|
try {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1078
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
1079
|
+
branch = execSync3("git symbolic-ref --short HEAD", {
|
|
1080
|
+
cwd: projectPath,
|
|
1081
|
+
encoding: "utf-8"
|
|
1082
|
+
}).trim();
|
|
924
1083
|
} catch {
|
|
925
1084
|
}
|
|
926
|
-
const
|
|
1085
|
+
const tupleId = await resolveWorktreeId({
|
|
1086
|
+
repoId: selectedRepo.id,
|
|
1087
|
+
repoPath: projectPath,
|
|
1088
|
+
branch,
|
|
1089
|
+
deviceId
|
|
1090
|
+
});
|
|
1091
|
+
const worktreeId = tupleId ?? pathBasedId;
|
|
1092
|
+
const codebyplanPath = join5(projectPath, ".codebyplan.json");
|
|
927
1093
|
const codebyplanConfig = {
|
|
928
1094
|
repo_id: selectedRepo.id
|
|
929
1095
|
};
|
|
930
1096
|
if (worktreeId) codebyplanConfig.worktree_id = worktreeId;
|
|
931
|
-
await
|
|
1097
|
+
await writeFile4(
|
|
932
1098
|
codebyplanPath,
|
|
933
1099
|
JSON.stringify(codebyplanConfig, null, 2) + "\n",
|
|
934
1100
|
"utf-8"
|
|
935
1101
|
);
|
|
936
1102
|
console.log(` Created ${codebyplanPath}`);
|
|
1103
|
+
if (worktreeId) {
|
|
1104
|
+
console.log(
|
|
1105
|
+
` Worktree id set (${worktreeId}) \u2014 this worktree is now identified for hard-lock enforcement.`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
937
1108
|
console.log("\n Running initial sync...\n");
|
|
938
1109
|
try {
|
|
939
1110
|
const { executeSyncToLocal: executeSyncToLocal2 } = await Promise.resolve().then(() => (init_sync_engine(), sync_engine_exports));
|
|
@@ -968,19 +1139,20 @@ async function runSetup() {
|
|
|
968
1139
|
var init_setup = __esm({
|
|
969
1140
|
"src/cli/setup.ts"() {
|
|
970
1141
|
"use strict";
|
|
971
|
-
|
|
1142
|
+
init_resolve_worktree();
|
|
1143
|
+
init_local_config();
|
|
972
1144
|
}
|
|
973
1145
|
});
|
|
974
1146
|
|
|
975
1147
|
// src/cli/config.ts
|
|
976
|
-
import { readFile as
|
|
977
|
-
import { join as
|
|
1148
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
1149
|
+
import { join as join6, resolve } from "node:path";
|
|
978
1150
|
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
979
1151
|
let cursor = resolve(startDir);
|
|
980
1152
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
981
|
-
const configPath =
|
|
1153
|
+
const configPath = join6(cursor, ".codebyplan.json");
|
|
982
1154
|
try {
|
|
983
|
-
const raw = await
|
|
1155
|
+
const raw = await readFile6(configPath, "utf-8");
|
|
984
1156
|
const parsed = JSON.parse(raw);
|
|
985
1157
|
return { path: configPath, contents: parsed };
|
|
986
1158
|
} catch {
|
|
@@ -1031,8 +1203,8 @@ var init_config = __esm({
|
|
|
1031
1203
|
});
|
|
1032
1204
|
|
|
1033
1205
|
// src/cli/fileMapper.ts
|
|
1034
|
-
import { readdir as readdir3, readFile as
|
|
1035
|
-
import { join as
|
|
1206
|
+
import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
|
|
1207
|
+
import { join as join7, extname } from "node:path";
|
|
1036
1208
|
function extractScope(content, type) {
|
|
1037
1209
|
if (type === "hook") {
|
|
1038
1210
|
const match = content.match(/^#\s*@scope:\s*(\S+)/m);
|
|
@@ -1062,29 +1234,29 @@ function compositeKey(type, name, category) {
|
|
|
1062
1234
|
}
|
|
1063
1235
|
async function scanLocalFiles(claudeDir, projectPath) {
|
|
1064
1236
|
const result = /* @__PURE__ */ new Map();
|
|
1065
|
-
await scanCommands(
|
|
1237
|
+
await scanCommands(join7(claudeDir, "commands", "cbp"), result);
|
|
1066
1238
|
await scanSubfolderType(
|
|
1067
|
-
|
|
1239
|
+
join7(claudeDir, "agents"),
|
|
1068
1240
|
"agent",
|
|
1069
1241
|
"AGENT.md",
|
|
1070
1242
|
result
|
|
1071
1243
|
);
|
|
1072
1244
|
await scanSubfolderType(
|
|
1073
|
-
|
|
1245
|
+
join7(claudeDir, "skills"),
|
|
1074
1246
|
"skill",
|
|
1075
1247
|
"SKILL.md",
|
|
1076
1248
|
result
|
|
1077
1249
|
);
|
|
1078
|
-
await scanFlatType(
|
|
1079
|
-
await scanFlatType(
|
|
1080
|
-
await scanTemplates(
|
|
1250
|
+
await scanFlatType(join7(claudeDir, "rules"), "rule", ".md", result);
|
|
1251
|
+
await scanFlatType(join7(claudeDir, "hooks"), "hook", ".sh", result);
|
|
1252
|
+
await scanTemplates(join7(claudeDir, "templates"), result);
|
|
1081
1253
|
await scanCategorizedType(
|
|
1082
|
-
|
|
1254
|
+
join7(claudeDir, "context"),
|
|
1083
1255
|
"context",
|
|
1084
1256
|
".md",
|
|
1085
1257
|
result
|
|
1086
1258
|
);
|
|
1087
|
-
await scanDocsRecursive(
|
|
1259
|
+
await scanDocsRecursive(join7(claudeDir, "docs"), result);
|
|
1088
1260
|
await scanSettings(claudeDir, projectPath, result);
|
|
1089
1261
|
return result;
|
|
1090
1262
|
}
|
|
@@ -1102,12 +1274,12 @@ async function scanCommandsRecursive(baseDir, currentDir, result) {
|
|
|
1102
1274
|
if (entry.isDirectory()) {
|
|
1103
1275
|
await scanCommandsRecursive(
|
|
1104
1276
|
baseDir,
|
|
1105
|
-
|
|
1277
|
+
join7(currentDir, entry.name),
|
|
1106
1278
|
result
|
|
1107
1279
|
);
|
|
1108
1280
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1109
1281
|
const name = entry.name.slice(0, -3);
|
|
1110
|
-
const content = await
|
|
1282
|
+
const content = await readFile7(join7(currentDir, entry.name), "utf-8");
|
|
1111
1283
|
const relDir = currentDir.slice(baseDir.length + 1);
|
|
1112
1284
|
const category = relDir || null;
|
|
1113
1285
|
const scope = extractScope(content, "command");
|
|
@@ -1125,9 +1297,9 @@ async function scanSubfolderType(dir, type, fileName, result) {
|
|
|
1125
1297
|
}
|
|
1126
1298
|
for (const entry of entries) {
|
|
1127
1299
|
if (entry.isDirectory()) {
|
|
1128
|
-
const filePath =
|
|
1300
|
+
const filePath = join7(dir, entry.name, fileName);
|
|
1129
1301
|
try {
|
|
1130
|
-
const content = await
|
|
1302
|
+
const content = await readFile7(filePath, "utf-8");
|
|
1131
1303
|
const scope = extractScope(content, type);
|
|
1132
1304
|
const key = compositeKey(type, entry.name, null);
|
|
1133
1305
|
result.set(key, {
|
|
@@ -1152,7 +1324,7 @@ async function scanFlatType(dir, type, ext, result) {
|
|
|
1152
1324
|
for (const entry of entries) {
|
|
1153
1325
|
if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
1154
1326
|
const name = entry.name.slice(0, -ext.length);
|
|
1155
|
-
const content = await
|
|
1327
|
+
const content = await readFile7(join7(dir, entry.name), "utf-8");
|
|
1156
1328
|
const scope = extractScope(content, type);
|
|
1157
1329
|
const key = compositeKey(type, name, null);
|
|
1158
1330
|
result.set(key, { type, name, category: null, content, scope });
|
|
@@ -1171,7 +1343,7 @@ async function scanCategorizedType(dir, type, ext, result) {
|
|
|
1171
1343
|
const category = entry.name;
|
|
1172
1344
|
let subEntries;
|
|
1173
1345
|
try {
|
|
1174
|
-
subEntries = await readdir3(
|
|
1346
|
+
subEntries = await readdir3(join7(dir, category), {
|
|
1175
1347
|
withFileTypes: true
|
|
1176
1348
|
});
|
|
1177
1349
|
} catch {
|
|
@@ -1180,8 +1352,8 @@ async function scanCategorizedType(dir, type, ext, result) {
|
|
|
1180
1352
|
for (const sub of subEntries) {
|
|
1181
1353
|
if (sub.isFile() && sub.name.endsWith(ext)) {
|
|
1182
1354
|
const name = sub.name.slice(0, -ext.length);
|
|
1183
|
-
const content = await
|
|
1184
|
-
|
|
1355
|
+
const content = await readFile7(
|
|
1356
|
+
join7(dir, category, sub.name),
|
|
1185
1357
|
"utf-8"
|
|
1186
1358
|
);
|
|
1187
1359
|
const scope = extractScope(content, type);
|
|
@@ -1191,7 +1363,7 @@ async function scanCategorizedType(dir, type, ext, result) {
|
|
|
1191
1363
|
}
|
|
1192
1364
|
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
1193
1365
|
const name = entry.name.slice(0, -ext.length);
|
|
1194
|
-
const content = await
|
|
1366
|
+
const content = await readFile7(join7(dir, entry.name), "utf-8");
|
|
1195
1367
|
const scope = extractScope(content, type);
|
|
1196
1368
|
const key = compositeKey(type, name, null);
|
|
1197
1369
|
result.set(key, { type, name, category: null, content, scope });
|
|
@@ -1210,10 +1382,10 @@ async function scanDocsDir(baseDir, currentDir, result) {
|
|
|
1210
1382
|
}
|
|
1211
1383
|
for (const entry of entries) {
|
|
1212
1384
|
if (entry.isDirectory()) {
|
|
1213
|
-
await scanDocsDir(baseDir,
|
|
1385
|
+
await scanDocsDir(baseDir, join7(currentDir, entry.name), result);
|
|
1214
1386
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1215
1387
|
const name = entry.name.slice(0, -3);
|
|
1216
|
-
const content = await
|
|
1388
|
+
const content = await readFile7(join7(currentDir, entry.name), "utf-8");
|
|
1217
1389
|
const scope = extractScope(content, "docs");
|
|
1218
1390
|
const relDir = currentDir.slice(baseDir.length + 1);
|
|
1219
1391
|
const category = relDir || null;
|
|
@@ -1231,7 +1403,7 @@ async function scanTemplates(dir, result) {
|
|
|
1231
1403
|
}
|
|
1232
1404
|
for (const entry of entries) {
|
|
1233
1405
|
if (entry.isFile() && extname(entry.name)) {
|
|
1234
|
-
const content = await
|
|
1406
|
+
const content = await readFile7(join7(dir, entry.name), "utf-8");
|
|
1235
1407
|
const scope = extractScope(content, "template");
|
|
1236
1408
|
const key = compositeKey("template", entry.name, null);
|
|
1237
1409
|
result.set(key, {
|
|
@@ -1245,10 +1417,10 @@ async function scanTemplates(dir, result) {
|
|
|
1245
1417
|
}
|
|
1246
1418
|
}
|
|
1247
1419
|
async function scanSettings(claudeDir, projectPath, result) {
|
|
1248
|
-
const settingsPath =
|
|
1420
|
+
const settingsPath = join7(claudeDir, "settings.json");
|
|
1249
1421
|
let raw;
|
|
1250
1422
|
try {
|
|
1251
|
-
raw = await
|
|
1423
|
+
raw = await readFile7(settingsPath, "utf-8");
|
|
1252
1424
|
} catch {
|
|
1253
1425
|
return;
|
|
1254
1426
|
}
|
|
@@ -1260,7 +1432,7 @@ async function scanSettings(claudeDir, projectPath, result) {
|
|
|
1260
1432
|
}
|
|
1261
1433
|
parsed = stripPermissionsAllow(parsed);
|
|
1262
1434
|
if (parsed.hooks && typeof parsed.hooks === "object") {
|
|
1263
|
-
const hooksDir = projectPath ?
|
|
1435
|
+
const hooksDir = projectPath ? join7(projectPath, ".claude", "hooks") : join7(claudeDir, "hooks");
|
|
1264
1436
|
const discovered = await discoverHooks(hooksDir);
|
|
1265
1437
|
if (discovered.size > 0) {
|
|
1266
1438
|
parsed.hooks = stripDiscoveredHooks(
|
|
@@ -1592,8 +1764,8 @@ var init_confirm = __esm({
|
|
|
1592
1764
|
});
|
|
1593
1765
|
|
|
1594
1766
|
// src/lib/tech-detect.ts
|
|
1595
|
-
import { readFile as
|
|
1596
|
-
import { join as
|
|
1767
|
+
import { readFile as readFile8, access, readdir as readdir4 } from "node:fs/promises";
|
|
1768
|
+
import { join as join8, relative } from "node:path";
|
|
1597
1769
|
async function fileExists(filePath) {
|
|
1598
1770
|
try {
|
|
1599
1771
|
await access(filePath);
|
|
@@ -1606,8 +1778,8 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1606
1778
|
const apps = [];
|
|
1607
1779
|
const patterns = [];
|
|
1608
1780
|
try {
|
|
1609
|
-
const raw = await
|
|
1610
|
-
|
|
1781
|
+
const raw = await readFile8(
|
|
1782
|
+
join8(projectPath, "pnpm-workspace.yaml"),
|
|
1611
1783
|
"utf-8"
|
|
1612
1784
|
);
|
|
1613
1785
|
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
@@ -1621,7 +1793,7 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1621
1793
|
}
|
|
1622
1794
|
if (patterns.length === 0) {
|
|
1623
1795
|
try {
|
|
1624
|
-
const raw = await
|
|
1796
|
+
const raw = await readFile8(join8(projectPath, "package.json"), "utf-8");
|
|
1625
1797
|
const pkg = JSON.parse(raw);
|
|
1626
1798
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1627
1799
|
if (ws) patterns.push(...ws);
|
|
@@ -1631,14 +1803,14 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1631
1803
|
for (const pattern of patterns) {
|
|
1632
1804
|
if (pattern.endsWith("/*")) {
|
|
1633
1805
|
const dir = pattern.slice(0, -2);
|
|
1634
|
-
const absDir =
|
|
1806
|
+
const absDir = join8(projectPath, dir);
|
|
1635
1807
|
try {
|
|
1636
1808
|
const entries = await readdir4(absDir, { withFileTypes: true });
|
|
1637
1809
|
for (const entry of entries) {
|
|
1638
1810
|
if (entry.isDirectory()) {
|
|
1639
|
-
const relPath =
|
|
1640
|
-
const absPath =
|
|
1641
|
-
if (await fileExists(
|
|
1811
|
+
const relPath = join8(dir, entry.name);
|
|
1812
|
+
const absPath = join8(absDir, entry.name);
|
|
1813
|
+
if (await fileExists(join8(absPath, "package.json"))) {
|
|
1642
1814
|
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1643
1815
|
}
|
|
1644
1816
|
}
|
|
@@ -1657,7 +1829,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1657
1829
|
const name = entry.name;
|
|
1658
1830
|
if (entry.isDirectory()) {
|
|
1659
1831
|
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1660
|
-
if (await hasJsxFile(
|
|
1832
|
+
if (await hasJsxFile(join8(dir, name), depth + 1)) return true;
|
|
1661
1833
|
} else if (entry.isFile()) {
|
|
1662
1834
|
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1663
1835
|
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
@@ -1676,7 +1848,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1676
1848
|
async function detectCapabilities(dirPath, pkgJson) {
|
|
1677
1849
|
const caps = /* @__PURE__ */ new Set();
|
|
1678
1850
|
for (const sub of JSX_SCAN_DIRS) {
|
|
1679
|
-
if (await hasJsxFile(
|
|
1851
|
+
if (await hasJsxFile(join8(dirPath, sub))) {
|
|
1680
1852
|
caps.add("jsx");
|
|
1681
1853
|
break;
|
|
1682
1854
|
}
|
|
@@ -1698,7 +1870,7 @@ async function detectCapabilities(dirPath, pkgJson) {
|
|
|
1698
1870
|
}
|
|
1699
1871
|
}
|
|
1700
1872
|
}
|
|
1701
|
-
if (!caps.has("node-server") && await fileExists(
|
|
1873
|
+
if (!caps.has("node-server") && await fileExists(join8(dirPath, "src", "main.ts"))) {
|
|
1702
1874
|
caps.add("node-server");
|
|
1703
1875
|
}
|
|
1704
1876
|
if (pkgJson && pkgJson.bin) {
|
|
@@ -1714,7 +1886,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1714
1886
|
const seen = /* @__PURE__ */ new Map();
|
|
1715
1887
|
let pkgJson = null;
|
|
1716
1888
|
try {
|
|
1717
|
-
const raw = await
|
|
1889
|
+
const raw = await readFile8(join8(dirPath, "package.json"), "utf-8");
|
|
1718
1890
|
pkgJson = JSON.parse(raw);
|
|
1719
1891
|
const allDeps = {
|
|
1720
1892
|
...pkgJson.dependencies ?? {},
|
|
@@ -1746,7 +1918,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1746
1918
|
}
|
|
1747
1919
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1748
1920
|
const key = rule.name.toLowerCase();
|
|
1749
|
-
if (!seen.has(key) && await fileExists(
|
|
1921
|
+
if (!seen.has(key) && await fileExists(join8(dirPath, file))) {
|
|
1750
1922
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1751
1923
|
}
|
|
1752
1924
|
}
|
|
@@ -1924,7 +2096,7 @@ function categorizeDependency(depName) {
|
|
|
1924
2096
|
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
1925
2097
|
if (depth > 4) return [];
|
|
1926
2098
|
const results = [];
|
|
1927
|
-
const pkgPath =
|
|
2099
|
+
const pkgPath = join8(dir, "package.json");
|
|
1928
2100
|
if (await fileExists(pkgPath)) {
|
|
1929
2101
|
results.push(pkgPath);
|
|
1930
2102
|
}
|
|
@@ -1933,7 +2105,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
|
1933
2105
|
for (const entry of entries) {
|
|
1934
2106
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
1935
2107
|
const subResults = await findPackageJsonFiles(
|
|
1936
|
-
|
|
2108
|
+
join8(dir, entry.name),
|
|
1937
2109
|
projectPath,
|
|
1938
2110
|
depth + 1
|
|
1939
2111
|
);
|
|
@@ -1948,7 +2120,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
1948
2120
|
const dependencies = [];
|
|
1949
2121
|
for (const pkgPath of packageJsonPaths) {
|
|
1950
2122
|
try {
|
|
1951
|
-
const raw = await
|
|
2123
|
+
const raw = await readFile8(pkgPath, "utf-8");
|
|
1952
2124
|
const pkg = JSON.parse(raw);
|
|
1953
2125
|
const sourcePath = relative(projectPath, pkgPath);
|
|
1954
2126
|
const depSections = [
|
|
@@ -2197,14 +2369,14 @@ var init_server_detect = __esm({
|
|
|
2197
2369
|
});
|
|
2198
2370
|
|
|
2199
2371
|
// src/lib/port-verify.ts
|
|
2200
|
-
import { readFile as
|
|
2372
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
2201
2373
|
async function verifyPorts(projectPath, portAllocations) {
|
|
2202
2374
|
const mismatches = [];
|
|
2203
2375
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
2204
2376
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
2205
2377
|
for (const pkgPath of packageJsonPaths) {
|
|
2206
2378
|
try {
|
|
2207
|
-
const raw = await
|
|
2379
|
+
const raw = await readFile9(pkgPath, "utf-8");
|
|
2208
2380
|
const pkg = JSON.parse(raw);
|
|
2209
2381
|
const scriptPort = detectPortFromScripts(pkg);
|
|
2210
2382
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -2267,7 +2439,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
2267
2439
|
}
|
|
2268
2440
|
let pkg;
|
|
2269
2441
|
try {
|
|
2270
|
-
const raw = await
|
|
2442
|
+
const raw = await readFile9(`${app.absPath}/package.json`, "utf-8");
|
|
2271
2443
|
pkg = JSON.parse(raw);
|
|
2272
2444
|
} catch {
|
|
2273
2445
|
continue;
|
|
@@ -2311,8 +2483,70 @@ var init_port_verify = __esm({
|
|
|
2311
2483
|
}
|
|
2312
2484
|
});
|
|
2313
2485
|
|
|
2486
|
+
// src/lib/migrate-local-config.ts
|
|
2487
|
+
import { readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
|
|
2488
|
+
import { join as join9 } from "node:path";
|
|
2489
|
+
function sharedConfigPath(projectPath) {
|
|
2490
|
+
return join9(projectPath, ".codebyplan.json");
|
|
2491
|
+
}
|
|
2492
|
+
async function needsLocalMigration(projectPath) {
|
|
2493
|
+
try {
|
|
2494
|
+
const raw = await readFile10(sharedConfigPath(projectPath), "utf-8");
|
|
2495
|
+
const parsed = JSON.parse(raw);
|
|
2496
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2497
|
+
return false;
|
|
2498
|
+
}
|
|
2499
|
+
const cfg = parsed;
|
|
2500
|
+
if (typeof cfg.worktree_id !== "string" || cfg.worktree_id === "") {
|
|
2501
|
+
return false;
|
|
2502
|
+
}
|
|
2503
|
+
const local = await readLocalConfig(projectPath);
|
|
2504
|
+
if (local?.device_id) {
|
|
2505
|
+
return false;
|
|
2506
|
+
}
|
|
2507
|
+
return true;
|
|
2508
|
+
} catch {
|
|
2509
|
+
return false;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
async function runLocalMigration(projectPath) {
|
|
2513
|
+
const raw = await readFile10(sharedConfigPath(projectPath), "utf-8");
|
|
2514
|
+
const parsed = JSON.parse(raw);
|
|
2515
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2516
|
+
throw new Error(
|
|
2517
|
+
".codebyplan.json does not contain a JSON object \u2014 cannot migrate"
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
const cfg = parsed;
|
|
2521
|
+
const hadWorktreeId = "worktree_id" in cfg;
|
|
2522
|
+
const localBefore = await readLocalConfig(projectPath);
|
|
2523
|
+
const localWillBeCreated = !localBefore?.device_id;
|
|
2524
|
+
const device_id = await getOrCreateDeviceId(projectPath);
|
|
2525
|
+
const cleaned = { ...cfg };
|
|
2526
|
+
delete cleaned.worktree_id;
|
|
2527
|
+
await writeFile5(
|
|
2528
|
+
sharedConfigPath(projectPath),
|
|
2529
|
+
JSON.stringify(cleaned, null, 2) + "\n",
|
|
2530
|
+
"utf-8"
|
|
2531
|
+
);
|
|
2532
|
+
const files_changed = [".codebyplan.json"];
|
|
2533
|
+
if (localWillBeCreated) files_changed.push(".codebyplan.local.json");
|
|
2534
|
+
return {
|
|
2535
|
+
migrated: true,
|
|
2536
|
+
was_dirty: hadWorktreeId || localWillBeCreated,
|
|
2537
|
+
files_changed,
|
|
2538
|
+
device_id
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
var init_migrate_local_config = __esm({
|
|
2542
|
+
"src/lib/migrate-local-config.ts"() {
|
|
2543
|
+
"use strict";
|
|
2544
|
+
init_local_config();
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2314
2548
|
// src/lib/eslint-generator.ts
|
|
2315
|
-
import { createHash } from "node:crypto";
|
|
2549
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2316
2550
|
function importedIdentifiers(importLines) {
|
|
2317
2551
|
const names = /* @__PURE__ */ new Set();
|
|
2318
2552
|
for (const line of importLines) {
|
|
@@ -2380,7 +2614,7 @@ function collectDependencies(presets) {
|
|
|
2380
2614
|
return deps;
|
|
2381
2615
|
}
|
|
2382
2616
|
function hashConfig(content) {
|
|
2383
|
-
return
|
|
2617
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
2384
2618
|
}
|
|
2385
2619
|
function buildRules(presets) {
|
|
2386
2620
|
const merged = {};
|
|
@@ -2700,8 +2934,8 @@ __export(eslint_exports, {
|
|
|
2700
2934
|
eslintSync: () => eslintSync,
|
|
2701
2935
|
runEslint: () => runEslint
|
|
2702
2936
|
});
|
|
2703
|
-
import { readFile as
|
|
2704
|
-
import { join as
|
|
2937
|
+
import { readFile as readFile11, writeFile as writeFile6, access as access2, readdir as readdir5 } from "node:fs/promises";
|
|
2938
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
2705
2939
|
async function fileExists2(filePath) {
|
|
2706
2940
|
try {
|
|
2707
2941
|
await access2(filePath);
|
|
@@ -2712,7 +2946,7 @@ async function fileExists2(filePath) {
|
|
|
2712
2946
|
}
|
|
2713
2947
|
async function autoDetectIgnorePatterns(absPath) {
|
|
2714
2948
|
const patterns = [];
|
|
2715
|
-
if (await fileExists2(
|
|
2949
|
+
if (await fileExists2(join10(absPath, "esbuild.js"))) {
|
|
2716
2950
|
patterns.push("esbuild.js");
|
|
2717
2951
|
}
|
|
2718
2952
|
let entries = [];
|
|
@@ -2732,19 +2966,19 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2732
2966
|
}
|
|
2733
2967
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2734
2968
|
const candidate = `vitest.config.${ext}`;
|
|
2735
|
-
if (await fileExists2(
|
|
2969
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2736
2970
|
patterns.push(candidate);
|
|
2737
2971
|
break;
|
|
2738
2972
|
}
|
|
2739
2973
|
}
|
|
2740
2974
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2741
2975
|
const candidate = `vite.config.${ext}`;
|
|
2742
|
-
if (await fileExists2(
|
|
2976
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2743
2977
|
patterns.push(candidate);
|
|
2744
2978
|
break;
|
|
2745
2979
|
}
|
|
2746
2980
|
}
|
|
2747
|
-
if (await fileExists2(
|
|
2981
|
+
if (await fileExists2(join10(absPath, "tauri.conf.json"))) {
|
|
2748
2982
|
patterns.push("src-tauri/**");
|
|
2749
2983
|
patterns.push("**/*.d.ts");
|
|
2750
2984
|
}
|
|
@@ -2752,14 +2986,14 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2752
2986
|
}
|
|
2753
2987
|
function detectPackageManager(projectPath) {
|
|
2754
2988
|
return (async () => {
|
|
2755
|
-
if (await fileExists2(
|
|
2756
|
-
if (await fileExists2(
|
|
2989
|
+
if (await fileExists2(join10(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2990
|
+
if (await fileExists2(join10(projectPath, "yarn.lock"))) return "yarn";
|
|
2757
2991
|
return "npm";
|
|
2758
2992
|
})();
|
|
2759
2993
|
}
|
|
2760
2994
|
async function getInstalledDeps(pkgJsonPath) {
|
|
2761
2995
|
try {
|
|
2762
|
-
const raw = await
|
|
2996
|
+
const raw = await readFile11(pkgJsonPath, "utf-8");
|
|
2763
2997
|
const pkg = JSON.parse(raw);
|
|
2764
2998
|
const all = /* @__PURE__ */ new Set();
|
|
2765
2999
|
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
@@ -2872,7 +3106,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2872
3106
|
ignorePatterns: detectedIgnores
|
|
2873
3107
|
});
|
|
2874
3108
|
const hash = hashConfig(content);
|
|
2875
|
-
const configPath =
|
|
3109
|
+
const configPath = join10(target.absPath, "eslint.config.mjs");
|
|
2876
3110
|
configsToWrite.push({
|
|
2877
3111
|
target,
|
|
2878
3112
|
presets,
|
|
@@ -2894,11 +3128,11 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2894
3128
|
return;
|
|
2895
3129
|
}
|
|
2896
3130
|
const pm = await detectPackageManager(projectPath);
|
|
2897
|
-
const rootPkgJsonPath =
|
|
3131
|
+
const rootPkgJsonPath = join10(projectPath, "package.json");
|
|
2898
3132
|
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2899
3133
|
if (isMonorepo) {
|
|
2900
3134
|
for (const { target } of configsToWrite) {
|
|
2901
|
-
const appPkgJson =
|
|
3135
|
+
const appPkgJson = join10(target.absPath, "package.json");
|
|
2902
3136
|
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2903
3137
|
for (const dep of appDeps) {
|
|
2904
3138
|
installed.add(dep);
|
|
@@ -2924,9 +3158,9 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2924
3158
|
Install ${missingPkgs.length} missing packages? [Y/n] `
|
|
2925
3159
|
);
|
|
2926
3160
|
if (confirmed) {
|
|
2927
|
-
const { execSync } = await import("node:child_process");
|
|
3161
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
2928
3162
|
try {
|
|
2929
|
-
|
|
3163
|
+
execSync3(installCmd, { cwd: projectPath, stdio: "inherit" });
|
|
2930
3164
|
console.log(" Packages installed.\n");
|
|
2931
3165
|
} catch (err) {
|
|
2932
3166
|
console.error(
|
|
@@ -2950,7 +3184,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2950
3184
|
} of configsToWrite) {
|
|
2951
3185
|
if (await fileExists2(configPath)) {
|
|
2952
3186
|
try {
|
|
2953
|
-
const existing = await
|
|
3187
|
+
const existing = await readFile11(configPath, "utf-8");
|
|
2954
3188
|
const existingHash = hashConfig(existing);
|
|
2955
3189
|
if (existingHash === hash) {
|
|
2956
3190
|
console.log(
|
|
@@ -2970,7 +3204,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2970
3204
|
}
|
|
2971
3205
|
}
|
|
2972
3206
|
try {
|
|
2973
|
-
await
|
|
3207
|
+
await writeFile6(configPath, content, "utf-8");
|
|
2974
3208
|
} catch (err) {
|
|
2975
3209
|
console.error(
|
|
2976
3210
|
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3020,8 +3254,8 @@ async function eslintSync(repoId, projectPath) {
|
|
|
3020
3254
|
let skippedCount = 0;
|
|
3021
3255
|
let driftCount = 0;
|
|
3022
3256
|
for (const config of configs) {
|
|
3023
|
-
const absPath = config.source_path === "." ? projectPath :
|
|
3024
|
-
const configPath =
|
|
3257
|
+
const absPath = config.source_path === "." ? projectPath : join10(projectPath, config.source_path);
|
|
3258
|
+
const configPath = join10(absPath, "eslint.config.mjs");
|
|
3025
3259
|
const detected = await detectTechStack(absPath);
|
|
3026
3260
|
const techNames = detected.flat.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
|
|
3027
3261
|
const capabilities = collectCapabilities(detected.flat);
|
|
@@ -3035,7 +3269,7 @@ async function eslintSync(repoId, projectPath) {
|
|
|
3035
3269
|
if (!presetsChanged) {
|
|
3036
3270
|
if (await fileExists2(configPath)) {
|
|
3037
3271
|
try {
|
|
3038
|
-
const currentContent = await
|
|
3272
|
+
const currentContent = await readFile11(configPath, "utf-8");
|
|
3039
3273
|
const currentHash = hashConfig(currentContent);
|
|
3040
3274
|
if (config.generated_hash && currentHash !== config.generated_hash) {
|
|
3041
3275
|
console.log(
|
|
@@ -3068,7 +3302,7 @@ async function eslintSync(repoId, projectPath) {
|
|
|
3068
3302
|
ignorePatterns: detectedIgnores
|
|
3069
3303
|
});
|
|
3070
3304
|
try {
|
|
3071
|
-
await
|
|
3305
|
+
await writeFile6(configPath, content, "utf-8");
|
|
3072
3306
|
} catch (err) {
|
|
3073
3307
|
console.error(
|
|
3074
3308
|
` ${config.source_path}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3104,11 +3338,11 @@ async function checkEslintDrift(repoId, projectPath) {
|
|
|
3104
3338
|
const configs = res.data ?? [];
|
|
3105
3339
|
for (const config of configs) {
|
|
3106
3340
|
if (!config.generated_hash) continue;
|
|
3107
|
-
const absPath = config.source_path === "." ? projectPath :
|
|
3108
|
-
const configPath =
|
|
3341
|
+
const absPath = config.source_path === "." ? projectPath : join10(projectPath, config.source_path);
|
|
3342
|
+
const configPath = join10(absPath, "eslint.config.mjs");
|
|
3109
3343
|
if (!await fileExists2(configPath)) continue;
|
|
3110
3344
|
try {
|
|
3111
|
-
const content = await
|
|
3345
|
+
const content = await readFile11(configPath, "utf-8");
|
|
3112
3346
|
const currentHash = hashConfig(content);
|
|
3113
3347
|
if (currentHash !== config.generated_hash) {
|
|
3114
3348
|
return true;
|
|
@@ -3159,11 +3393,11 @@ var sync_exports = {};
|
|
|
3159
3393
|
__export(sync_exports, {
|
|
3160
3394
|
runSync: () => runSync
|
|
3161
3395
|
});
|
|
3162
|
-
import { createHash as
|
|
3163
|
-
import { readFile as
|
|
3164
|
-
import { join as
|
|
3396
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3397
|
+
import { readFile as readFile12, writeFile as writeFile7, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
|
|
3398
|
+
import { join as join11, dirname as dirname2 } from "node:path";
|
|
3165
3399
|
function contentHash(content) {
|
|
3166
|
-
return
|
|
3400
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
3167
3401
|
}
|
|
3168
3402
|
async function runSync() {
|
|
3169
3403
|
const flags = parseFlags(3);
|
|
@@ -3229,7 +3463,7 @@ async function runSync() {
|
|
|
3229
3463
|
}
|
|
3230
3464
|
async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
3231
3465
|
console.log(" Reading local and remote state...");
|
|
3232
|
-
const claudeDir =
|
|
3466
|
+
const claudeDir = join11(projectPath, ".claude");
|
|
3233
3467
|
let localFiles = /* @__PURE__ */ new Map();
|
|
3234
3468
|
try {
|
|
3235
3469
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
@@ -3464,7 +3698,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
3464
3698
|
for (const p of toPull) {
|
|
3465
3699
|
if (p.filePath && p.remoteContent !== null) {
|
|
3466
3700
|
await mkdir2(dirname2(p.filePath), { recursive: true });
|
|
3467
|
-
await
|
|
3701
|
+
await writeFile7(p.filePath, p.remoteContent, "utf-8");
|
|
3468
3702
|
if (p.isHook) await chmod2(p.filePath, 493);
|
|
3469
3703
|
}
|
|
3470
3704
|
}
|
|
@@ -3618,7 +3852,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
3618
3852
|
console.log("\n Sync complete.\n");
|
|
3619
3853
|
}
|
|
3620
3854
|
async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
|
|
3621
|
-
const settingsPath =
|
|
3855
|
+
const settingsPath = join11(claudeDir, "settings.json");
|
|
3622
3856
|
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
3623
3857
|
let globalSettings = {};
|
|
3624
3858
|
for (const gf of globalSettingsFiles) {
|
|
@@ -3638,11 +3872,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
3638
3872
|
globalSettings,
|
|
3639
3873
|
repoSettings
|
|
3640
3874
|
);
|
|
3641
|
-
const hooksDir =
|
|
3875
|
+
const hooksDir = join11(projectPath, ".claude", "hooks");
|
|
3642
3876
|
const discovered = await discoverHooks(hooksDir);
|
|
3643
3877
|
let localSettings = {};
|
|
3644
3878
|
try {
|
|
3645
|
-
const raw = await
|
|
3879
|
+
const raw = await readFile12(settingsPath, "utf-8");
|
|
3646
3880
|
localSettings = JSON.parse(raw);
|
|
3647
3881
|
} catch {
|
|
3648
3882
|
}
|
|
@@ -3657,7 +3891,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
3657
3891
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
3658
3892
|
let currentContent = "";
|
|
3659
3893
|
try {
|
|
3660
|
-
currentContent = await
|
|
3894
|
+
currentContent = await readFile12(settingsPath, "utf-8");
|
|
3661
3895
|
} catch {
|
|
3662
3896
|
}
|
|
3663
3897
|
if (currentContent === mergedContent) {
|
|
@@ -3669,18 +3903,74 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
3669
3903
|
return;
|
|
3670
3904
|
}
|
|
3671
3905
|
await mkdir2(dirname2(settingsPath), { recursive: true });
|
|
3672
|
-
await
|
|
3906
|
+
await writeFile7(settingsPath, mergedContent, "utf-8");
|
|
3673
3907
|
console.log(" Updated settings.json");
|
|
3674
3908
|
}
|
|
3675
3909
|
async function syncConfig(repoId, projectPath, dryRun) {
|
|
3676
|
-
const configPath =
|
|
3910
|
+
const configPath = join11(projectPath, ".codebyplan.json");
|
|
3677
3911
|
let currentConfig = {};
|
|
3678
3912
|
try {
|
|
3679
|
-
const raw = await
|
|
3913
|
+
const raw = await readFile12(configPath, "utf-8");
|
|
3680
3914
|
currentConfig = JSON.parse(raw);
|
|
3681
3915
|
} catch {
|
|
3682
3916
|
currentConfig = { repo_id: repoId };
|
|
3683
3917
|
}
|
|
3918
|
+
if (dryRun) {
|
|
3919
|
+
try {
|
|
3920
|
+
if (await needsLocalMigration(projectPath)) {
|
|
3921
|
+
console.log(
|
|
3922
|
+
` Would migrate .codebyplan.json -> worktree_id to .codebyplan.local.json (dry-run, skipping actual write).`
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3925
|
+
} catch {
|
|
3926
|
+
}
|
|
3927
|
+
} else {
|
|
3928
|
+
try {
|
|
3929
|
+
if (await needsLocalMigration(projectPath)) {
|
|
3930
|
+
const result = await runLocalMigration(projectPath);
|
|
3931
|
+
delete currentConfig.worktree_id;
|
|
3932
|
+
console.log(
|
|
3933
|
+
` Migrated .codebyplan.json -> moved worktree_id to gitignored .codebyplan.local.json (device_id=${result.device_id.slice(0, 8)})`
|
|
3934
|
+
);
|
|
3935
|
+
console.log(
|
|
3936
|
+
` Suggest /cbp-git-commit to stage the cleaned shared file.`
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
} catch (err) {
|
|
3940
|
+
console.warn(
|
|
3941
|
+
` Warning: local migration failed (continuing): ${err instanceof Error ? err.message : String(err)}`
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
let resolvedWorktreeId;
|
|
3946
|
+
try {
|
|
3947
|
+
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
3948
|
+
let branch = "main";
|
|
3949
|
+
try {
|
|
3950
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
3951
|
+
branch = execSync3("git symbolic-ref --short HEAD", {
|
|
3952
|
+
cwd: projectPath,
|
|
3953
|
+
encoding: "utf-8"
|
|
3954
|
+
}).trim();
|
|
3955
|
+
} catch {
|
|
3956
|
+
}
|
|
3957
|
+
const tupleId = await resolveWorktreeId({
|
|
3958
|
+
repoId,
|
|
3959
|
+
repoPath: projectPath,
|
|
3960
|
+
branch,
|
|
3961
|
+
deviceId
|
|
3962
|
+
});
|
|
3963
|
+
if (tupleId) {
|
|
3964
|
+
resolvedWorktreeId = tupleId;
|
|
3965
|
+
} else {
|
|
3966
|
+
resolvedWorktreeId = await resolveAndCacheWorktreeId(repoId, projectPath) ?? void 0;
|
|
3967
|
+
}
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3970
|
+
console.warn(
|
|
3971
|
+
` Warning: failed to cache worktree_id (self-heal skipped): ${msg}`
|
|
3972
|
+
);
|
|
3973
|
+
}
|
|
3684
3974
|
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
3685
3975
|
const repo = repoRes.data;
|
|
3686
3976
|
let portAllocations = [];
|
|
@@ -3690,8 +3980,8 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
3690
3980
|
{ repo_id: repoId }
|
|
3691
3981
|
);
|
|
3692
3982
|
const allAllocations = portsRes.data ?? [];
|
|
3693
|
-
const
|
|
3694
|
-
const filtered =
|
|
3983
|
+
const filteredByWorktree = resolvedWorktreeId;
|
|
3984
|
+
const filtered = filteredByWorktree ? allAllocations.filter((a) => a.worktree_id === filteredByWorktree) : allAllocations.filter((a) => !a.worktree_id);
|
|
3695
3985
|
const ALLOWED_FIELDS = [
|
|
3696
3986
|
"id",
|
|
3697
3987
|
"repo_id",
|
|
@@ -3719,7 +4009,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
3719
4009
|
` Warning: failed to fetch port allocations: ${err instanceof Error ? err.message : String(err)}`
|
|
3720
4010
|
);
|
|
3721
4011
|
}
|
|
3722
|
-
const worktreeId =
|
|
4012
|
+
const worktreeId = resolvedWorktreeId;
|
|
3723
4013
|
const matchingAlloc = portAllocations[0];
|
|
3724
4014
|
const defaultBranchConfig = {
|
|
3725
4015
|
protected: ["main", "development"],
|
|
@@ -3730,7 +4020,9 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
3730
4020
|
const branchConfig = repo.branch_config ?? defaultBranchConfig;
|
|
3731
4021
|
const newConfig = {
|
|
3732
4022
|
repo_id: repoId,
|
|
3733
|
-
|
|
4023
|
+
// worktree_id is intentionally omitted — it is never persisted in
|
|
4024
|
+
// .codebyplan.json (CHK-108). The in-memory worktreeId is used only
|
|
4025
|
+
// for server_port / server_type resolution immediately below.
|
|
3734
4026
|
server_port: worktreeId && matchingAlloc ? matchingAlloc.port : repo.server_port,
|
|
3735
4027
|
server_type: worktreeId && matchingAlloc ? matchingAlloc.server_type : repo.server_type,
|
|
3736
4028
|
git_branch: repo.git_branch ?? "development",
|
|
@@ -3748,7 +4040,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
3748
4040
|
console.log(" Config would be updated (dry-run).");
|
|
3749
4041
|
return;
|
|
3750
4042
|
}
|
|
3751
|
-
await
|
|
4043
|
+
await writeFile7(configPath, newJson + "\n", "utf-8");
|
|
3752
4044
|
console.log(" Updated .codebyplan.json");
|
|
3753
4045
|
}
|
|
3754
4046
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
@@ -3900,28 +4192,28 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
3900
4192
|
hook: { dir: "hooks", ext: ".sh" },
|
|
3901
4193
|
template: { dir: "templates", ext: "" },
|
|
3902
4194
|
context: { dir: "context", ext: ".md" },
|
|
3903
|
-
docs_stack: { dir:
|
|
4195
|
+
docs_stack: { dir: join11("docs", "stack"), ext: ".md" },
|
|
3904
4196
|
docs: { dir: "docs", ext: ".md" },
|
|
3905
4197
|
claude_md: { dir: "", ext: "" },
|
|
3906
4198
|
settings: { dir: "", ext: "" }
|
|
3907
4199
|
};
|
|
3908
|
-
if (remote.type === "claude_md") return
|
|
3909
|
-
if (remote.type === "settings") return
|
|
4200
|
+
if (remote.type === "claude_md") return join11(projectPath, "CLAUDE.md");
|
|
4201
|
+
if (remote.type === "settings") return join11(claudeDir, "settings.json");
|
|
3910
4202
|
const cfg = typeConfig2[remote.type];
|
|
3911
|
-
if (!cfg) return
|
|
3912
|
-
const typeDir = remote.type === "command" ?
|
|
4203
|
+
if (!cfg) return join11(claudeDir, remote.name);
|
|
4204
|
+
const typeDir = remote.type === "command" ? join11(claudeDir, cfg.dir, "cbp") : join11(claudeDir, cfg.dir);
|
|
3913
4205
|
if (cfg.subfolder)
|
|
3914
|
-
return
|
|
4206
|
+
return join11(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
3915
4207
|
if (remote.type === "command" && remote.category)
|
|
3916
|
-
return
|
|
3917
|
-
if (remote.type === "template") return
|
|
4208
|
+
return join11(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
4209
|
+
if (remote.type === "template") return join11(typeDir, remote.name);
|
|
3918
4210
|
if (remote.category && (remote.type === "context" || remote.type === "docs_stack" || remote.type === "docs"))
|
|
3919
|
-
return
|
|
3920
|
-
return
|
|
4211
|
+
return join11(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
4212
|
+
return join11(typeDir, `${remote.name}${cfg.ext}`);
|
|
3921
4213
|
}
|
|
3922
4214
|
function getSyncVersion() {
|
|
3923
4215
|
try {
|
|
3924
|
-
return "1.
|
|
4216
|
+
return "1.5.0";
|
|
3925
4217
|
} catch {
|
|
3926
4218
|
return "unknown";
|
|
3927
4219
|
}
|
|
@@ -3970,10 +4262,64 @@ var init_sync = __esm({
|
|
|
3970
4262
|
init_settings_merge();
|
|
3971
4263
|
init_hook_registry();
|
|
3972
4264
|
init_port_verify();
|
|
4265
|
+
init_resolve_worktree();
|
|
4266
|
+
init_local_config();
|
|
4267
|
+
init_migrate_local_config();
|
|
3973
4268
|
init_eslint();
|
|
3974
4269
|
}
|
|
3975
4270
|
});
|
|
3976
4271
|
|
|
4272
|
+
// src/cli/resolve-worktree.ts
|
|
4273
|
+
var resolve_worktree_exports = {};
|
|
4274
|
+
__export(resolve_worktree_exports, {
|
|
4275
|
+
runResolveWorktree: () => runResolveWorktree
|
|
4276
|
+
});
|
|
4277
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
4278
|
+
async function runResolveWorktree() {
|
|
4279
|
+
try {
|
|
4280
|
+
const projectPath = process.cwd();
|
|
4281
|
+
const found = await findCodebyplanConfig(projectPath);
|
|
4282
|
+
if (!found?.contents.repo_id) {
|
|
4283
|
+
process.exit(0);
|
|
4284
|
+
}
|
|
4285
|
+
const repoId = found.contents.repo_id;
|
|
4286
|
+
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
4287
|
+
let branch = "";
|
|
4288
|
+
try {
|
|
4289
|
+
branch = execSync2("git symbolic-ref --short HEAD", {
|
|
4290
|
+
cwd: projectPath,
|
|
4291
|
+
encoding: "utf-8"
|
|
4292
|
+
}).trim();
|
|
4293
|
+
} catch {
|
|
4294
|
+
}
|
|
4295
|
+
const worktreeId = await resolveWorktreeId({
|
|
4296
|
+
repoId,
|
|
4297
|
+
repoPath: projectPath,
|
|
4298
|
+
branch,
|
|
4299
|
+
deviceId
|
|
4300
|
+
});
|
|
4301
|
+
if (worktreeId) {
|
|
4302
|
+
process.stdout.write(worktreeId);
|
|
4303
|
+
}
|
|
4304
|
+
process.exit(0);
|
|
4305
|
+
} catch (err) {
|
|
4306
|
+
if (process.env.CODEBYPLAN_DEBUG === "1") {
|
|
4307
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4308
|
+
process.stderr.write(`resolve-worktree: ${msg}
|
|
4309
|
+
`);
|
|
4310
|
+
}
|
|
4311
|
+
process.exit(0);
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
var init_resolve_worktree2 = __esm({
|
|
4315
|
+
"src/cli/resolve-worktree.ts"() {
|
|
4316
|
+
"use strict";
|
|
4317
|
+
init_config();
|
|
4318
|
+
init_local_config();
|
|
4319
|
+
init_resolve_worktree();
|
|
4320
|
+
}
|
|
4321
|
+
});
|
|
4322
|
+
|
|
3977
4323
|
// src/index.ts
|
|
3978
4324
|
init_version();
|
|
3979
4325
|
import { readFileSync } from "node:fs";
|
|
@@ -4038,16 +4384,22 @@ void (async () => {
|
|
|
4038
4384
|
}
|
|
4039
4385
|
process.exit(0);
|
|
4040
4386
|
}
|
|
4387
|
+
if (arg === "resolve-worktree") {
|
|
4388
|
+
const { runResolveWorktree: runResolveWorktree2 } = await Promise.resolve().then(() => (init_resolve_worktree2(), resolve_worktree_exports));
|
|
4389
|
+
await runResolveWorktree2();
|
|
4390
|
+
process.exit(0);
|
|
4391
|
+
}
|
|
4041
4392
|
if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
4042
4393
|
console.log(`
|
|
4043
4394
|
CodeByPlan CLI v${VERSION}
|
|
4044
4395
|
|
|
4045
4396
|
Usage:
|
|
4046
|
-
codebyplan setup
|
|
4047
|
-
codebyplan sync
|
|
4048
|
-
codebyplan eslint
|
|
4049
|
-
codebyplan
|
|
4050
|
-
codebyplan
|
|
4397
|
+
codebyplan setup Interactive setup (API key + project init + first sync)
|
|
4398
|
+
codebyplan sync Bidirectional sync (pull + push + config)
|
|
4399
|
+
codebyplan eslint ESLint config management (init, sync)
|
|
4400
|
+
codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
|
|
4401
|
+
codebyplan help Show this help message
|
|
4402
|
+
codebyplan --version Print version
|
|
4051
4403
|
|
|
4052
4404
|
Sync options:
|
|
4053
4405
|
--path <dir> Project root directory (default: cwd)
|