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.
- package/README.md +2 -0
- package/dist/cli.js +221 -145
- 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.
|
|
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
|
|
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
|
|
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
|
|
451
|
+
import { join as join3, dirname } from "node:path";
|
|
390
452
|
function getTypeDir(claudeDir, dir) {
|
|
391
|
-
if (dir === "commands") return
|
|
392
|
-
return
|
|
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
|
|
460
|
+
return join3(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
|
|
399
461
|
}
|
|
400
462
|
if (typeName === "command" && file.category) {
|
|
401
|
-
return
|
|
463
|
+
return join3(typeDir, file.category, `${file.name}${cfg.ext}`);
|
|
402
464
|
}
|
|
403
465
|
if (typeName === "template") {
|
|
404
|
-
return
|
|
466
|
+
return join3(typeDir, file.name);
|
|
405
467
|
}
|
|
406
|
-
return
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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: () =>
|
|
603
|
-
settings: () =>
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
842
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
781
843
|
import { homedir } from "node:os";
|
|
782
|
-
import { join as
|
|
844
|
+
import { join as join4 } from "node:path";
|
|
783
845
|
function getConfigPath(scope) {
|
|
784
|
-
return scope === "user" ?
|
|
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
|
|
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
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
|
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
|
-
|
|
1034
|
+
init_resolve_worktree();
|
|
972
1035
|
}
|
|
973
1036
|
});
|
|
974
1037
|
|
|
975
1038
|
// src/cli/config.ts
|
|
976
|
-
import { readFile as
|
|
977
|
-
import { join as
|
|
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 =
|
|
1044
|
+
const configPath = join5(cursor, ".codebyplan.json");
|
|
982
1045
|
try {
|
|
983
|
-
const raw = await
|
|
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
|
|
1035
|
-
import { join as
|
|
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(
|
|
1128
|
+
await scanCommands(join6(claudeDir, "commands", "cbp"), result);
|
|
1066
1129
|
await scanSubfolderType(
|
|
1067
|
-
|
|
1130
|
+
join6(claudeDir, "agents"),
|
|
1068
1131
|
"agent",
|
|
1069
1132
|
"AGENT.md",
|
|
1070
1133
|
result
|
|
1071
1134
|
);
|
|
1072
1135
|
await scanSubfolderType(
|
|
1073
|
-
|
|
1136
|
+
join6(claudeDir, "skills"),
|
|
1074
1137
|
"skill",
|
|
1075
1138
|
"SKILL.md",
|
|
1076
1139
|
result
|
|
1077
1140
|
);
|
|
1078
|
-
await scanFlatType(
|
|
1079
|
-
await scanFlatType(
|
|
1080
|
-
await scanTemplates(
|
|
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
|
-
|
|
1145
|
+
join6(claudeDir, "context"),
|
|
1083
1146
|
"context",
|
|
1084
1147
|
".md",
|
|
1085
1148
|
result
|
|
1086
1149
|
);
|
|
1087
|
-
await scanDocsRecursive(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1191
|
+
const filePath = join6(dir, entry.name, fileName);
|
|
1129
1192
|
try {
|
|
1130
|
-
const content = await
|
|
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
|
|
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(
|
|
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
|
|
1184
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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 =
|
|
1311
|
+
const settingsPath = join6(claudeDir, "settings.json");
|
|
1249
1312
|
let raw;
|
|
1250
1313
|
try {
|
|
1251
|
-
raw = await
|
|
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 ?
|
|
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
|
|
1596
|
-
import { join as
|
|
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
|
|
1610
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
1640
|
-
const absPath =
|
|
1641
|
-
if (await fileExists(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2704
|
-
import { join as
|
|
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(
|
|
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(
|
|
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(
|
|
2805
|
+
if (await fileExists2(join8(absPath, candidate))) {
|
|
2743
2806
|
patterns.push(candidate);
|
|
2744
2807
|
break;
|
|
2745
2808
|
}
|
|
2746
2809
|
}
|
|
2747
|
-
if (await fileExists2(
|
|
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(
|
|
2756
|
-
if (await fileExists2(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 :
|
|
3024
|
-
const configPath =
|
|
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
|
|
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
|
|
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 :
|
|
3108
|
-
const configPath =
|
|
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
|
|
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
|
|
3164
|
-
import { join as
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
3739
|
+
const configPath = join9(projectPath, ".codebyplan.json");
|
|
3677
3740
|
let currentConfig = {};
|
|
3678
3741
|
try {
|
|
3679
|
-
const raw = await
|
|
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
|
|
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:
|
|
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
|
|
3909
|
-
if (remote.type === "settings") return
|
|
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
|
|
3912
|
-
const typeDir = remote.type === "command" ?
|
|
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
|
|
3989
|
+
return join9(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
3915
3990
|
if (remote.type === "command" && remote.category)
|
|
3916
|
-
return
|
|
3917
|
-
if (remote.type === "template") return
|
|
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
|
|
3920
|
-
return
|
|
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.
|
|
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
|
});
|