codebyplan 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +10 -7
  2. package/dist/cli.js +790 -289
  3. package/package.json +3 -3
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.1";
17
+ VERSION = "1.4.3";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -52,7 +52,7 @@ function isRetryable(err) {
52
52
  return false;
53
53
  }
54
54
  function delay(ms) {
55
- return new Promise((resolve2) => setTimeout(resolve2, ms));
55
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
56
56
  }
57
57
  async function request(method, path, options) {
58
58
  const url = buildUrl(path, options?.params);
@@ -126,7 +126,7 @@ var init_api = __esm({
126
126
  "use strict";
127
127
  init_version();
128
128
  API_KEY = process.env.CODEBYPLAN_API_KEY ?? "";
129
- BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com").replace(/\/$/, "");
129
+ BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
130
130
  REQUEST_TIMEOUT_MS = 12e4;
131
131
  MAX_RETRIES = 3;
132
132
  BASE_DELAY_MS = 1e3;
@@ -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,14 +270,17 @@ 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
- const match = content.match(/^#\s*@hook:\s*(\S+)(?:\s+(.+))?$/m);
215
- if (!match) return null;
276
+ const lineMatch = content.match(/^#\s*@hook:(.*)$/m);
277
+ if (!lineMatch) return null;
278
+ const parts = lineMatch[1].trim().split(/\s+/);
279
+ const event = parts[0];
280
+ if (!event) return null;
216
281
  return {
217
- event: match[1],
218
- matcher: match[2]?.trim() ?? ""
282
+ event,
283
+ matcher: parts.slice(1).join(" ")
219
284
  };
220
285
  }
221
286
  async function discoverHooks(hooksDir) {
@@ -228,7 +293,7 @@ async function discoverHooks(hooksDir) {
228
293
  return discovered;
229
294
  }
230
295
  for (const filename of filenames) {
231
- const content = await readFile(join(hooksDir, filename), "utf-8");
296
+ const content = await readFile2(join2(hooksDir, filename), "utf-8");
232
297
  const meta = parseHookMeta(content);
233
298
  if (meta) {
234
299
  discovered.set(filename.replace(/\.sh$/, ""), meta);
@@ -375,45 +440,45 @@ __export(sync_engine_exports, {
375
440
  });
376
441
  import {
377
442
  readdir as readdir2,
378
- readFile as readFile2,
379
- writeFile,
443
+ readFile as readFile3,
444
+ writeFile as writeFile2,
380
445
  unlink,
381
446
  mkdir,
382
447
  rmdir,
383
448
  chmod,
384
449
  stat
385
450
  } from "node:fs/promises";
386
- import { join as join2, dirname } from "node:path";
451
+ import { join as join3, dirname } from "node:path";
387
452
  function getTypeDir(claudeDir, dir) {
388
- if (dir === "commands") return join2(claudeDir, dir, "cbp");
389
- return join2(claudeDir, dir);
453
+ if (dir === "commands") return join3(claudeDir, dir, "cbp");
454
+ return join3(claudeDir, dir);
390
455
  }
391
456
  function getFilePath(claudeDir, typeName, file) {
392
457
  const cfg = typeConfig[typeName];
393
458
  const typeDir = getTypeDir(claudeDir, cfg.dir);
394
459
  if (cfg.subfolder) {
395
- return join2(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
460
+ return join3(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
396
461
  }
397
462
  if (typeName === "command" && file.category) {
398
- return join2(typeDir, file.category, `${file.name}${cfg.ext}`);
463
+ return join3(typeDir, file.category, `${file.name}${cfg.ext}`);
399
464
  }
400
465
  if (typeName === "template") {
401
- return join2(typeDir, file.name);
466
+ return join3(typeDir, file.name);
402
467
  }
403
- return join2(typeDir, `${file.name}${cfg.ext}`);
468
+ return join3(typeDir, `${file.name}${cfg.ext}`);
404
469
  }
405
470
  async function readDirRecursive(dir, base = dir) {
406
471
  const result = /* @__PURE__ */ new Map();
407
472
  try {
408
473
  const entries = await readdir2(dir, { withFileTypes: true });
409
474
  for (const entry of entries) {
410
- const fullPath = join2(dir, entry.name);
475
+ const fullPath = join3(dir, entry.name);
411
476
  if (entry.isDirectory()) {
412
477
  const sub = await readDirRecursive(fullPath, base);
413
478
  for (const [k, v] of sub) result.set(k, v);
414
479
  } else {
415
480
  const relPath = fullPath.slice(base.length + 1);
416
- const fileContent = await readFile2(fullPath, "utf-8");
481
+ const fileContent = await readFile3(fullPath, "utf-8");
417
482
  result.set(relPath, fileContent);
418
483
  }
419
484
  }
@@ -423,7 +488,7 @@ async function readDirRecursive(dir, base = dir) {
423
488
  }
424
489
  async function isGitWorktree(projectPath) {
425
490
  try {
426
- const gitPath = join2(projectPath, ".git");
491
+ const gitPath = join3(projectPath, ".git");
427
492
  const info = await stat(gitPath);
428
493
  return info.isFile();
429
494
  } catch {
@@ -450,7 +515,7 @@ async function executeSyncToLocal(options) {
450
515
  const syncData = syncRes.data;
451
516
  const repoData = repoRes.data;
452
517
  syncData.claude_md = [];
453
- const claudeDir = join2(projectPath, ".claude");
518
+ const claudeDir = join3(projectPath, ".claude");
454
519
  const worktree = await isGitWorktree(projectPath);
455
520
  const byType = {};
456
521
  const totals = { created: 0, updated: 0, deleted: 0, unchanged: 0 };
@@ -486,7 +551,7 @@ async function executeSyncToLocal(options) {
486
551
  remotePathMap.set(relPath, { content: substituted, name: remote.name });
487
552
  }
488
553
  for (const [relPath, { content, name }] of remotePathMap) {
489
- const fullPath = join2(targetDir, relPath);
554
+ const fullPath = join3(targetDir, relPath);
490
555
  const localContent = localFiles.get(relPath);
491
556
  if (localContent === void 0) {
492
557
  const remoteFile = remoteFiles.find((f) => f.name === name);
@@ -498,14 +563,14 @@ async function executeSyncToLocal(options) {
498
563
  });
499
564
  if (!dryRun) {
500
565
  await mkdir(dirname(fullPath), { recursive: true });
501
- await writeFile(fullPath, content, "utf-8");
566
+ await writeFile2(fullPath, content, "utf-8");
502
567
  if (typeName === "hook") await chmod(fullPath, 493);
503
568
  }
504
569
  result.created.push(name);
505
570
  totals.created++;
506
571
  } else if (localContent !== content) {
507
572
  if (!dryRun) {
508
- await writeFile(fullPath, content, "utf-8");
573
+ await writeFile2(fullPath, content, "utf-8");
509
574
  if (typeName === "hook") await chmod(fullPath, 493);
510
575
  }
511
576
  result.updated.push(name);
@@ -517,7 +582,7 @@ async function executeSyncToLocal(options) {
517
582
  }
518
583
  for (const [relPath] of localFiles) {
519
584
  if (!remotePathMap.has(relPath)) {
520
- const fullPath = join2(targetDir, relPath);
585
+ const fullPath = join3(targetDir, relPath);
521
586
  if (!dryRun) {
522
587
  await unlink(fullPath);
523
588
  await removeEmptyParents(fullPath, targetDir);
@@ -532,7 +597,7 @@ async function executeSyncToLocal(options) {
532
597
  {
533
598
  const typeName = "docs_stack";
534
599
  const syncKey = "docs_stack";
535
- const targetDir = join2(projectPath, "docs", "stack");
600
+ const targetDir = join3(projectPath, "docs", "stack");
536
601
  const remoteFiles = syncData[syncKey] ?? [];
537
602
  const result = {
538
603
  created: [],
@@ -546,26 +611,26 @@ async function executeSyncToLocal(options) {
546
611
  const localFiles = await readDirRecursive(targetDir);
547
612
  const remotePathMap = /* @__PURE__ */ new Map();
548
613
  for (const remote of remoteFiles) {
549
- const relPath = remote.category ? join2(remote.category, remote.name) : remote.name;
614
+ const relPath = remote.category ? join3(remote.category, remote.name) : remote.name;
550
615
  const substituted = substituteVariables(remote.content, repoData);
551
616
  remotePathMap.set(relPath, {
552
617
  content: substituted,
553
- name: `${remote.category ?? ""}/${remote.name}`
618
+ name: remote.category ? `${remote.category}/${remote.name}` : remote.name
554
619
  });
555
620
  }
556
621
  for (const [relPath, { content, name }] of remotePathMap) {
557
- const fullPath = join2(targetDir, relPath);
622
+ const fullPath = join3(targetDir, relPath);
558
623
  const localContent = localFiles.get(relPath);
559
624
  if (localContent === void 0) {
560
625
  if (!dryRun) {
561
626
  await mkdir(dirname(fullPath), { recursive: true });
562
- await writeFile(fullPath, content, "utf-8");
627
+ await writeFile2(fullPath, content, "utf-8");
563
628
  }
564
629
  result.created.push(name);
565
630
  totals.created++;
566
631
  } else if (localContent !== content) {
567
632
  if (!dryRun) {
568
- await writeFile(fullPath, content, "utf-8");
633
+ await writeFile2(fullPath, content, "utf-8");
569
634
  }
570
635
  result.updated.push(name);
571
636
  totals.updated++;
@@ -576,7 +641,7 @@ async function executeSyncToLocal(options) {
576
641
  }
577
642
  for (const [relPath] of localFiles) {
578
643
  if (!remotePathMap.has(relPath)) {
579
- const fullPath = join2(targetDir, relPath);
644
+ const fullPath = join3(targetDir, relPath);
580
645
  if (!dryRun) {
581
646
  await unlink(fullPath);
582
647
  await removeEmptyParents(fullPath, targetDir);
@@ -596,8 +661,8 @@ async function executeSyncToLocal(options) {
596
661
  globalSettings = { ...globalSettings, ...parsed };
597
662
  }
598
663
  const specialTypes = {
599
- claude_md: () => join2(projectPath, "CLAUDE.md"),
600
- settings: () => join2(projectPath, ".claude", "settings.json")
664
+ claude_md: () => join3(projectPath, "CLAUDE.md"),
665
+ settings: () => join3(projectPath, ".claude", "settings.json")
601
666
  };
602
667
  for (const [typeName, getPath] of Object.entries(specialTypes)) {
603
668
  const remoteFiles = syncData[typeName] ?? [];
@@ -612,7 +677,7 @@ async function executeSyncToLocal(options) {
612
677
  const remoteContent = substituteVariables(remote.content, repoData);
613
678
  let localContent;
614
679
  try {
615
- localContent = await readFile2(targetPath, "utf-8");
680
+ localContent = await readFile3(targetPath, "utf-8");
616
681
  } catch {
617
682
  }
618
683
  if (typeName === "settings") {
@@ -621,7 +686,7 @@ async function executeSyncToLocal(options) {
621
686
  globalSettings,
622
687
  repoSettings
623
688
  );
624
- const hooksDir = join2(projectPath, ".claude", "hooks");
689
+ const hooksDir = join3(projectPath, ".claude", "hooks");
625
690
  const discovered = await discoverHooks(hooksDir);
626
691
  if (localContent === void 0) {
627
692
  const finalSettings = stripPermissionsAllow(combinedTemplate);
@@ -633,7 +698,7 @@ async function executeSyncToLocal(options) {
633
698
  }
634
699
  if (!dryRun) {
635
700
  await mkdir(dirname(targetPath), { recursive: true });
636
- await writeFile(
701
+ await writeFile2(
637
702
  targetPath,
638
703
  JSON.stringify(finalSettings, null, 2) + "\n",
639
704
  "utf-8"
@@ -654,7 +719,7 @@ async function executeSyncToLocal(options) {
654
719
  const mergedContent = JSON.stringify(merged, null, 2) + "\n";
655
720
  if (localContent !== mergedContent) {
656
721
  if (!dryRun) {
657
- await writeFile(targetPath, mergedContent, "utf-8");
722
+ await writeFile2(targetPath, mergedContent, "utf-8");
658
723
  }
659
724
  result.updated.push(remote.name);
660
725
  totals.updated++;
@@ -667,13 +732,13 @@ async function executeSyncToLocal(options) {
667
732
  if (localContent === void 0) {
668
733
  if (!dryRun) {
669
734
  await mkdir(dirname(targetPath), { recursive: true });
670
- await writeFile(targetPath, remoteContent, "utf-8");
735
+ await writeFile2(targetPath, remoteContent, "utf-8");
671
736
  }
672
737
  result.created.push(remote.name);
673
738
  totals.created++;
674
739
  } else if (localContent !== remoteContent) {
675
740
  if (!dryRun) {
676
- await writeFile(targetPath, remoteContent, "utf-8");
741
+ await writeFile2(targetPath, remoteContent, "utf-8");
677
742
  }
678
743
  result.updated.push(remote.name);
679
744
  totals.updated++;
@@ -774,15 +839,15 @@ __export(setup_exports, {
774
839
  });
775
840
  import { createInterface } from "node:readline/promises";
776
841
  import { stdin, stdout } from "node:process";
777
- import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
842
+ import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
778
843
  import { homedir } from "node:os";
779
- import { join as join3 } from "node:path";
844
+ import { join as join4 } from "node:path";
780
845
  function getConfigPath(scope) {
781
- return scope === "user" ? join3(homedir(), ".claude.json") : join3(process.cwd(), ".mcp.json");
846
+ return scope === "user" ? join4(homedir(), ".claude.json") : join4(process.cwd(), ".mcp.json");
782
847
  }
783
848
  async function readConfig(path) {
784
849
  try {
785
- const raw = await readFile3(path, "utf-8");
850
+ const raw = await readFile4(path, "utf-8");
786
851
  const parsed = JSON.parse(raw);
787
852
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
788
853
  return parsed;
@@ -793,7 +858,7 @@ async function readConfig(path) {
793
858
  }
794
859
  }
795
860
  function buildMcpEntry(apiKey) {
796
- const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
861
+ const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
797
862
  return {
798
863
  url: `${baseUrl}/mcp`,
799
864
  headers: { "x-api-key": apiKey }
@@ -806,7 +871,7 @@ async function writeMcpConfig(scope, apiKey) {
806
871
  config.mcpServers = {};
807
872
  }
808
873
  config.mcpServers.codebyplan = buildMcpEntry(apiKey);
809
- await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
874
+ await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
810
875
  return configPath;
811
876
  }
812
877
  async function verifyMcpConfig(scope, apiKey) {
@@ -835,7 +900,7 @@ async function runSetup() {
835
900
  return;
836
901
  }
837
902
  console.log("\n Validating API key...");
838
- const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
903
+ const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
839
904
  const res = await fetch(`${baseUrl}/api/repos`, {
840
905
  headers: { "x-api-key": apiKey },
841
906
  signal: AbortSignal.timeout(1e4)
@@ -887,7 +952,7 @@ async function runSetup() {
887
952
  `
888
953
  );
889
954
  console.log(
890
- ` { "url": "https://codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
955
+ ` { "url": "https://www.codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
891
956
  `
892
957
  );
893
958
  }
@@ -910,27 +975,28 @@ async function runSetup() {
910
975
  console.log(`
911
976
  Selected: ${selectedRepo.name}
912
977
  `);
913
- let worktreeId;
914
978
  const projectPath = process.cwd();
915
- try {
916
- const worktreesRes = await apiGet(`/worktrees?repo_id=${selectedRepo.id}`);
917
- const match = worktreesRes.data.find(
918
- (wt) => projectPath === wt.path || projectPath.startsWith(wt.path + "/")
919
- );
920
- if (match) worktreeId = match.id;
921
- } catch {
922
- }
923
- 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");
924
985
  const codebyplanConfig = {
925
986
  repo_id: selectedRepo.id
926
987
  };
927
988
  if (worktreeId) codebyplanConfig.worktree_id = worktreeId;
928
- await writeFile2(
989
+ await writeFile3(
929
990
  codebyplanPath,
930
991
  JSON.stringify(codebyplanConfig, null, 2) + "\n",
931
992
  "utf-8"
932
993
  );
933
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
+ }
934
1000
  console.log("\n Running initial sync...\n");
935
1001
  try {
936
1002
  const { executeSyncToLocal: executeSyncToLocal2 } = await Promise.resolve().then(() => (init_sync_engine(), sync_engine_exports));
@@ -965,20 +1031,36 @@ async function runSetup() {
965
1031
  var init_setup = __esm({
966
1032
  "src/cli/setup.ts"() {
967
1033
  "use strict";
968
- init_api();
1034
+ init_resolve_worktree();
969
1035
  }
970
1036
  });
971
1037
 
972
1038
  // src/cli/config.ts
973
- import { readFile as readFile4 } from "node:fs/promises";
974
- import { join as join4 } from "node:path";
1039
+ import { readFile as readFile5 } from "node:fs/promises";
1040
+ import { join as join5, resolve } from "node:path";
1041
+ async function findCodebyplanConfig(startDir, maxDepth = 20) {
1042
+ let cursor = resolve(startDir);
1043
+ for (let depth = 0; depth < maxDepth; depth++) {
1044
+ const configPath = join5(cursor, ".codebyplan.json");
1045
+ try {
1046
+ const raw = await readFile5(configPath, "utf-8");
1047
+ const parsed = JSON.parse(raw);
1048
+ return { path: configPath, contents: parsed };
1049
+ } catch {
1050
+ }
1051
+ const parent = resolve(cursor, "..");
1052
+ if (parent === cursor) return null;
1053
+ cursor = parent;
1054
+ }
1055
+ return null;
1056
+ }
975
1057
  function parseFlags(startIndex) {
976
1058
  const flags = {};
977
1059
  const args = process.argv.slice(startIndex);
978
1060
  for (let i = 0; i < args.length; i++) {
979
- const arg2 = args[i];
980
- if (arg2.startsWith("--") && i + 1 < args.length) {
981
- const key = arg2.slice(2);
1061
+ const arg = args[i];
1062
+ if (arg.startsWith("--") && i + 1 < args.length) {
1063
+ const key = arg.slice(2);
982
1064
  flags[key] = args[++i];
983
1065
  }
984
1066
  }
@@ -992,13 +1074,10 @@ async function resolveConfig(flags) {
992
1074
  let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
993
1075
  let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
994
1076
  if (!repoId || !worktreeId) {
995
- try {
996
- const configPath = join4(projectPath, ".codebyplan.json");
997
- const raw = await readFile4(configPath, "utf-8");
998
- const config = JSON.parse(raw);
999
- if (!repoId) repoId = config.repo_id;
1000
- if (!worktreeId) worktreeId = config.worktree_id;
1001
- } catch {
1077
+ const found = await findCodebyplanConfig(projectPath);
1078
+ if (found) {
1079
+ if (!repoId) repoId = found.contents.repo_id;
1080
+ if (!worktreeId) worktreeId = found.contents.worktree_id;
1002
1081
  }
1003
1082
  }
1004
1083
  if (!repoId) {
@@ -1015,8 +1094,8 @@ var init_config = __esm({
1015
1094
  });
1016
1095
 
1017
1096
  // src/cli/fileMapper.ts
1018
- import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
1019
- import { join as join5, extname } from "node:path";
1097
+ import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
1098
+ import { join as join6, extname } from "node:path";
1020
1099
  function extractScope(content, type) {
1021
1100
  if (type === "hook") {
1022
1101
  const match = content.match(/^#\s*@scope:\s*(\S+)/m);
@@ -1046,29 +1125,29 @@ function compositeKey(type, name, category) {
1046
1125
  }
1047
1126
  async function scanLocalFiles(claudeDir, projectPath) {
1048
1127
  const result = /* @__PURE__ */ new Map();
1049
- await scanCommands(join5(claudeDir, "commands", "cbp"), result);
1128
+ await scanCommands(join6(claudeDir, "commands", "cbp"), result);
1050
1129
  await scanSubfolderType(
1051
- join5(claudeDir, "agents"),
1130
+ join6(claudeDir, "agents"),
1052
1131
  "agent",
1053
1132
  "AGENT.md",
1054
1133
  result
1055
1134
  );
1056
1135
  await scanSubfolderType(
1057
- join5(claudeDir, "skills"),
1136
+ join6(claudeDir, "skills"),
1058
1137
  "skill",
1059
1138
  "SKILL.md",
1060
1139
  result
1061
1140
  );
1062
- await scanFlatType(join5(claudeDir, "rules"), "rule", ".md", result);
1063
- await scanFlatType(join5(claudeDir, "hooks"), "hook", ".sh", result);
1064
- await scanTemplates(join5(claudeDir, "templates"), result);
1141
+ await scanFlatType(join6(claudeDir, "rules"), "rule", ".md", result);
1142
+ await scanFlatType(join6(claudeDir, "hooks"), "hook", ".sh", result);
1143
+ await scanTemplates(join6(claudeDir, "templates"), result);
1065
1144
  await scanCategorizedType(
1066
- join5(claudeDir, "context"),
1145
+ join6(claudeDir, "context"),
1067
1146
  "context",
1068
1147
  ".md",
1069
1148
  result
1070
1149
  );
1071
- await scanDocsRecursive(join5(claudeDir, "docs"), result);
1150
+ await scanDocsRecursive(join6(claudeDir, "docs"), result);
1072
1151
  await scanSettings(claudeDir, projectPath, result);
1073
1152
  return result;
1074
1153
  }
@@ -1086,12 +1165,12 @@ async function scanCommandsRecursive(baseDir, currentDir, result) {
1086
1165
  if (entry.isDirectory()) {
1087
1166
  await scanCommandsRecursive(
1088
1167
  baseDir,
1089
- join5(currentDir, entry.name),
1168
+ join6(currentDir, entry.name),
1090
1169
  result
1091
1170
  );
1092
1171
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
1093
1172
  const name = entry.name.slice(0, -3);
1094
- const content = await readFile5(join5(currentDir, entry.name), "utf-8");
1173
+ const content = await readFile6(join6(currentDir, entry.name), "utf-8");
1095
1174
  const relDir = currentDir.slice(baseDir.length + 1);
1096
1175
  const category = relDir || null;
1097
1176
  const scope = extractScope(content, "command");
@@ -1109,9 +1188,9 @@ async function scanSubfolderType(dir, type, fileName, result) {
1109
1188
  }
1110
1189
  for (const entry of entries) {
1111
1190
  if (entry.isDirectory()) {
1112
- const filePath = join5(dir, entry.name, fileName);
1191
+ const filePath = join6(dir, entry.name, fileName);
1113
1192
  try {
1114
- const content = await readFile5(filePath, "utf-8");
1193
+ const content = await readFile6(filePath, "utf-8");
1115
1194
  const scope = extractScope(content, type);
1116
1195
  const key = compositeKey(type, entry.name, null);
1117
1196
  result.set(key, {
@@ -1136,7 +1215,7 @@ async function scanFlatType(dir, type, ext, result) {
1136
1215
  for (const entry of entries) {
1137
1216
  if (entry.isFile() && entry.name.endsWith(ext)) {
1138
1217
  const name = entry.name.slice(0, -ext.length);
1139
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1218
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1140
1219
  const scope = extractScope(content, type);
1141
1220
  const key = compositeKey(type, name, null);
1142
1221
  result.set(key, { type, name, category: null, content, scope });
@@ -1155,7 +1234,7 @@ async function scanCategorizedType(dir, type, ext, result) {
1155
1234
  const category = entry.name;
1156
1235
  let subEntries;
1157
1236
  try {
1158
- subEntries = await readdir3(join5(dir, category), {
1237
+ subEntries = await readdir3(join6(dir, category), {
1159
1238
  withFileTypes: true
1160
1239
  });
1161
1240
  } catch {
@@ -1164,8 +1243,8 @@ async function scanCategorizedType(dir, type, ext, result) {
1164
1243
  for (const sub of subEntries) {
1165
1244
  if (sub.isFile() && sub.name.endsWith(ext)) {
1166
1245
  const name = sub.name.slice(0, -ext.length);
1167
- const content = await readFile5(
1168
- join5(dir, category, sub.name),
1246
+ const content = await readFile6(
1247
+ join6(dir, category, sub.name),
1169
1248
  "utf-8"
1170
1249
  );
1171
1250
  const scope = extractScope(content, type);
@@ -1175,7 +1254,7 @@ async function scanCategorizedType(dir, type, ext, result) {
1175
1254
  }
1176
1255
  } else if (entry.isFile() && entry.name.endsWith(ext)) {
1177
1256
  const name = entry.name.slice(0, -ext.length);
1178
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1257
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1179
1258
  const scope = extractScope(content, type);
1180
1259
  const key = compositeKey(type, name, null);
1181
1260
  result.set(key, { type, name, category: null, content, scope });
@@ -1194,10 +1273,10 @@ async function scanDocsDir(baseDir, currentDir, result) {
1194
1273
  }
1195
1274
  for (const entry of entries) {
1196
1275
  if (entry.isDirectory()) {
1197
- await scanDocsDir(baseDir, join5(currentDir, entry.name), result);
1276
+ await scanDocsDir(baseDir, join6(currentDir, entry.name), result);
1198
1277
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
1199
1278
  const name = entry.name.slice(0, -3);
1200
- const content = await readFile5(join5(currentDir, entry.name), "utf-8");
1279
+ const content = await readFile6(join6(currentDir, entry.name), "utf-8");
1201
1280
  const scope = extractScope(content, "docs");
1202
1281
  const relDir = currentDir.slice(baseDir.length + 1);
1203
1282
  const category = relDir || null;
@@ -1215,7 +1294,7 @@ async function scanTemplates(dir, result) {
1215
1294
  }
1216
1295
  for (const entry of entries) {
1217
1296
  if (entry.isFile() && extname(entry.name)) {
1218
- const content = await readFile5(join5(dir, entry.name), "utf-8");
1297
+ const content = await readFile6(join6(dir, entry.name), "utf-8");
1219
1298
  const scope = extractScope(content, "template");
1220
1299
  const key = compositeKey("template", entry.name, null);
1221
1300
  result.set(key, {
@@ -1229,10 +1308,10 @@ async function scanTemplates(dir, result) {
1229
1308
  }
1230
1309
  }
1231
1310
  async function scanSettings(claudeDir, projectPath, result) {
1232
- const settingsPath = join5(claudeDir, "settings.json");
1311
+ const settingsPath = join6(claudeDir, "settings.json");
1233
1312
  let raw;
1234
1313
  try {
1235
- raw = await readFile5(settingsPath, "utf-8");
1314
+ raw = await readFile6(settingsPath, "utf-8");
1236
1315
  } catch {
1237
1316
  return;
1238
1317
  }
@@ -1244,7 +1323,7 @@ async function scanSettings(claudeDir, projectPath, result) {
1244
1323
  }
1245
1324
  parsed = stripPermissionsAllow(parsed);
1246
1325
  if (parsed.hooks && typeof parsed.hooks === "object") {
1247
- const hooksDir = projectPath ? join5(projectPath, ".claude", "hooks") : join5(claudeDir, "hooks");
1326
+ const hooksDir = projectPath ? join6(projectPath, ".claude", "hooks") : join6(claudeDir, "hooks");
1248
1327
  const discovered = await discoverHooks(hooksDir);
1249
1328
  if (discovered.size > 0) {
1250
1329
  parsed.hooks = stripDiscoveredHooks(
@@ -1576,8 +1655,8 @@ var init_confirm = __esm({
1576
1655
  });
1577
1656
 
1578
1657
  // src/lib/tech-detect.ts
1579
- import { readFile as readFile6, access, readdir as readdir4 } from "node:fs/promises";
1580
- import { join as join6, relative } from "node:path";
1658
+ import { readFile as readFile7, access, readdir as readdir4 } from "node:fs/promises";
1659
+ import { join as join7, relative } from "node:path";
1581
1660
  async function fileExists(filePath) {
1582
1661
  try {
1583
1662
  await access(filePath);
@@ -1590,8 +1669,8 @@ async function discoverMonorepoApps(projectPath) {
1590
1669
  const apps = [];
1591
1670
  const patterns = [];
1592
1671
  try {
1593
- const raw = await readFile6(
1594
- join6(projectPath, "pnpm-workspace.yaml"),
1672
+ const raw = await readFile7(
1673
+ join7(projectPath, "pnpm-workspace.yaml"),
1595
1674
  "utf-8"
1596
1675
  );
1597
1676
  const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
@@ -1605,7 +1684,7 @@ async function discoverMonorepoApps(projectPath) {
1605
1684
  }
1606
1685
  if (patterns.length === 0) {
1607
1686
  try {
1608
- const raw = await readFile6(join6(projectPath, "package.json"), "utf-8");
1687
+ const raw = await readFile7(join7(projectPath, "package.json"), "utf-8");
1609
1688
  const pkg = JSON.parse(raw);
1610
1689
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
1611
1690
  if (ws) patterns.push(...ws);
@@ -1615,14 +1694,14 @@ async function discoverMonorepoApps(projectPath) {
1615
1694
  for (const pattern of patterns) {
1616
1695
  if (pattern.endsWith("/*")) {
1617
1696
  const dir = pattern.slice(0, -2);
1618
- const absDir = join6(projectPath, dir);
1697
+ const absDir = join7(projectPath, dir);
1619
1698
  try {
1620
1699
  const entries = await readdir4(absDir, { withFileTypes: true });
1621
1700
  for (const entry of entries) {
1622
1701
  if (entry.isDirectory()) {
1623
- const relPath = join6(dir, entry.name);
1624
- const absPath = join6(absDir, entry.name);
1625
- if (await fileExists(join6(absPath, "package.json"))) {
1702
+ const relPath = join7(dir, entry.name);
1703
+ const absPath = join7(absDir, entry.name);
1704
+ if (await fileExists(join7(absPath, "package.json"))) {
1626
1705
  apps.push({ name: entry.name, path: relPath, absPath });
1627
1706
  }
1628
1707
  }
@@ -1633,12 +1712,77 @@ async function discoverMonorepoApps(projectPath) {
1633
1712
  }
1634
1713
  return apps;
1635
1714
  }
1715
+ async function hasJsxFile(dir, depth = 0) {
1716
+ if (depth > 6) return false;
1717
+ try {
1718
+ const entries = await readdir4(dir, { withFileTypes: true });
1719
+ for (const entry of entries) {
1720
+ const name = entry.name;
1721
+ if (entry.isDirectory()) {
1722
+ if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
1723
+ if (await hasJsxFile(join7(dir, name), depth + 1)) return true;
1724
+ } else if (entry.isFile()) {
1725
+ if (JSX_TEST_PATTERN.test(name)) continue;
1726
+ if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
1727
+ }
1728
+ }
1729
+ } catch (err) {
1730
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1731
+ return false;
1732
+ }
1733
+ console.error(
1734
+ `detectCapabilities: readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`
1735
+ );
1736
+ }
1737
+ return false;
1738
+ }
1739
+ async function detectCapabilities(dirPath, pkgJson) {
1740
+ const caps = /* @__PURE__ */ new Set();
1741
+ for (const sub of JSX_SCAN_DIRS) {
1742
+ if (await hasJsxFile(join7(dirPath, sub))) {
1743
+ caps.add("jsx");
1744
+ break;
1745
+ }
1746
+ }
1747
+ if (pkgJson) {
1748
+ const allDeps = {
1749
+ ...pkgJson.dependencies ?? {},
1750
+ ...pkgJson.devDependencies ?? {}
1751
+ };
1752
+ for (const dep of Object.keys(allDeps)) {
1753
+ if (SERVER_FRAMEWORK_DEPS.has(dep)) {
1754
+ caps.add("node-server");
1755
+ break;
1756
+ }
1757
+ }
1758
+ if (!caps.has("node-server") && typeof pkgJson.main === "string") {
1759
+ if (SERVER_MAIN_ENTRIES.has(pkgJson.main.trim())) {
1760
+ caps.add("node-server");
1761
+ }
1762
+ }
1763
+ }
1764
+ if (!caps.has("node-server") && await fileExists(join7(dirPath, "src", "main.ts"))) {
1765
+ caps.add("node-server");
1766
+ }
1767
+ if (pkgJson && pkgJson.bin) {
1768
+ if (typeof pkgJson.bin === "string" && pkgJson.bin.trim().length > 0) {
1769
+ caps.add("cli-bin");
1770
+ } else if (typeof pkgJson.bin === "object" && pkgJson.bin !== null && Object.keys(pkgJson.bin).length > 0) {
1771
+ caps.add("cli-bin");
1772
+ }
1773
+ }
1774
+ return caps;
1775
+ }
1636
1776
  async function detectFromDirectory(dirPath) {
1637
1777
  const seen = /* @__PURE__ */ new Map();
1778
+ let pkgJson = null;
1638
1779
  try {
1639
- const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
1640
- const pkg = JSON.parse(raw);
1641
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1780
+ const raw = await readFile7(join7(dirPath, "package.json"), "utf-8");
1781
+ pkgJson = JSON.parse(raw);
1782
+ const allDeps = {
1783
+ ...pkgJson.dependencies ?? {},
1784
+ ...pkgJson.devDependencies ?? {}
1785
+ };
1642
1786
  for (const depName of Object.keys(allDeps)) {
1643
1787
  const rule = PACKAGE_MAP[depName];
1644
1788
  if (rule) {
@@ -1665,15 +1809,44 @@ async function detectFromDirectory(dirPath) {
1665
1809
  }
1666
1810
  for (const { file, rule } of CONFIG_FILE_MAP) {
1667
1811
  const key = rule.name.toLowerCase();
1668
- if (!seen.has(key) && await fileExists(join6(dirPath, file))) {
1812
+ if (!seen.has(key) && await fileExists(join7(dirPath, file))) {
1669
1813
  seen.set(key, { name: rule.name, category: rule.category });
1670
1814
  }
1671
1815
  }
1672
- return Array.from(seen.values()).sort((a, b) => {
1673
- const catCmp = a.category.localeCompare(b.category);
1674
- if (catCmp !== 0) return catCmp;
1675
- return a.name.localeCompare(b.name);
1816
+ const capabilities = await detectCapabilities(dirPath, pkgJson);
1817
+ const capsArray = Array.from(capabilities).sort();
1818
+ const entries = Array.from(seen.values()).map((entry) => {
1819
+ const isBearer = CAPABILITY_BEARER_NAMES.has(entry.name.toLowerCase());
1820
+ return {
1821
+ ...entry,
1822
+ capabilities: isBearer ? capsArray : []
1823
+ };
1676
1824
  });
1825
+ if (capsArray.length > 0) {
1826
+ const hasBearerWithCaps = entries.some(
1827
+ (e) => CAPABILITY_BEARER_NAMES.has(e.name.toLowerCase()) && (e.capabilities?.some((c) => c.length > 0) ?? false)
1828
+ );
1829
+ if (!hasBearerWithCaps) {
1830
+ entries.push({
1831
+ name: SYNTHETIC_CARRIER_NAME,
1832
+ category: "tool",
1833
+ capabilities: capsArray
1834
+ });
1835
+ }
1836
+ }
1837
+ return entries.sort(compareByCategoryThenName);
1838
+ }
1839
+ function collectCapabilities(entries) {
1840
+ const set = /* @__PURE__ */ new Set();
1841
+ for (const entry of entries) {
1842
+ if (!entry.capabilities) continue;
1843
+ for (const cap of entry.capabilities) {
1844
+ if (typeof cap === "string" && cap.length > 0) {
1845
+ set.add(cap.toLowerCase());
1846
+ }
1847
+ }
1848
+ }
1849
+ return Array.from(set).sort();
1677
1850
  }
1678
1851
  async function detectTechStack(projectPath) {
1679
1852
  const repo = await detectFromDirectory(projectPath);
@@ -1692,40 +1865,80 @@ async function detectTechStack(projectPath) {
1692
1865
  for (const app of apps) {
1693
1866
  for (const entry of app.stack) {
1694
1867
  const key = entry.name.toLowerCase();
1695
- if (!flatMap.has(key)) {
1868
+ const existing = flatMap.get(key);
1869
+ if (!existing) {
1696
1870
  flatMap.set(key, entry);
1871
+ } else if (entry.capabilities?.length) {
1872
+ const merged = Array.from(
1873
+ /* @__PURE__ */ new Set([...existing.capabilities ?? [], ...entry.capabilities])
1874
+ ).sort();
1875
+ flatMap.set(key, { ...existing, capabilities: merged });
1697
1876
  }
1698
1877
  }
1699
1878
  }
1700
- const flat = Array.from(flatMap.values()).sort((a, b) => {
1701
- const catCmp = a.category.localeCompare(b.category);
1702
- if (catCmp !== 0) return catCmp;
1703
- return a.name.localeCompare(b.name);
1704
- });
1705
- return { repo, apps, flat };
1879
+ const repoCleaned = stripSyntheticIfCovered(repo).sort(
1880
+ compareByCategoryThenName
1881
+ );
1882
+ const appsCleaned = apps.map((a) => ({
1883
+ ...a,
1884
+ stack: stripSyntheticIfCovered(a.stack).sort(compareByCategoryThenName)
1885
+ }));
1886
+ const flat = stripSyntheticIfCovered(Array.from(flatMap.values())).sort(
1887
+ compareByCategoryThenName
1888
+ );
1889
+ return { repo: repoCleaned, apps: appsCleaned, flat };
1890
+ }
1891
+ function stripSyntheticIfCovered(entries) {
1892
+ const synth = entries.find((e) => e.name === SYNTHETIC_CARRIER_NAME);
1893
+ if (!synth?.capabilities?.length) return entries;
1894
+ const realBearerCaps = /* @__PURE__ */ new Set();
1895
+ for (const e of entries) {
1896
+ if (e.name === SYNTHETIC_CARRIER_NAME) continue;
1897
+ for (const c of e.capabilities ?? []) realBearerCaps.add(c);
1898
+ }
1899
+ if (synth.capabilities.every((c) => realBearerCaps.has(c))) {
1900
+ return entries.filter((e) => e.name !== SYNTHETIC_CARRIER_NAME);
1901
+ }
1902
+ return entries;
1706
1903
  }
1707
1904
  function mergeTechStack(remote, detected) {
1708
- const remoteResult = Array.isArray(remote) ? { repo: remote, apps: [], flat: remote } : remote;
1905
+ const stripCarrier = (e) => e.name !== SYNTHETIC_CARRIER_NAME;
1906
+ const cleanDetected = {
1907
+ repo: detected.repo.filter(stripCarrier),
1908
+ apps: detected.apps.map((a) => ({
1909
+ ...a,
1910
+ stack: a.stack.filter(stripCarrier)
1911
+ })),
1912
+ flat: detected.flat.filter(stripCarrier)
1913
+ };
1914
+ const remoteResult = Array.isArray(remote) ? {
1915
+ repo: remote.filter(stripCarrier),
1916
+ apps: [],
1917
+ flat: remote.filter(stripCarrier)
1918
+ } : {
1919
+ repo: remote.repo.filter(stripCarrier),
1920
+ apps: remote.apps.map((a) => ({
1921
+ ...a,
1922
+ stack: a.stack.filter(stripCarrier)
1923
+ })),
1924
+ flat: remote.flat.filter(stripCarrier)
1925
+ };
1709
1926
  const seen = /* @__PURE__ */ new Map();
1710
1927
  for (const entry of remoteResult.flat) {
1711
1928
  seen.set(entry.name.toLowerCase(), entry);
1712
1929
  }
1713
1930
  const added = [];
1714
- for (const entry of detected.flat) {
1931
+ for (const entry of cleanDetected.flat) {
1715
1932
  const key = entry.name.toLowerCase();
1716
1933
  if (!seen.has(key)) {
1717
1934
  seen.set(key, entry);
1718
1935
  added.push(entry);
1719
1936
  }
1720
1937
  }
1721
- const flat = Array.from(seen.values()).sort((a, b) => {
1722
- const catCmp = a.category.localeCompare(b.category);
1723
- if (catCmp !== 0) return catCmp;
1724
- return a.name.localeCompare(b.name);
1725
- });
1938
+ const flat = Array.from(seen.values()).sort(compareByCategoryThenName);
1726
1939
  const merged = {
1727
- repo: detected.repo,
1728
- apps: detected.apps,
1940
+ repo: cleanDetected.repo,
1941
+ apps: cleanDetected.apps,
1729
1942
  flat
1730
1943
  };
1731
1944
  return { merged, added };
@@ -1774,7 +1987,7 @@ function categorizeDependency(depName) {
1774
1987
  async function findPackageJsonFiles(dir, projectPath, depth = 0) {
1775
1988
  if (depth > 4) return [];
1776
1989
  const results = [];
1777
- const pkgPath = join6(dir, "package.json");
1990
+ const pkgPath = join7(dir, "package.json");
1778
1991
  if (await fileExists(pkgPath)) {
1779
1992
  results.push(pkgPath);
1780
1993
  }
@@ -1783,7 +1996,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
1783
1996
  for (const entry of entries) {
1784
1997
  if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
1785
1998
  const subResults = await findPackageJsonFiles(
1786
- join6(dir, entry.name),
1999
+ join7(dir, entry.name),
1787
2000
  projectPath,
1788
2001
  depth + 1
1789
2002
  );
@@ -1798,7 +2011,7 @@ async function scanAllDependencies(projectPath) {
1798
2011
  const dependencies = [];
1799
2012
  for (const pkgPath of packageJsonPaths) {
1800
2013
  try {
1801
- const raw = await readFile6(pkgPath, "utf-8");
2014
+ const raw = await readFile7(pkgPath, "utf-8");
1802
2015
  const pkg = JSON.parse(raw);
1803
2016
  const sourcePath = relative(projectPath, pkgPath);
1804
2017
  const depSections = [
@@ -1825,7 +2038,7 @@ async function scanAllDependencies(projectPath) {
1825
2038
  }
1826
2039
  return { dependencies };
1827
2040
  }
1828
- var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SKIP_DIRS;
2041
+ var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SYNTHETIC_CARRIER_NAME, CAPABILITY_BEARER_NAMES, JSX_TEST_PATTERN, JSX_SKIP_DIRS, JSX_SCAN_DIRS, SERVER_MAIN_ENTRIES, SERVER_FRAMEWORK_DEPS, compareByCategoryThenName, SKIP_DIRS;
1829
2042
  var init_tech_detect = __esm({
1830
2043
  "src/lib/tech-detect.ts"() {
1831
2044
  "use strict";
@@ -1841,6 +2054,7 @@ var init_tech_detect = __esm({
1841
2054
  svelte: { name: "Svelte", category: "framework" },
1842
2055
  astro: { name: "Astro", category: "framework" },
1843
2056
  "@angular/core": { name: "Angular", category: "framework" },
2057
+ "@nestjs/core": { name: "NestJS", category: "framework" },
1844
2058
  // Libraries (UI)
1845
2059
  react: { name: "React", category: "framework" },
1846
2060
  vue: { name: "Vue", category: "framework" },
@@ -1955,6 +2169,47 @@ var init_tech_detect = __esm({
1955
2169
  { file: "nx.json", rule: { name: "Nx", category: "build" } },
1956
2170
  { file: "lerna.json", rule: { name: "Lerna", category: "build" } }
1957
2171
  ];
2172
+ SYNTHETIC_CARRIER_NAME = "__capabilities__";
2173
+ CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
2174
+ "react",
2175
+ "next.js",
2176
+ "vue",
2177
+ "svelte",
2178
+ "solid",
2179
+ "preact",
2180
+ "remix",
2181
+ "astro",
2182
+ "angular",
2183
+ "nestjs",
2184
+ "nuxt",
2185
+ "gatsby",
2186
+ "express",
2187
+ "fastify",
2188
+ "hono",
2189
+ "react native",
2190
+ "expo"
2191
+ ]);
2192
+ JSX_TEST_PATTERN = /\.(test|spec)\.(tsx|jsx)$/;
2193
+ JSX_SKIP_DIRS = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
2194
+ JSX_SCAN_DIRS = ["src", "app", "pages"];
2195
+ SERVER_MAIN_ENTRIES = /* @__PURE__ */ new Set([
2196
+ "dist/main.js",
2197
+ "dist/server.js",
2198
+ "dist/index.js",
2199
+ "main.js",
2200
+ "server.js"
2201
+ ]);
2202
+ SERVER_FRAMEWORK_DEPS = /* @__PURE__ */ new Set([
2203
+ "@nestjs/core",
2204
+ "express",
2205
+ "fastify",
2206
+ "hono"
2207
+ ]);
2208
+ compareByCategoryThenName = (a, b) => {
2209
+ const catCmp = a.category.localeCompare(b.category);
2210
+ if (catCmp !== 0) return catCmp;
2211
+ return a.name.localeCompare(b.name);
2212
+ };
1958
2213
  SKIP_DIRS = /* @__PURE__ */ new Set([
1959
2214
  "node_modules",
1960
2215
  ".next",
@@ -2005,14 +2260,14 @@ var init_server_detect = __esm({
2005
2260
  });
2006
2261
 
2007
2262
  // src/lib/port-verify.ts
2008
- import { readFile as readFile7 } from "node:fs/promises";
2263
+ import { readFile as readFile8 } from "node:fs/promises";
2009
2264
  async function verifyPorts(projectPath, portAllocations) {
2010
2265
  const mismatches = [];
2011
2266
  const allocatedPorts = new Set(portAllocations.map((a) => a.port));
2012
2267
  const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
2013
2268
  for (const pkgPath of packageJsonPaths) {
2014
2269
  try {
2015
- const raw = await readFile7(pkgPath, "utf-8");
2270
+ const raw = await readFile8(pkgPath, "utf-8");
2016
2271
  const pkg = JSON.parse(raw);
2017
2272
  const scriptPort = detectPortFromScripts(pkg);
2018
2273
  if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
@@ -2032,30 +2287,65 @@ async function verifyPorts(projectPath, portAllocations) {
2032
2287
  }
2033
2288
  return mismatches;
2034
2289
  }
2290
+ function isDevServerScript(pkg) {
2291
+ const scripts = pkg.scripts;
2292
+ const raw = scripts?.dev;
2293
+ if (!raw || typeof raw !== "string") return false;
2294
+ const script = raw.trim().toLowerCase();
2295
+ if (!script) return false;
2296
+ for (const pattern of DEV_SERVER_BIN_PATTERNS) {
2297
+ if (pattern.test(script)) return true;
2298
+ }
2299
+ const tokens = script.split(/\s+/);
2300
+ for (const token of tokens) {
2301
+ if (token === "--port" || token === "-p") return true;
2302
+ if (token.startsWith("--port=")) return true;
2303
+ }
2304
+ return false;
2305
+ }
2306
+ function labelMatchesAppName(label, appName) {
2307
+ if (!label || !appName) return false;
2308
+ const normalize = (s) => s.toLowerCase().replace(/-/g, " ").replace(/[()]/g, " ").replace(/\s+/g, " ").trim();
2309
+ const labelTokens = normalize(label).split(" ").filter(Boolean);
2310
+ const appToken = normalize(appName);
2311
+ if (!appToken) return false;
2312
+ const appTokens = appToken.split(" ").filter(Boolean);
2313
+ if (appTokens.length === 1) {
2314
+ return labelTokens.includes(appTokens[0]);
2315
+ }
2316
+ for (let i = 0; i <= labelTokens.length - appTokens.length; i++) {
2317
+ if (appTokens.every((t, j) => labelTokens[i + j] === t)) return true;
2318
+ }
2319
+ return false;
2320
+ }
2035
2321
  async function findUnallocatedApps(projectPath, portAllocations) {
2036
2322
  const apps = await discoverMonorepoApps(projectPath);
2037
2323
  if (apps.length === 0) {
2038
2324
  return [];
2039
2325
  }
2040
- const allocatedLabels = new Set(portAllocations.map((a) => a.label));
2041
2326
  const unallocated = [];
2042
2327
  for (const app of apps) {
2043
- if (allocatedLabels.has(app.name)) continue;
2328
+ if (portAllocations.some((a) => labelMatchesAppName(a.label ?? "", app.name))) {
2329
+ continue;
2330
+ }
2331
+ let pkg;
2044
2332
  try {
2045
- const raw = await readFile7(`${app.absPath}/package.json`, "utf-8");
2046
- const pkg = JSON.parse(raw);
2047
- const framework = detectFramework(pkg);
2048
- const detectedPort = detectPortFromScripts(pkg);
2049
- const command = `pnpm --filter ${app.name} dev`;
2050
- unallocated.push({
2051
- name: app.name,
2052
- path: app.path,
2053
- framework,
2054
- detectedPort,
2055
- command
2056
- });
2333
+ const raw = await readFile8(`${app.absPath}/package.json`, "utf-8");
2334
+ pkg = JSON.parse(raw);
2057
2335
  } catch {
2336
+ continue;
2058
2337
  }
2338
+ if (!isDevServerScript(pkg)) continue;
2339
+ const framework = detectFramework(pkg);
2340
+ const detectedPort = detectPortFromScripts(pkg);
2341
+ const command = `pnpm --filter ${app.name} dev`;
2342
+ unallocated.push({
2343
+ name: app.name,
2344
+ path: app.path,
2345
+ framework,
2346
+ detectedPort,
2347
+ command
2348
+ });
2059
2349
  }
2060
2350
  return unallocated;
2061
2351
  }
@@ -2066,16 +2356,46 @@ function getAppLabel(relativePath) {
2066
2356
  }
2067
2357
  return "root";
2068
2358
  }
2359
+ var DEV_SERVER_BIN_PATTERNS;
2069
2360
  var init_port_verify = __esm({
2070
2361
  "src/lib/port-verify.ts"() {
2071
2362
  "use strict";
2072
2363
  init_tech_detect();
2073
2364
  init_server_detect();
2365
+ DEV_SERVER_BIN_PATTERNS = [
2366
+ /\bnext\s+dev\b/,
2367
+ /\bnest\s+start\b/,
2368
+ /\bvite\s+(?:dev|serve)\b/,
2369
+ /\bvite\s+preview\b/,
2370
+ /\bnuxt\s+dev\b/,
2371
+ /\b(?:svelte-kit|sveltekit)\s+dev\b/,
2372
+ /\bexpo\s+start\b/
2373
+ ];
2074
2374
  }
2075
2375
  });
2076
2376
 
2077
2377
  // src/lib/eslint-generator.ts
2078
2378
  import { createHash } from "node:crypto";
2379
+ function importedIdentifiers(importLines) {
2380
+ const names = /* @__PURE__ */ new Set();
2381
+ for (const line of importLines) {
2382
+ let m = line.match(/^import\s+([A-Za-z_$][\w$]*)\s+from/);
2383
+ if (m) names.add(m[1]);
2384
+ m = line.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from/);
2385
+ if (m) names.add(m[1]);
2386
+ m = line.match(/^import\s*\{([^}]*)\}\s*from/);
2387
+ if (m) {
2388
+ for (const entry of m[1].split(",")) {
2389
+ const parts = entry.trim().split(/\s+as\s+/);
2390
+ const n = (parts[1] ?? parts[0]).trim();
2391
+ if (n) names.add(n);
2392
+ }
2393
+ }
2394
+ m = line.match(/^const\s+([A-Za-z_$][\w$]*)\s*=\s*require/);
2395
+ if (m) names.add(m[1]);
2396
+ }
2397
+ return names;
2398
+ }
2079
2399
  function parseFragment(fragment) {
2080
2400
  if (!fragment) return { imports: [], configComments: [] };
2081
2401
  const lines = fragment.split("\n");
@@ -2125,7 +2445,7 @@ function collectDependencies(presets) {
2125
2445
  function hashConfig(content) {
2126
2446
  return createHash("sha256").update(content).digest("hex");
2127
2447
  }
2128
- function buildRules(presets, userOverrides) {
2448
+ function buildRules(presets) {
2129
2449
  const merged = {};
2130
2450
  for (const preset of presets) {
2131
2451
  const rules = preset.rules;
@@ -2133,11 +2453,30 @@ function buildRules(presets, userOverrides) {
2133
2453
  Object.assign(merged, rules);
2134
2454
  }
2135
2455
  }
2136
- if (userOverrides) {
2137
- Object.assign(merged, userOverrides);
2138
- }
2139
2456
  return merged;
2140
2457
  }
2458
+ function splitRulesByPlugin(rules, hasNextJs) {
2459
+ const reactHooks = {};
2460
+ const reactOrA11y = {};
2461
+ const importPlugin = {};
2462
+ const generic = {};
2463
+ for (const [key, value] of Object.entries(rules)) {
2464
+ if (key.startsWith("react-hooks/") || key.startsWith("react-compiler/")) {
2465
+ reactHooks[key] = value;
2466
+ } else if (key.startsWith("react/") || key.startsWith("jsx-a11y/")) {
2467
+ reactOrA11y[key] = value;
2468
+ } else if (key.startsWith("import/")) {
2469
+ if (hasNextJs) {
2470
+ importPlugin[key] = value;
2471
+ } else {
2472
+ generic[key] = value;
2473
+ }
2474
+ } else {
2475
+ generic[key] = value;
2476
+ }
2477
+ }
2478
+ return { reactHooks, reactOrA11y, importPlugin, generic };
2479
+ }
2141
2480
  function formatRules(rules, indent) {
2142
2481
  const entries = Object.entries(rules);
2143
2482
  if (entries.length === 0) return "{}";
@@ -2151,6 +2490,7 @@ ${indent}}`;
2151
2490
  }
2152
2491
  function generateEslintConfig(input) {
2153
2492
  const { presets, ruleOverrides, tsconfigRootDir, ignorePatterns } = input;
2493
+ const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
2154
2494
  const allImports = [];
2155
2495
  const allConfigComments = [];
2156
2496
  for (const preset of presets) {
@@ -2163,7 +2503,14 @@ function generateEslintConfig(input) {
2163
2503
  const dedupedImports = deduplicateImports(allImports);
2164
2504
  const tsCheck = dedupedImports.find((i) => i === "// @ts-check");
2165
2505
  const importLines = dedupedImports.filter((i) => i !== "// @ts-check");
2166
- const rules = buildRules(presets, ruleOverrides);
2506
+ const presetRules = buildRules(presets);
2507
+ const userOverrides = ruleOverrides ?? {};
2508
+ const presetRulesClean = Object.fromEntries(
2509
+ Object.entries(presetRules).filter(
2510
+ ([k]) => !Object.hasOwn(userOverrides, k)
2511
+ )
2512
+ );
2513
+ const splitRules = splitRulesByPlugin(presetRulesClean, hasNextJs);
2167
2514
  const defaultIgnores = [
2168
2515
  "eslint.config.mjs",
2169
2516
  "node_modules/**",
@@ -2174,7 +2521,24 @@ function generateEslintConfig(input) {
2174
2521
  const ignores = [.../* @__PURE__ */ new Set([...defaultIgnores, ...ignorePatterns ?? []])];
2175
2522
  const rootDir = tsconfigRootDir ?? "import.meta.dirname";
2176
2523
  const rootDirValue = rootDir === "import.meta.dirname" ? "import.meta.dirname" : `"${rootDir}"`;
2177
- const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
2524
+ const KNOWN_SYSTEM_PRESETS = [
2525
+ "base",
2526
+ "react",
2527
+ "nextjs",
2528
+ "node",
2529
+ "cli",
2530
+ "testing",
2531
+ "testing-react",
2532
+ "testing-e2e"
2533
+ ];
2534
+ for (const preset of presets) {
2535
+ if (preset.is_system && !KNOWN_SYSTEM_PRESETS.includes(preset.name) && !warnedPresetNames.has(preset.name)) {
2536
+ console.warn(
2537
+ `Unknown system preset: ${preset.name} \u2014 skipping structural emission`
2538
+ );
2539
+ warnedPresetNames.add(preset.name);
2540
+ }
2541
+ }
2178
2542
  const hasReact = presets.some((p) => p.is_system && p.name === "react");
2179
2543
  const hasNode = presets.some((p) => p.is_system && p.name === "node");
2180
2544
  const hasTesting = presets.some((p) => p.is_system && p.name === "testing");
@@ -2204,8 +2568,9 @@ function generateEslintConfig(input) {
2204
2568
  if ((hasNode || hasReact || hasNextJs) && !hasGlobalsImport) {
2205
2569
  sections.push('import globals from "globals";');
2206
2570
  }
2571
+ sections.push('import { defineConfig } from "eslint/config";');
2207
2572
  sections.push("");
2208
- sections.push("export default [");
2573
+ sections.push("export default defineConfig([");
2209
2574
  sections.push(` { ignores: ${JSON.stringify(ignores)} },`);
2210
2575
  sections.push("");
2211
2576
  const hasBase = presets.some((p) => p.is_system && p.name === "base");
@@ -2226,11 +2591,43 @@ function generateEslintConfig(input) {
2226
2591
  sections.push("");
2227
2592
  }
2228
2593
  if (hasNextJs) {
2594
+ const bindings = importedIdentifiers(importLines);
2595
+ const nextFusedRules = {
2596
+ ...splitRules.reactHooks,
2597
+ ...splitRules.importPlugin
2598
+ };
2229
2599
  sections.push(" // Next.js: Core Web Vitals + TypeScript");
2230
2600
  sections.push(" ...nextCoreWebVitals,");
2231
2601
  sections.push(" ...nextTypescript,");
2232
- sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
2233
- sections.push(' { plugins: { "react-compiler": reactCompiler } },');
2602
+ if (bindings.has("jsxA11y")) {
2603
+ sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
2604
+ } else {
2605
+ console.warn(
2606
+ "eslint-generator: skipping `jsxA11y.flatConfigs.strict.rules` emission \u2014 `jsxA11y` binding missing from nextjs preset config_fragment. See CHK-087 TASK-4/TASK-5."
2607
+ );
2608
+ }
2609
+ if (Object.keys(nextFusedRules).length > 0) {
2610
+ sections.push(" {");
2611
+ sections.push(' plugins: { "react-compiler": reactCompiler },');
2612
+ sections.push(` rules: ${formatRules(nextFusedRules, " ")},`);
2613
+ sections.push(" },");
2614
+ } else {
2615
+ sections.push(' { plugins: { "react-compiler": reactCompiler } },');
2616
+ }
2617
+ if (Object.keys(splitRules.reactOrA11y).length > 0) {
2618
+ if (bindings.has("react") && bindings.has("jsxA11y")) {
2619
+ sections.push(" {");
2620
+ sections.push(' plugins: { react, "jsx-a11y": jsxA11y },');
2621
+ sections.push(
2622
+ ` rules: ${formatRules(splitRules.reactOrA11y, " ")},`
2623
+ );
2624
+ sections.push(" },");
2625
+ } else {
2626
+ console.warn(
2627
+ "eslint-generator: skipping defensive reactOrA11y block \u2014 requires both `react` and `jsxA11y` bindings from nextjs preset config_fragment. See CHK-087 TASK-4/TASK-5."
2628
+ );
2629
+ }
2630
+ }
2234
2631
  sections.push("");
2235
2632
  }
2236
2633
  if (hasReact && !hasNextJs) {
@@ -2250,6 +2647,12 @@ function generateEslintConfig(input) {
2250
2647
  sections.push(" rules: {");
2251
2648
  sections.push(" ...react.configs.flat.recommended.rules,");
2252
2649
  sections.push(' ...react.configs.flat["jsx-runtime"].rules,');
2650
+ for (const [key, value] of Object.entries(splitRules.reactHooks)) {
2651
+ sections.push(` "${key}": ${JSON.stringify(value)},`);
2652
+ }
2653
+ for (const [key, value] of Object.entries(splitRules.reactOrA11y)) {
2654
+ sections.push(` "${key}": ${JSON.stringify(value)},`);
2655
+ }
2253
2656
  sections.push(" },");
2254
2657
  sections.push(" },");
2255
2658
  sections.push(" jsxA11y.flatConfigs.strict,");
@@ -2270,10 +2673,14 @@ function generateEslintConfig(input) {
2270
2673
  sections.push(" prettier,");
2271
2674
  sections.push("");
2272
2675
  }
2273
- if (Object.keys(rules).length > 0) {
2676
+ const overridesCombined = {
2677
+ ...splitRules.generic,
2678
+ ...userOverrides
2679
+ };
2680
+ if (Object.keys(overridesCombined).length > 0) {
2274
2681
  sections.push(" // Rule overrides");
2275
2682
  sections.push(" {");
2276
- sections.push(` rules: ${formatRules(rules, " ")},`);
2683
+ sections.push(` rules: ${formatRules(overridesCombined, " ")},`);
2277
2684
  sections.push(" },");
2278
2685
  sections.push("");
2279
2686
  }
@@ -2335,26 +2742,29 @@ function generateEslintConfig(input) {
2335
2742
  sections.push(" },");
2336
2743
  sections.push("");
2337
2744
  }
2338
- sections.push("];");
2745
+ sections.push("]);");
2339
2746
  sections.push("");
2340
2747
  return sections.join("\n");
2341
2748
  }
2749
+ var warnedPresetNames;
2342
2750
  var init_eslint_generator = __esm({
2343
2751
  "src/lib/eslint-generator.ts"() {
2344
2752
  "use strict";
2753
+ warnedPresetNames = /* @__PURE__ */ new Set();
2345
2754
  }
2346
2755
  });
2347
2756
 
2348
2757
  // src/cli/eslint.ts
2349
2758
  var eslint_exports = {};
2350
2759
  __export(eslint_exports, {
2760
+ autoDetectIgnorePatterns: () => autoDetectIgnorePatterns,
2351
2761
  checkEslintDrift: () => checkEslintDrift,
2352
2762
  eslintInit: () => eslintInit,
2353
2763
  eslintSync: () => eslintSync,
2354
2764
  runEslint: () => runEslint
2355
2765
  });
2356
- import { readFile as readFile8, writeFile as writeFile3, access as access2 } from "node:fs/promises";
2357
- import { join as join7, relative as relative2 } from "node:path";
2766
+ import { readFile as readFile9, writeFile as writeFile4, access as access2, readdir as readdir5 } from "node:fs/promises";
2767
+ import { join as join8, relative as relative2 } from "node:path";
2358
2768
  async function fileExists2(filePath) {
2359
2769
  try {
2360
2770
  await access2(filePath);
@@ -2363,16 +2773,56 @@ async function fileExists2(filePath) {
2363
2773
  return false;
2364
2774
  }
2365
2775
  }
2776
+ async function autoDetectIgnorePatterns(absPath) {
2777
+ const patterns = [];
2778
+ if (await fileExists2(join8(absPath, "esbuild.js"))) {
2779
+ patterns.push("esbuild.js");
2780
+ }
2781
+ let entries = [];
2782
+ try {
2783
+ entries = await readdir5(absPath);
2784
+ } catch (err) {
2785
+ console.error(
2786
+ ` autoDetectIgnorePatterns: failed to read ${absPath}: ${err instanceof Error ? err.message : String(err)}`
2787
+ );
2788
+ entries = [];
2789
+ }
2790
+ const esbuildVariant = /^esbuild\.[^.]+\.(mjs|cjs|js)$/;
2791
+ for (const name of entries) {
2792
+ if (esbuildVariant.test(name)) {
2793
+ patterns.push(name);
2794
+ }
2795
+ }
2796
+ for (const ext of ["ts", "mts", "js", "mjs"]) {
2797
+ const candidate = `vitest.config.${ext}`;
2798
+ if (await fileExists2(join8(absPath, candidate))) {
2799
+ patterns.push(candidate);
2800
+ break;
2801
+ }
2802
+ }
2803
+ for (const ext of ["ts", "mts", "js", "mjs"]) {
2804
+ const candidate = `vite.config.${ext}`;
2805
+ if (await fileExists2(join8(absPath, candidate))) {
2806
+ patterns.push(candidate);
2807
+ break;
2808
+ }
2809
+ }
2810
+ if (await fileExists2(join8(absPath, "tauri.conf.json"))) {
2811
+ patterns.push("src-tauri/**");
2812
+ patterns.push("**/*.d.ts");
2813
+ }
2814
+ return patterns;
2815
+ }
2366
2816
  function detectPackageManager(projectPath) {
2367
2817
  return (async () => {
2368
- if (await fileExists2(join7(projectPath, "pnpm-lock.yaml"))) return "pnpm";
2369
- if (await fileExists2(join7(projectPath, "yarn.lock"))) return "yarn";
2818
+ if (await fileExists2(join8(projectPath, "pnpm-lock.yaml"))) return "pnpm";
2819
+ if (await fileExists2(join8(projectPath, "yarn.lock"))) return "yarn";
2370
2820
  return "npm";
2371
2821
  })();
2372
2822
  }
2373
2823
  async function getInstalledDeps(pkgJsonPath) {
2374
2824
  try {
2375
- const raw = await readFile8(pkgJsonPath, "utf-8");
2825
+ const raw = await readFile9(pkgJsonPath, "utf-8");
2376
2826
  const pkg = JSON.parse(raw);
2377
2827
  const all = /* @__PURE__ */ new Set();
2378
2828
  for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
@@ -2393,11 +2843,13 @@ function buildInstallCommand(pm, packages, workspaceRoot) {
2393
2843
  return `npm install -D ${pkgStr}`;
2394
2844
  }
2395
2845
  }
2396
- async function resolvePresetsForTechStack(techNames) {
2846
+ async function resolvePresetsForTechStack(techNames, capabilities = []) {
2397
2847
  const techParam = techNames.join(",");
2398
- const res = await apiGet("/eslint-presets", {
2399
- tech_stack: techParam
2400
- });
2848
+ const query = { tech_stack: techParam };
2849
+ if (capabilities.length > 0) {
2850
+ query.capabilities = capabilities.join(",");
2851
+ }
2852
+ const res = await apiGet("/eslint-presets", query);
2401
2853
  return res.data ?? [];
2402
2854
  }
2403
2855
  async function eslintInit(repoId, projectPath) {
@@ -2432,19 +2884,27 @@ async function eslintInit(repoId, projectPath) {
2432
2884
  const allRequiredDeps = /* @__PURE__ */ new Map();
2433
2885
  const configsToWrite = [];
2434
2886
  for (const target of targets) {
2435
- const techNames = target.techStack.map((t) => t.name);
2887
+ const techNames = target.techStack.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
2888
+ const capabilities = collectCapabilities(target.techStack);
2436
2889
  console.log(
2437
2890
  ` ${target.name}: ${techNames.length > 0 ? techNames.join(", ") : "(no tech detected)"}`
2438
2891
  );
2439
- if (techNames.length === 0) {
2440
- console.log(` Skipping \u2014 no tech stack detected.
2441
- `);
2892
+ if (capabilities.length > 0) {
2893
+ console.log(` Capabilities: ${capabilities.join(", ")}`);
2894
+ }
2895
+ if (techNames.length === 0 && capabilities.length === 0) {
2896
+ console.log(
2897
+ ` Skipping ${target.name}: no tech or capabilities detected
2898
+ `
2899
+ );
2442
2900
  continue;
2443
2901
  }
2444
- const presets = await resolvePresetsForTechStack(techNames);
2902
+ const presets = await resolvePresetsForTechStack(techNames, capabilities);
2445
2903
  if (presets.length === 0) {
2446
- console.log(` No matching presets found.
2447
- `);
2904
+ console.log(
2905
+ ` No preset matches ${target.name}; skipping (override via --force-preset if needed)
2906
+ `
2907
+ );
2448
2908
  continue;
2449
2909
  }
2450
2910
  console.log(` Presets: ${presets.map((p) => p.name).join(", ")}`);
@@ -2465,12 +2925,17 @@ async function eslintInit(repoId, projectPath) {
2465
2925
  }
2466
2926
  } catch {
2467
2927
  }
2928
+ const detectedIgnores = await autoDetectIgnorePatterns(target.absPath);
2929
+ if (detectedIgnores.length > 0) {
2930
+ console.log(` Auto-ignore: ${detectedIgnores.join(", ")}`);
2931
+ }
2468
2932
  const content = generateEslintConfig({
2469
2933
  presets,
2470
- ruleOverrides: userOverrides
2934
+ ruleOverrides: userOverrides,
2935
+ ignorePatterns: detectedIgnores
2471
2936
  });
2472
2937
  const hash = hashConfig(content);
2473
- const configPath = join7(target.absPath, "eslint.config.mjs");
2938
+ const configPath = join8(target.absPath, "eslint.config.mjs");
2474
2939
  configsToWrite.push({
2475
2940
  target,
2476
2941
  presets,
@@ -2492,11 +2957,11 @@ async function eslintInit(repoId, projectPath) {
2492
2957
  return;
2493
2958
  }
2494
2959
  const pm = await detectPackageManager(projectPath);
2495
- const rootPkgJsonPath = join7(projectPath, "package.json");
2960
+ const rootPkgJsonPath = join8(projectPath, "package.json");
2496
2961
  const installed = await getInstalledDeps(rootPkgJsonPath);
2497
2962
  if (isMonorepo) {
2498
2963
  for (const { target } of configsToWrite) {
2499
- const appPkgJson = join7(target.absPath, "package.json");
2964
+ const appPkgJson = join8(target.absPath, "package.json");
2500
2965
  const appDeps = await getInstalledDeps(appPkgJson);
2501
2966
  for (const dep of appDeps) {
2502
2967
  installed.add(dep);
@@ -2548,7 +3013,7 @@ async function eslintInit(repoId, projectPath) {
2548
3013
  } of configsToWrite) {
2549
3014
  if (await fileExists2(configPath)) {
2550
3015
  try {
2551
- const existing = await readFile8(configPath, "utf-8");
3016
+ const existing = await readFile9(configPath, "utf-8");
2552
3017
  const existingHash = hashConfig(existing);
2553
3018
  if (existingHash === hash) {
2554
3019
  console.log(
@@ -2568,7 +3033,7 @@ async function eslintInit(repoId, projectPath) {
2568
3033
  }
2569
3034
  }
2570
3035
  try {
2571
- await writeFile3(configPath, content, "utf-8");
3036
+ await writeFile4(configPath, content, "utf-8");
2572
3037
  } catch (err) {
2573
3038
  console.error(
2574
3039
  ` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
@@ -2618,18 +3083,22 @@ async function eslintSync(repoId, projectPath) {
2618
3083
  let skippedCount = 0;
2619
3084
  let driftCount = 0;
2620
3085
  for (const config of configs) {
2621
- const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
2622
- const configPath = join7(absPath, "eslint.config.mjs");
3086
+ const absPath = config.source_path === "." ? projectPath : join8(projectPath, config.source_path);
3087
+ const configPath = join8(absPath, "eslint.config.mjs");
2623
3088
  const detected = await detectTechStack(absPath);
2624
- const techNames = detected.flat.map((t) => t.name);
2625
- const currentPresets = await resolvePresetsForTechStack(techNames);
3089
+ const techNames = detected.flat.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
3090
+ const capabilities = collectCapabilities(detected.flat);
3091
+ const currentPresets = await resolvePresetsForTechStack(
3092
+ techNames,
3093
+ capabilities
3094
+ );
2626
3095
  const currentPresetIds = currentPresets.map((p) => p.id).sort();
2627
3096
  const savedPresetIds = [...config.active_preset_ids ?? []].sort();
2628
3097
  const presetsChanged = currentPresetIds.length !== savedPresetIds.length || currentPresetIds.some((id) => !savedPresetIds.includes(id));
2629
3098
  if (!presetsChanged) {
2630
3099
  if (await fileExists2(configPath)) {
2631
3100
  try {
2632
- const currentContent = await readFile8(configPath, "utf-8");
3101
+ const currentContent = await readFile9(configPath, "utf-8");
2633
3102
  const currentHash = hashConfig(currentContent);
2634
3103
  if (config.generated_hash && currentHash !== config.generated_hash) {
2635
3104
  console.log(
@@ -2651,14 +3120,18 @@ async function eslintSync(repoId, projectPath) {
2651
3120
  );
2652
3121
  }
2653
3122
  }
2654
- console.log(` ${config.source_path}: presets changed, regenerating...`);
3123
+ if (presetsChanged) {
3124
+ console.log(` ${config.source_path}: presets changed, regenerating...`);
3125
+ }
2655
3126
  const userOverrides = config.rule_overrides;
3127
+ const detectedIgnores = await autoDetectIgnorePatterns(absPath);
2656
3128
  const content = generateEslintConfig({
2657
3129
  presets: currentPresets,
2658
- ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0
3130
+ ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0,
3131
+ ignorePatterns: detectedIgnores
2659
3132
  });
2660
3133
  try {
2661
- await writeFile3(configPath, content, "utf-8");
3134
+ await writeFile4(configPath, content, "utf-8");
2662
3135
  } catch (err) {
2663
3136
  console.error(
2664
3137
  ` ${config.source_path}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
@@ -2694,11 +3167,11 @@ async function checkEslintDrift(repoId, projectPath) {
2694
3167
  const configs = res.data ?? [];
2695
3168
  for (const config of configs) {
2696
3169
  if (!config.generated_hash) continue;
2697
- const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
2698
- const configPath = join7(absPath, "eslint.config.mjs");
3170
+ const absPath = config.source_path === "." ? projectPath : join8(projectPath, config.source_path);
3171
+ const configPath = join8(absPath, "eslint.config.mjs");
2699
3172
  if (!await fileExists2(configPath)) continue;
2700
3173
  try {
2701
- const content = await readFile8(configPath, "utf-8");
3174
+ const content = await readFile9(configPath, "utf-8");
2702
3175
  const currentHash = hashConfig(content);
2703
3176
  if (currentHash !== config.generated_hash) {
2704
3177
  return true;
@@ -2750,8 +3223,8 @@ __export(sync_exports, {
2750
3223
  runSync: () => runSync
2751
3224
  });
2752
3225
  import { createHash as createHash2 } from "node:crypto";
2753
- import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
2754
- import { join as join8, dirname as dirname2 } from "node:path";
3226
+ import { readFile as readFile10, writeFile as writeFile5, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
3227
+ import { join as join9, dirname as dirname2 } from "node:path";
2755
3228
  function contentHash(content) {
2756
3229
  return createHash2("sha256").update(content).digest("hex");
2757
3230
  }
@@ -2819,11 +3292,14 @@ async function runSync() {
2819
3292
  }
2820
3293
  async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
2821
3294
  console.log(" Reading local and remote state...");
2822
- const claudeDir = join8(projectPath, ".claude");
3295
+ const claudeDir = join9(projectPath, ".claude");
2823
3296
  let localFiles = /* @__PURE__ */ new Map();
2824
3297
  try {
2825
3298
  localFiles = await scanLocalFiles(claudeDir, projectPath);
2826
- } catch {
3299
+ } catch (err) {
3300
+ console.warn(
3301
+ ` Local file scan incomplete: ${err instanceof Error ? err.message : String(err)}`
3302
+ );
2827
3303
  }
2828
3304
  const [defaultsRes, repoSyncRes, repoRes, , fileReposRes] = await Promise.all(
2829
3305
  [
@@ -3051,7 +3527,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3051
3527
  for (const p of toPull) {
3052
3528
  if (p.filePath && p.remoteContent !== null) {
3053
3529
  await mkdir2(dirname2(p.filePath), { recursive: true });
3054
- await writeFile4(p.filePath, p.remoteContent, "utf-8");
3530
+ await writeFile5(p.filePath, p.remoteContent, "utf-8");
3055
3531
  if (p.isHook) await chmod2(p.filePath, 493);
3056
3532
  }
3057
3533
  }
@@ -3142,7 +3618,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3142
3618
  repo_id: repoId,
3143
3619
  file_repos: fileRepoUpdates
3144
3620
  });
3145
- } catch {
3621
+ } catch (err) {
3622
+ console.warn(
3623
+ ` Warning: failed to update file-repo tracking for ${fileRepoUpdates.length} files: ${err instanceof Error ? err.message : String(err)}`
3624
+ );
3146
3625
  }
3147
3626
  }
3148
3627
  console.log(
@@ -3202,7 +3681,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3202
3681
  console.log("\n Sync complete.\n");
3203
3682
  }
3204
3683
  async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
3205
- const settingsPath = join8(claudeDir, "settings.json");
3684
+ const settingsPath = join9(claudeDir, "settings.json");
3206
3685
  const globalSettingsFiles = syncData.global_settings ?? [];
3207
3686
  let globalSettings = {};
3208
3687
  for (const gf of globalSettingsFiles) {
@@ -3222,11 +3701,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3222
3701
  globalSettings,
3223
3702
  repoSettings
3224
3703
  );
3225
- const hooksDir = join8(projectPath, ".claude", "hooks");
3704
+ const hooksDir = join9(projectPath, ".claude", "hooks");
3226
3705
  const discovered = await discoverHooks(hooksDir);
3227
3706
  let localSettings = {};
3228
3707
  try {
3229
- const raw = await readFile9(settingsPath, "utf-8");
3708
+ const raw = await readFile10(settingsPath, "utf-8");
3230
3709
  localSettings = JSON.parse(raw);
3231
3710
  } catch {
3232
3711
  }
@@ -3241,7 +3720,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3241
3720
  const mergedContent = JSON.stringify(merged, null, 2) + "\n";
3242
3721
  let currentContent = "";
3243
3722
  try {
3244
- currentContent = await readFile9(settingsPath, "utf-8");
3723
+ currentContent = await readFile10(settingsPath, "utf-8");
3245
3724
  } catch {
3246
3725
  }
3247
3726
  if (currentContent === mergedContent) {
@@ -3253,18 +3732,30 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
3253
3732
  return;
3254
3733
  }
3255
3734
  await mkdir2(dirname2(settingsPath), { recursive: true });
3256
- await writeFile4(settingsPath, mergedContent, "utf-8");
3735
+ await writeFile5(settingsPath, mergedContent, "utf-8");
3257
3736
  console.log(" Updated settings.json");
3258
3737
  }
3259
3738
  async function syncConfig(repoId, projectPath, dryRun) {
3260
- const configPath = join8(projectPath, ".codebyplan.json");
3739
+ const configPath = join9(projectPath, ".codebyplan.json");
3261
3740
  let currentConfig = {};
3262
3741
  try {
3263
- const raw = await readFile9(configPath, "utf-8");
3742
+ const raw = await readFile10(configPath, "utf-8");
3264
3743
  currentConfig = JSON.parse(raw);
3265
3744
  } catch {
3266
3745
  currentConfig = { repo_id: repoId };
3267
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
+ }
3268
3759
  const repoRes = await apiGet(`/repos/${repoId}`);
3269
3760
  const repo = repoRes.data;
3270
3761
  let portAllocations = [];
@@ -3298,7 +3789,10 @@ async function syncConfig(repoId, projectPath, dryRun) {
3298
3789
  }
3299
3790
  return clean;
3300
3791
  });
3301
- } catch {
3792
+ } catch (err) {
3793
+ console.warn(
3794
+ ` Warning: failed to fetch port allocations: ${err instanceof Error ? err.message : String(err)}`
3795
+ );
3302
3796
  }
3303
3797
  const worktreeId = currentConfig.worktree_id;
3304
3798
  const matchingAlloc = portAllocations[0];
@@ -3329,7 +3823,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
3329
3823
  console.log(" Config would be updated (dry-run).");
3330
3824
  return;
3331
3825
  }
3332
- await writeFile4(configPath, newJson + "\n", "utf-8");
3826
+ await writeFile5(configPath, newJson + "\n", "utf-8");
3333
3827
  console.log(" Updated .codebyplan.json");
3334
3828
  }
3335
3829
  async function syncTechStack(repoId, projectPath, dryRun) {
@@ -3363,8 +3857,10 @@ async function syncTechStack(repoId, projectPath, dryRun) {
3363
3857
  }
3364
3858
  }
3365
3859
  }
3366
- } catch {
3367
- console.log(" Tech stack detection skipped.");
3860
+ } catch (err) {
3861
+ console.warn(
3862
+ ` Tech stack detection skipped: ${err instanceof Error ? err.message : String(err)}`
3863
+ );
3368
3864
  }
3369
3865
  }
3370
3866
  async function syncEslintDriftCheck(repoId, projectPath) {
@@ -3442,8 +3938,10 @@ async function syncPortVerification(repoId, projectPath, dryRun, fix) {
3442
3938
  if (mismatches.length === 0 && unallocated.length === 0) {
3443
3939
  console.log(" Ports verified.");
3444
3940
  }
3445
- } catch {
3446
- console.log(" Port verification skipped.");
3941
+ } catch (err) {
3942
+ console.warn(
3943
+ ` Port verification skipped: ${err instanceof Error ? err.message : String(err)}`
3944
+ );
3447
3945
  }
3448
3946
  }
3449
3947
  function groupByType(items) {
@@ -3477,28 +3975,28 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
3477
3975
  hook: { dir: "hooks", ext: ".sh" },
3478
3976
  template: { dir: "templates", ext: "" },
3479
3977
  context: { dir: "context", ext: ".md" },
3480
- docs_stack: { dir: join8("docs", "stack"), ext: ".md" },
3978
+ docs_stack: { dir: join9("docs", "stack"), ext: ".md" },
3481
3979
  docs: { dir: "docs", ext: ".md" },
3482
3980
  claude_md: { dir: "", ext: "" },
3483
3981
  settings: { dir: "", ext: "" }
3484
3982
  };
3485
- if (remote.type === "claude_md") return join8(projectPath, "CLAUDE.md");
3486
- if (remote.type === "settings") return join8(claudeDir, "settings.json");
3983
+ if (remote.type === "claude_md") return join9(projectPath, "CLAUDE.md");
3984
+ if (remote.type === "settings") return join9(claudeDir, "settings.json");
3487
3985
  const cfg = typeConfig2[remote.type];
3488
- if (!cfg) return join8(claudeDir, remote.name);
3489
- const typeDir = remote.type === "command" ? join8(claudeDir, cfg.dir, "cbp") : join8(claudeDir, cfg.dir);
3986
+ if (!cfg) return join9(claudeDir, remote.name);
3987
+ const typeDir = remote.type === "command" ? join9(claudeDir, cfg.dir, "cbp") : join9(claudeDir, cfg.dir);
3490
3988
  if (cfg.subfolder)
3491
- return join8(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
3989
+ return join9(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
3492
3990
  if (remote.type === "command" && remote.category)
3493
- return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3494
- if (remote.type === "template") return join8(typeDir, remote.name);
3991
+ return join9(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3992
+ if (remote.type === "template") return join9(typeDir, remote.name);
3495
3993
  if (remote.category && (remote.type === "context" || remote.type === "docs_stack" || remote.type === "docs"))
3496
- return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3497
- return join8(typeDir, `${remote.name}${cfg.ext}`);
3994
+ return join9(typeDir, remote.category, `${remote.name}${cfg.ext}`);
3995
+ return join9(typeDir, `${remote.name}${cfg.ext}`);
3498
3996
  }
3499
3997
  function getSyncVersion() {
3500
3998
  try {
3501
- return "1.4.1";
3999
+ return "1.4.3";
3502
4000
  } catch {
3503
4001
  return "unknown";
3504
4002
  }
@@ -3547,6 +4045,7 @@ var init_sync = __esm({
3547
4045
  init_settings_merge();
3548
4046
  init_hook_registry();
3549
4047
  init_port_verify();
4048
+ init_resolve_worktree();
3550
4049
  init_eslint();
3551
4050
  }
3552
4051
  });
@@ -3554,68 +4053,69 @@ var init_sync = __esm({
3554
4053
  // src/index.ts
3555
4054
  init_version();
3556
4055
  import { readFileSync } from "node:fs";
3557
- import { resolve } from "node:path";
3558
- if (!process.env.CODEBYPLAN_API_KEY) {
3559
- try {
3560
- const envPath = resolve(process.cwd(), ".env.local");
3561
- const content = readFileSync(envPath, "utf-8");
3562
- for (const line of content.split("\n")) {
3563
- const trimmed = line.trim();
3564
- if (!trimmed || trimmed.startsWith("#")) continue;
3565
- const eq = trimmed.indexOf("=");
3566
- if (eq === -1) continue;
3567
- const key = trimmed.slice(0, eq).trim();
3568
- const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
3569
- if (!process.env[key]) process.env[key] = val;
4056
+ import { resolve as resolve2 } from "node:path";
4057
+ void (async () => {
4058
+ if (!process.env.CODEBYPLAN_API_KEY) {
4059
+ try {
4060
+ const envPath = resolve2(process.cwd(), ".env.local");
4061
+ const content = readFileSync(envPath, "utf-8");
4062
+ for (const line of content.split("\n")) {
4063
+ const trimmed = line.trim();
4064
+ if (!trimmed || trimmed.startsWith("#")) continue;
4065
+ const eq = trimmed.indexOf("=");
4066
+ if (eq === -1) continue;
4067
+ const key = trimmed.slice(0, eq).trim();
4068
+ const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
4069
+ if (!process.env[key]) process.env[key] = val;
4070
+ }
4071
+ } catch {
3570
4072
  }
3571
- } catch {
3572
4073
  }
3573
- }
3574
- if (process.env.CODEBYPLAN_API_KEY?.startsWith("CODEBYPLAN_API_KEY=")) {
3575
- process.env.CODEBYPLAN_API_KEY = process.env.CODEBYPLAN_API_KEY.slice(
3576
- "CODEBYPLAN_API_KEY=".length
3577
- );
3578
- }
3579
- var arg = process.argv[2];
3580
- if (arg === "--version" || arg === "-v") {
3581
- console.log(VERSION);
3582
- process.exit(0);
3583
- }
3584
- if (arg === "setup") {
3585
- const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
3586
- await runSetup2();
3587
- process.exit(0);
3588
- }
3589
- if (arg === "sync") {
3590
- const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
3591
- const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
3592
- try {
3593
- await runSync2();
3594
- } catch (err) {
3595
- if (err instanceof SyncCancelledError2) {
3596
- console.log("\n Sync cancelled.\n");
3597
- process.exit(0);
4074
+ if (process.env.CODEBYPLAN_API_KEY?.startsWith("CODEBYPLAN_API_KEY=")) {
4075
+ process.env.CODEBYPLAN_API_KEY = process.env.CODEBYPLAN_API_KEY.slice(
4076
+ "CODEBYPLAN_API_KEY=".length
4077
+ );
4078
+ }
4079
+ const arg = process.argv[2];
4080
+ if (arg === "--version" || arg === "-v") {
4081
+ console.log(VERSION);
4082
+ process.exit(0);
4083
+ }
4084
+ if (arg === "setup") {
4085
+ const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
4086
+ await runSetup2();
4087
+ process.exit(0);
4088
+ }
4089
+ if (arg === "sync") {
4090
+ const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
4091
+ const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
4092
+ try {
4093
+ await runSync2();
4094
+ } catch (err) {
4095
+ if (err instanceof SyncCancelledError2) {
4096
+ console.log("\n Sync cancelled.\n");
4097
+ process.exit(0);
4098
+ }
4099
+ throw err;
3598
4100
  }
3599
- throw err;
4101
+ process.exit(0);
3600
4102
  }
3601
- process.exit(0);
3602
- }
3603
- if (arg === "eslint") {
3604
- const { runEslint: runEslint2 } = await Promise.resolve().then(() => (init_eslint(), eslint_exports));
3605
- const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
3606
- try {
3607
- await runEslint2();
3608
- } catch (err) {
3609
- if (err instanceof SyncCancelledError2) {
3610
- console.log("\n Cancelled.\n");
3611
- process.exit(0);
4103
+ if (arg === "eslint") {
4104
+ const { runEslint: runEslint2 } = await Promise.resolve().then(() => (init_eslint(), eslint_exports));
4105
+ const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
4106
+ try {
4107
+ await runEslint2();
4108
+ } catch (err) {
4109
+ if (err instanceof SyncCancelledError2) {
4110
+ console.log("\n Cancelled.\n");
4111
+ process.exit(0);
4112
+ }
4113
+ throw err;
3612
4114
  }
3613
- throw err;
4115
+ process.exit(0);
3614
4116
  }
3615
- process.exit(0);
3616
- }
3617
- if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
3618
- console.log(`
4117
+ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
4118
+ console.log(`
3619
4119
  CodeByPlan CLI v${VERSION}
3620
4120
 
3621
4121
  Usage:
@@ -3638,13 +4138,14 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
3638
4138
 
3639
4139
  MCP Server:
3640
4140
  Claude Code connects to CodeByPlan via remote MCP:
3641
- URL: https://codebyplan.com/mcp
4141
+ URL: https://www.codebyplan.com/mcp
3642
4142
  Auth: x-api-key header (configured during setup)
3643
4143
 
3644
4144
  Learn more: https://codebyplan.com
3645
4145
  `);
3646
- process.exit(0);
3647
- }
3648
- console.error(`Unknown command: ${arg}`);
3649
- console.error("Run 'codebyplan help' for usage.");
3650
- process.exit(1);
4146
+ process.exit(0);
4147
+ }
4148
+ console.error(`Unknown command: ${arg}`);
4149
+ console.error("Run 'codebyplan help' for usage.");
4150
+ process.exit(1);
4151
+ })();