codebyplan 1.4.1 → 1.4.2

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 +8 -7
  2. package/dist/cli.js +582 -157
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -9,6 +9,7 @@ npx codebyplan setup
9
9
  ```
10
10
 
11
11
  This will:
12
+
12
13
  1. Prompt for your API key (get one at [codebyplan.com/settings/api-keys](https://codebyplan.com/settings/api-keys))
13
14
  2. Configure Claude Code to connect via remote MCP
14
15
  3. Optionally link a repository and run the first sync
@@ -25,13 +26,13 @@ Bidirectional sync of `.claude/` infrastructure files between your local project
25
26
 
26
27
  **Options:**
27
28
 
28
- | Flag | Description |
29
- |------|-------------|
30
- | `--path <dir>` | Project root directory (default: cwd) |
29
+ | Flag | Description |
30
+ | ------------------ | ------------------------------------------------ |
31
+ | `--path <dir>` | Project root directory (default: cwd) |
31
32
  | `--repo-id <uuid>` | Repository ID (default: from `.codebyplan.json`) |
32
- | `--dry-run` | Preview changes without writing |
33
- | `--force` | Skip confirmation and conflict prompts |
34
- | `--fix` | Auto-create missing port allocations |
33
+ | `--dry-run` | Preview changes without writing |
34
+ | `--force` | Skip confirmation and conflict prompts |
35
+ | `--fix` | Auto-create missing port allocations |
35
36
 
36
37
  ### `codebyplan help`
37
38
 
@@ -49,7 +50,7 @@ Claude Code connects to CodeByPlan via a remote MCP server. The `setup` command
49
50
  {
50
51
  "mcpServers": {
51
52
  "codebyplan": {
52
- "url": "https://codebyplan.com/mcp",
53
+ "url": "https://www.codebyplan.com/mcp",
53
54
  "headers": { "x-api-key": "your-api-key" }
54
55
  }
55
56
  }
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.2";
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;
@@ -211,11 +211,14 @@ var init_settings_merge = __esm({
211
211
  import { readdir, readFile } from "node:fs/promises";
212
212
  import { join } from "node:path";
213
213
  function parseHookMeta(content) {
214
- const match = content.match(/^#\s*@hook:\s*(\S+)(?:\s+(.+))?$/m);
215
- if (!match) return null;
214
+ const lineMatch = content.match(/^#\s*@hook:(.*)$/m);
215
+ if (!lineMatch) return null;
216
+ const parts = lineMatch[1].trim().split(/\s+/);
217
+ const event = parts[0];
218
+ if (!event) return null;
216
219
  return {
217
- event: match[1],
218
- matcher: match[2]?.trim() ?? ""
220
+ event,
221
+ matcher: parts.slice(1).join(" ")
219
222
  };
220
223
  }
221
224
  async function discoverHooks(hooksDir) {
@@ -550,7 +553,7 @@ async function executeSyncToLocal(options) {
550
553
  const substituted = substituteVariables(remote.content, repoData);
551
554
  remotePathMap.set(relPath, {
552
555
  content: substituted,
553
- name: `${remote.category ?? ""}/${remote.name}`
556
+ name: remote.category ? `${remote.category}/${remote.name}` : remote.name
554
557
  });
555
558
  }
556
559
  for (const [relPath, { content, name }] of remotePathMap) {
@@ -793,7 +796,7 @@ async function readConfig(path) {
793
796
  }
794
797
  }
795
798
  function buildMcpEntry(apiKey) {
796
- const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
799
+ const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
797
800
  return {
798
801
  url: `${baseUrl}/mcp`,
799
802
  headers: { "x-api-key": apiKey }
@@ -835,7 +838,7 @@ async function runSetup() {
835
838
  return;
836
839
  }
837
840
  console.log("\n Validating API key...");
838
- const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
841
+ const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
839
842
  const res = await fetch(`${baseUrl}/api/repos`, {
840
843
  headers: { "x-api-key": apiKey },
841
844
  signal: AbortSignal.timeout(1e4)
@@ -887,7 +890,7 @@ async function runSetup() {
887
890
  `
888
891
  );
889
892
  console.log(
890
- ` { "url": "https://codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
893
+ ` { "url": "https://www.codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
891
894
  `
892
895
  );
893
896
  }
@@ -971,14 +974,30 @@ var init_setup = __esm({
971
974
 
972
975
  // src/cli/config.ts
973
976
  import { readFile as readFile4 } from "node:fs/promises";
974
- import { join as join4 } from "node:path";
977
+ import { join as join4, resolve } from "node:path";
978
+ async function findCodebyplanConfig(startDir, maxDepth = 20) {
979
+ let cursor = resolve(startDir);
980
+ for (let depth = 0; depth < maxDepth; depth++) {
981
+ const configPath = join4(cursor, ".codebyplan.json");
982
+ try {
983
+ const raw = await readFile4(configPath, "utf-8");
984
+ const parsed = JSON.parse(raw);
985
+ return { path: configPath, contents: parsed };
986
+ } catch {
987
+ }
988
+ const parent = resolve(cursor, "..");
989
+ if (parent === cursor) return null;
990
+ cursor = parent;
991
+ }
992
+ return null;
993
+ }
975
994
  function parseFlags(startIndex) {
976
995
  const flags = {};
977
996
  const args = process.argv.slice(startIndex);
978
997
  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);
998
+ const arg = args[i];
999
+ if (arg.startsWith("--") && i + 1 < args.length) {
1000
+ const key = arg.slice(2);
982
1001
  flags[key] = args[++i];
983
1002
  }
984
1003
  }
@@ -992,13 +1011,10 @@ async function resolveConfig(flags) {
992
1011
  let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
993
1012
  let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
994
1013
  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 {
1014
+ const found = await findCodebyplanConfig(projectPath);
1015
+ if (found) {
1016
+ if (!repoId) repoId = found.contents.repo_id;
1017
+ if (!worktreeId) worktreeId = found.contents.worktree_id;
1002
1018
  }
1003
1019
  }
1004
1020
  if (!repoId) {
@@ -1633,12 +1649,77 @@ async function discoverMonorepoApps(projectPath) {
1633
1649
  }
1634
1650
  return apps;
1635
1651
  }
1652
+ async function hasJsxFile(dir, depth = 0) {
1653
+ if (depth > 6) return false;
1654
+ try {
1655
+ const entries = await readdir4(dir, { withFileTypes: true });
1656
+ for (const entry of entries) {
1657
+ const name = entry.name;
1658
+ if (entry.isDirectory()) {
1659
+ if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
1660
+ if (await hasJsxFile(join6(dir, name), depth + 1)) return true;
1661
+ } else if (entry.isFile()) {
1662
+ if (JSX_TEST_PATTERN.test(name)) continue;
1663
+ if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
1664
+ }
1665
+ }
1666
+ } catch (err) {
1667
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1668
+ return false;
1669
+ }
1670
+ console.error(
1671
+ `detectCapabilities: readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`
1672
+ );
1673
+ }
1674
+ return false;
1675
+ }
1676
+ async function detectCapabilities(dirPath, pkgJson) {
1677
+ const caps = /* @__PURE__ */ new Set();
1678
+ for (const sub of JSX_SCAN_DIRS) {
1679
+ if (await hasJsxFile(join6(dirPath, sub))) {
1680
+ caps.add("jsx");
1681
+ break;
1682
+ }
1683
+ }
1684
+ if (pkgJson) {
1685
+ const allDeps = {
1686
+ ...pkgJson.dependencies ?? {},
1687
+ ...pkgJson.devDependencies ?? {}
1688
+ };
1689
+ for (const dep of Object.keys(allDeps)) {
1690
+ if (SERVER_FRAMEWORK_DEPS.has(dep)) {
1691
+ caps.add("node-server");
1692
+ break;
1693
+ }
1694
+ }
1695
+ if (!caps.has("node-server") && typeof pkgJson.main === "string") {
1696
+ if (SERVER_MAIN_ENTRIES.has(pkgJson.main.trim())) {
1697
+ caps.add("node-server");
1698
+ }
1699
+ }
1700
+ }
1701
+ if (!caps.has("node-server") && await fileExists(join6(dirPath, "src", "main.ts"))) {
1702
+ caps.add("node-server");
1703
+ }
1704
+ if (pkgJson && pkgJson.bin) {
1705
+ if (typeof pkgJson.bin === "string" && pkgJson.bin.trim().length > 0) {
1706
+ caps.add("cli-bin");
1707
+ } else if (typeof pkgJson.bin === "object" && pkgJson.bin !== null && Object.keys(pkgJson.bin).length > 0) {
1708
+ caps.add("cli-bin");
1709
+ }
1710
+ }
1711
+ return caps;
1712
+ }
1636
1713
  async function detectFromDirectory(dirPath) {
1637
1714
  const seen = /* @__PURE__ */ new Map();
1715
+ let pkgJson = null;
1638
1716
  try {
1639
1717
  const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
1640
- const pkg = JSON.parse(raw);
1641
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1718
+ pkgJson = JSON.parse(raw);
1719
+ const allDeps = {
1720
+ ...pkgJson.dependencies ?? {},
1721
+ ...pkgJson.devDependencies ?? {}
1722
+ };
1642
1723
  for (const depName of Object.keys(allDeps)) {
1643
1724
  const rule = PACKAGE_MAP[depName];
1644
1725
  if (rule) {
@@ -1669,11 +1750,40 @@ async function detectFromDirectory(dirPath) {
1669
1750
  seen.set(key, { name: rule.name, category: rule.category });
1670
1751
  }
1671
1752
  }
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);
1753
+ const capabilities = await detectCapabilities(dirPath, pkgJson);
1754
+ const capsArray = Array.from(capabilities).sort();
1755
+ const entries = Array.from(seen.values()).map((entry) => {
1756
+ const isBearer = CAPABILITY_BEARER_NAMES.has(entry.name.toLowerCase());
1757
+ return {
1758
+ ...entry,
1759
+ capabilities: isBearer ? capsArray : []
1760
+ };
1676
1761
  });
1762
+ if (capsArray.length > 0) {
1763
+ const hasBearerWithCaps = entries.some(
1764
+ (e) => CAPABILITY_BEARER_NAMES.has(e.name.toLowerCase()) && (e.capabilities?.some((c) => c.length > 0) ?? false)
1765
+ );
1766
+ if (!hasBearerWithCaps) {
1767
+ entries.push({
1768
+ name: SYNTHETIC_CARRIER_NAME,
1769
+ category: "tool",
1770
+ capabilities: capsArray
1771
+ });
1772
+ }
1773
+ }
1774
+ return entries.sort(compareByCategoryThenName);
1775
+ }
1776
+ function collectCapabilities(entries) {
1777
+ const set = /* @__PURE__ */ new Set();
1778
+ for (const entry of entries) {
1779
+ if (!entry.capabilities) continue;
1780
+ for (const cap of entry.capabilities) {
1781
+ if (typeof cap === "string" && cap.length > 0) {
1782
+ set.add(cap.toLowerCase());
1783
+ }
1784
+ }
1785
+ }
1786
+ return Array.from(set).sort();
1677
1787
  }
1678
1788
  async function detectTechStack(projectPath) {
1679
1789
  const repo = await detectFromDirectory(projectPath);
@@ -1692,40 +1802,80 @@ async function detectTechStack(projectPath) {
1692
1802
  for (const app of apps) {
1693
1803
  for (const entry of app.stack) {
1694
1804
  const key = entry.name.toLowerCase();
1695
- if (!flatMap.has(key)) {
1805
+ const existing = flatMap.get(key);
1806
+ if (!existing) {
1696
1807
  flatMap.set(key, entry);
1808
+ } else if (entry.capabilities?.length) {
1809
+ const merged = Array.from(
1810
+ /* @__PURE__ */ new Set([...existing.capabilities ?? [], ...entry.capabilities])
1811
+ ).sort();
1812
+ flatMap.set(key, { ...existing, capabilities: merged });
1697
1813
  }
1698
1814
  }
1699
1815
  }
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 };
1816
+ const repoCleaned = stripSyntheticIfCovered(repo).sort(
1817
+ compareByCategoryThenName
1818
+ );
1819
+ const appsCleaned = apps.map((a) => ({
1820
+ ...a,
1821
+ stack: stripSyntheticIfCovered(a.stack).sort(compareByCategoryThenName)
1822
+ }));
1823
+ const flat = stripSyntheticIfCovered(Array.from(flatMap.values())).sort(
1824
+ compareByCategoryThenName
1825
+ );
1826
+ return { repo: repoCleaned, apps: appsCleaned, flat };
1827
+ }
1828
+ function stripSyntheticIfCovered(entries) {
1829
+ const synth = entries.find((e) => e.name === SYNTHETIC_CARRIER_NAME);
1830
+ if (!synth?.capabilities?.length) return entries;
1831
+ const realBearerCaps = /* @__PURE__ */ new Set();
1832
+ for (const e of entries) {
1833
+ if (e.name === SYNTHETIC_CARRIER_NAME) continue;
1834
+ for (const c of e.capabilities ?? []) realBearerCaps.add(c);
1835
+ }
1836
+ if (synth.capabilities.every((c) => realBearerCaps.has(c))) {
1837
+ return entries.filter((e) => e.name !== SYNTHETIC_CARRIER_NAME);
1838
+ }
1839
+ return entries;
1706
1840
  }
1707
1841
  function mergeTechStack(remote, detected) {
1708
- const remoteResult = Array.isArray(remote) ? { repo: remote, apps: [], flat: remote } : remote;
1842
+ const stripCarrier = (e) => e.name !== SYNTHETIC_CARRIER_NAME;
1843
+ const cleanDetected = {
1844
+ repo: detected.repo.filter(stripCarrier),
1845
+ apps: detected.apps.map((a) => ({
1846
+ ...a,
1847
+ stack: a.stack.filter(stripCarrier)
1848
+ })),
1849
+ flat: detected.flat.filter(stripCarrier)
1850
+ };
1851
+ const remoteResult = Array.isArray(remote) ? {
1852
+ repo: remote.filter(stripCarrier),
1853
+ apps: [],
1854
+ flat: remote.filter(stripCarrier)
1855
+ } : {
1856
+ repo: remote.repo.filter(stripCarrier),
1857
+ apps: remote.apps.map((a) => ({
1858
+ ...a,
1859
+ stack: a.stack.filter(stripCarrier)
1860
+ })),
1861
+ flat: remote.flat.filter(stripCarrier)
1862
+ };
1709
1863
  const seen = /* @__PURE__ */ new Map();
1710
1864
  for (const entry of remoteResult.flat) {
1711
1865
  seen.set(entry.name.toLowerCase(), entry);
1712
1866
  }
1713
1867
  const added = [];
1714
- for (const entry of detected.flat) {
1868
+ for (const entry of cleanDetected.flat) {
1715
1869
  const key = entry.name.toLowerCase();
1716
1870
  if (!seen.has(key)) {
1717
1871
  seen.set(key, entry);
1718
1872
  added.push(entry);
1719
1873
  }
1720
1874
  }
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
- });
1875
+ const flat = Array.from(seen.values()).sort(compareByCategoryThenName);
1726
1876
  const merged = {
1727
- repo: detected.repo,
1728
- apps: detected.apps,
1877
+ repo: cleanDetected.repo,
1878
+ apps: cleanDetected.apps,
1729
1879
  flat
1730
1880
  };
1731
1881
  return { merged, added };
@@ -1825,7 +1975,7 @@ async function scanAllDependencies(projectPath) {
1825
1975
  }
1826
1976
  return { dependencies };
1827
1977
  }
1828
- var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SKIP_DIRS;
1978
+ 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
1979
  var init_tech_detect = __esm({
1830
1980
  "src/lib/tech-detect.ts"() {
1831
1981
  "use strict";
@@ -1841,6 +1991,7 @@ var init_tech_detect = __esm({
1841
1991
  svelte: { name: "Svelte", category: "framework" },
1842
1992
  astro: { name: "Astro", category: "framework" },
1843
1993
  "@angular/core": { name: "Angular", category: "framework" },
1994
+ "@nestjs/core": { name: "NestJS", category: "framework" },
1844
1995
  // Libraries (UI)
1845
1996
  react: { name: "React", category: "framework" },
1846
1997
  vue: { name: "Vue", category: "framework" },
@@ -1955,6 +2106,47 @@ var init_tech_detect = __esm({
1955
2106
  { file: "nx.json", rule: { name: "Nx", category: "build" } },
1956
2107
  { file: "lerna.json", rule: { name: "Lerna", category: "build" } }
1957
2108
  ];
2109
+ SYNTHETIC_CARRIER_NAME = "__capabilities__";
2110
+ CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
2111
+ "react",
2112
+ "next.js",
2113
+ "vue",
2114
+ "svelte",
2115
+ "solid",
2116
+ "preact",
2117
+ "remix",
2118
+ "astro",
2119
+ "angular",
2120
+ "nestjs",
2121
+ "nuxt",
2122
+ "gatsby",
2123
+ "express",
2124
+ "fastify",
2125
+ "hono",
2126
+ "react native",
2127
+ "expo"
2128
+ ]);
2129
+ JSX_TEST_PATTERN = /\.(test|spec)\.(tsx|jsx)$/;
2130
+ JSX_SKIP_DIRS = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
2131
+ JSX_SCAN_DIRS = ["src", "app", "pages"];
2132
+ SERVER_MAIN_ENTRIES = /* @__PURE__ */ new Set([
2133
+ "dist/main.js",
2134
+ "dist/server.js",
2135
+ "dist/index.js",
2136
+ "main.js",
2137
+ "server.js"
2138
+ ]);
2139
+ SERVER_FRAMEWORK_DEPS = /* @__PURE__ */ new Set([
2140
+ "@nestjs/core",
2141
+ "express",
2142
+ "fastify",
2143
+ "hono"
2144
+ ]);
2145
+ compareByCategoryThenName = (a, b) => {
2146
+ const catCmp = a.category.localeCompare(b.category);
2147
+ if (catCmp !== 0) return catCmp;
2148
+ return a.name.localeCompare(b.name);
2149
+ };
1958
2150
  SKIP_DIRS = /* @__PURE__ */ new Set([
1959
2151
  "node_modules",
1960
2152
  ".next",
@@ -2032,30 +2224,65 @@ async function verifyPorts(projectPath, portAllocations) {
2032
2224
  }
2033
2225
  return mismatches;
2034
2226
  }
2227
+ function isDevServerScript(pkg) {
2228
+ const scripts = pkg.scripts;
2229
+ const raw = scripts?.dev;
2230
+ if (!raw || typeof raw !== "string") return false;
2231
+ const script = raw.trim().toLowerCase();
2232
+ if (!script) return false;
2233
+ for (const pattern of DEV_SERVER_BIN_PATTERNS) {
2234
+ if (pattern.test(script)) return true;
2235
+ }
2236
+ const tokens = script.split(/\s+/);
2237
+ for (const token of tokens) {
2238
+ if (token === "--port" || token === "-p") return true;
2239
+ if (token.startsWith("--port=")) return true;
2240
+ }
2241
+ return false;
2242
+ }
2243
+ function labelMatchesAppName(label, appName) {
2244
+ if (!label || !appName) return false;
2245
+ const normalize = (s) => s.toLowerCase().replace(/-/g, " ").replace(/[()]/g, " ").replace(/\s+/g, " ").trim();
2246
+ const labelTokens = normalize(label).split(" ").filter(Boolean);
2247
+ const appToken = normalize(appName);
2248
+ if (!appToken) return false;
2249
+ const appTokens = appToken.split(" ").filter(Boolean);
2250
+ if (appTokens.length === 1) {
2251
+ return labelTokens.includes(appTokens[0]);
2252
+ }
2253
+ for (let i = 0; i <= labelTokens.length - appTokens.length; i++) {
2254
+ if (appTokens.every((t, j) => labelTokens[i + j] === t)) return true;
2255
+ }
2256
+ return false;
2257
+ }
2035
2258
  async function findUnallocatedApps(projectPath, portAllocations) {
2036
2259
  const apps = await discoverMonorepoApps(projectPath);
2037
2260
  if (apps.length === 0) {
2038
2261
  return [];
2039
2262
  }
2040
- const allocatedLabels = new Set(portAllocations.map((a) => a.label));
2041
2263
  const unallocated = [];
2042
2264
  for (const app of apps) {
2043
- if (allocatedLabels.has(app.name)) continue;
2265
+ if (portAllocations.some((a) => labelMatchesAppName(a.label ?? "", app.name))) {
2266
+ continue;
2267
+ }
2268
+ let pkg;
2044
2269
  try {
2045
2270
  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
- });
2271
+ pkg = JSON.parse(raw);
2057
2272
  } catch {
2273
+ continue;
2058
2274
  }
2275
+ if (!isDevServerScript(pkg)) continue;
2276
+ const framework = detectFramework(pkg);
2277
+ const detectedPort = detectPortFromScripts(pkg);
2278
+ const command = `pnpm --filter ${app.name} dev`;
2279
+ unallocated.push({
2280
+ name: app.name,
2281
+ path: app.path,
2282
+ framework,
2283
+ detectedPort,
2284
+ command
2285
+ });
2059
2286
  }
2060
2287
  return unallocated;
2061
2288
  }
@@ -2066,16 +2293,46 @@ function getAppLabel(relativePath) {
2066
2293
  }
2067
2294
  return "root";
2068
2295
  }
2296
+ var DEV_SERVER_BIN_PATTERNS;
2069
2297
  var init_port_verify = __esm({
2070
2298
  "src/lib/port-verify.ts"() {
2071
2299
  "use strict";
2072
2300
  init_tech_detect();
2073
2301
  init_server_detect();
2302
+ DEV_SERVER_BIN_PATTERNS = [
2303
+ /\bnext\s+dev\b/,
2304
+ /\bnest\s+start\b/,
2305
+ /\bvite\s+(?:dev|serve)\b/,
2306
+ /\bvite\s+preview\b/,
2307
+ /\bnuxt\s+dev\b/,
2308
+ /\b(?:svelte-kit|sveltekit)\s+dev\b/,
2309
+ /\bexpo\s+start\b/
2310
+ ];
2074
2311
  }
2075
2312
  });
2076
2313
 
2077
2314
  // src/lib/eslint-generator.ts
2078
2315
  import { createHash } from "node:crypto";
2316
+ function importedIdentifiers(importLines) {
2317
+ const names = /* @__PURE__ */ new Set();
2318
+ for (const line of importLines) {
2319
+ let m = line.match(/^import\s+([A-Za-z_$][\w$]*)\s+from/);
2320
+ if (m) names.add(m[1]);
2321
+ m = line.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from/);
2322
+ if (m) names.add(m[1]);
2323
+ m = line.match(/^import\s*\{([^}]*)\}\s*from/);
2324
+ if (m) {
2325
+ for (const entry of m[1].split(",")) {
2326
+ const parts = entry.trim().split(/\s+as\s+/);
2327
+ const n = (parts[1] ?? parts[0]).trim();
2328
+ if (n) names.add(n);
2329
+ }
2330
+ }
2331
+ m = line.match(/^const\s+([A-Za-z_$][\w$]*)\s*=\s*require/);
2332
+ if (m) names.add(m[1]);
2333
+ }
2334
+ return names;
2335
+ }
2079
2336
  function parseFragment(fragment) {
2080
2337
  if (!fragment) return { imports: [], configComments: [] };
2081
2338
  const lines = fragment.split("\n");
@@ -2125,7 +2382,7 @@ function collectDependencies(presets) {
2125
2382
  function hashConfig(content) {
2126
2383
  return createHash("sha256").update(content).digest("hex");
2127
2384
  }
2128
- function buildRules(presets, userOverrides) {
2385
+ function buildRules(presets) {
2129
2386
  const merged = {};
2130
2387
  for (const preset of presets) {
2131
2388
  const rules = preset.rules;
@@ -2133,11 +2390,30 @@ function buildRules(presets, userOverrides) {
2133
2390
  Object.assign(merged, rules);
2134
2391
  }
2135
2392
  }
2136
- if (userOverrides) {
2137
- Object.assign(merged, userOverrides);
2138
- }
2139
2393
  return merged;
2140
2394
  }
2395
+ function splitRulesByPlugin(rules, hasNextJs) {
2396
+ const reactHooks = {};
2397
+ const reactOrA11y = {};
2398
+ const importPlugin = {};
2399
+ const generic = {};
2400
+ for (const [key, value] of Object.entries(rules)) {
2401
+ if (key.startsWith("react-hooks/") || key.startsWith("react-compiler/")) {
2402
+ reactHooks[key] = value;
2403
+ } else if (key.startsWith("react/") || key.startsWith("jsx-a11y/")) {
2404
+ reactOrA11y[key] = value;
2405
+ } else if (key.startsWith("import/")) {
2406
+ if (hasNextJs) {
2407
+ importPlugin[key] = value;
2408
+ } else {
2409
+ generic[key] = value;
2410
+ }
2411
+ } else {
2412
+ generic[key] = value;
2413
+ }
2414
+ }
2415
+ return { reactHooks, reactOrA11y, importPlugin, generic };
2416
+ }
2141
2417
  function formatRules(rules, indent) {
2142
2418
  const entries = Object.entries(rules);
2143
2419
  if (entries.length === 0) return "{}";
@@ -2151,6 +2427,7 @@ ${indent}}`;
2151
2427
  }
2152
2428
  function generateEslintConfig(input) {
2153
2429
  const { presets, ruleOverrides, tsconfigRootDir, ignorePatterns } = input;
2430
+ const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
2154
2431
  const allImports = [];
2155
2432
  const allConfigComments = [];
2156
2433
  for (const preset of presets) {
@@ -2163,7 +2440,14 @@ function generateEslintConfig(input) {
2163
2440
  const dedupedImports = deduplicateImports(allImports);
2164
2441
  const tsCheck = dedupedImports.find((i) => i === "// @ts-check");
2165
2442
  const importLines = dedupedImports.filter((i) => i !== "// @ts-check");
2166
- const rules = buildRules(presets, ruleOverrides);
2443
+ const presetRules = buildRules(presets);
2444
+ const userOverrides = ruleOverrides ?? {};
2445
+ const presetRulesClean = Object.fromEntries(
2446
+ Object.entries(presetRules).filter(
2447
+ ([k]) => !Object.hasOwn(userOverrides, k)
2448
+ )
2449
+ );
2450
+ const splitRules = splitRulesByPlugin(presetRulesClean, hasNextJs);
2167
2451
  const defaultIgnores = [
2168
2452
  "eslint.config.mjs",
2169
2453
  "node_modules/**",
@@ -2174,7 +2458,24 @@ function generateEslintConfig(input) {
2174
2458
  const ignores = [.../* @__PURE__ */ new Set([...defaultIgnores, ...ignorePatterns ?? []])];
2175
2459
  const rootDir = tsconfigRootDir ?? "import.meta.dirname";
2176
2460
  const rootDirValue = rootDir === "import.meta.dirname" ? "import.meta.dirname" : `"${rootDir}"`;
2177
- const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
2461
+ const KNOWN_SYSTEM_PRESETS = [
2462
+ "base",
2463
+ "react",
2464
+ "nextjs",
2465
+ "node",
2466
+ "cli",
2467
+ "testing",
2468
+ "testing-react",
2469
+ "testing-e2e"
2470
+ ];
2471
+ for (const preset of presets) {
2472
+ if (preset.is_system && !KNOWN_SYSTEM_PRESETS.includes(preset.name) && !warnedPresetNames.has(preset.name)) {
2473
+ console.warn(
2474
+ `Unknown system preset: ${preset.name} \u2014 skipping structural emission`
2475
+ );
2476
+ warnedPresetNames.add(preset.name);
2477
+ }
2478
+ }
2178
2479
  const hasReact = presets.some((p) => p.is_system && p.name === "react");
2179
2480
  const hasNode = presets.some((p) => p.is_system && p.name === "node");
2180
2481
  const hasTesting = presets.some((p) => p.is_system && p.name === "testing");
@@ -2204,8 +2505,9 @@ function generateEslintConfig(input) {
2204
2505
  if ((hasNode || hasReact || hasNextJs) && !hasGlobalsImport) {
2205
2506
  sections.push('import globals from "globals";');
2206
2507
  }
2508
+ sections.push('import { defineConfig } from "eslint/config";');
2207
2509
  sections.push("");
2208
- sections.push("export default [");
2510
+ sections.push("export default defineConfig([");
2209
2511
  sections.push(` { ignores: ${JSON.stringify(ignores)} },`);
2210
2512
  sections.push("");
2211
2513
  const hasBase = presets.some((p) => p.is_system && p.name === "base");
@@ -2226,11 +2528,43 @@ function generateEslintConfig(input) {
2226
2528
  sections.push("");
2227
2529
  }
2228
2530
  if (hasNextJs) {
2531
+ const bindings = importedIdentifiers(importLines);
2532
+ const nextFusedRules = {
2533
+ ...splitRules.reactHooks,
2534
+ ...splitRules.importPlugin
2535
+ };
2229
2536
  sections.push(" // Next.js: Core Web Vitals + TypeScript");
2230
2537
  sections.push(" ...nextCoreWebVitals,");
2231
2538
  sections.push(" ...nextTypescript,");
2232
- sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
2233
- sections.push(' { plugins: { "react-compiler": reactCompiler } },');
2539
+ if (bindings.has("jsxA11y")) {
2540
+ sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
2541
+ } else {
2542
+ console.warn(
2543
+ "eslint-generator: skipping `jsxA11y.flatConfigs.strict.rules` emission \u2014 `jsxA11y` binding missing from nextjs preset config_fragment. See CHK-087 TASK-4/TASK-5."
2544
+ );
2545
+ }
2546
+ if (Object.keys(nextFusedRules).length > 0) {
2547
+ sections.push(" {");
2548
+ sections.push(' plugins: { "react-compiler": reactCompiler },');
2549
+ sections.push(` rules: ${formatRules(nextFusedRules, " ")},`);
2550
+ sections.push(" },");
2551
+ } else {
2552
+ sections.push(' { plugins: { "react-compiler": reactCompiler } },');
2553
+ }
2554
+ if (Object.keys(splitRules.reactOrA11y).length > 0) {
2555
+ if (bindings.has("react") && bindings.has("jsxA11y")) {
2556
+ sections.push(" {");
2557
+ sections.push(' plugins: { react, "jsx-a11y": jsxA11y },');
2558
+ sections.push(
2559
+ ` rules: ${formatRules(splitRules.reactOrA11y, " ")},`
2560
+ );
2561
+ sections.push(" },");
2562
+ } else {
2563
+ console.warn(
2564
+ "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."
2565
+ );
2566
+ }
2567
+ }
2234
2568
  sections.push("");
2235
2569
  }
2236
2570
  if (hasReact && !hasNextJs) {
@@ -2250,6 +2584,12 @@ function generateEslintConfig(input) {
2250
2584
  sections.push(" rules: {");
2251
2585
  sections.push(" ...react.configs.flat.recommended.rules,");
2252
2586
  sections.push(' ...react.configs.flat["jsx-runtime"].rules,');
2587
+ for (const [key, value] of Object.entries(splitRules.reactHooks)) {
2588
+ sections.push(` "${key}": ${JSON.stringify(value)},`);
2589
+ }
2590
+ for (const [key, value] of Object.entries(splitRules.reactOrA11y)) {
2591
+ sections.push(` "${key}": ${JSON.stringify(value)},`);
2592
+ }
2253
2593
  sections.push(" },");
2254
2594
  sections.push(" },");
2255
2595
  sections.push(" jsxA11y.flatConfigs.strict,");
@@ -2270,10 +2610,14 @@ function generateEslintConfig(input) {
2270
2610
  sections.push(" prettier,");
2271
2611
  sections.push("");
2272
2612
  }
2273
- if (Object.keys(rules).length > 0) {
2613
+ const overridesCombined = {
2614
+ ...splitRules.generic,
2615
+ ...userOverrides
2616
+ };
2617
+ if (Object.keys(overridesCombined).length > 0) {
2274
2618
  sections.push(" // Rule overrides");
2275
2619
  sections.push(" {");
2276
- sections.push(` rules: ${formatRules(rules, " ")},`);
2620
+ sections.push(` rules: ${formatRules(overridesCombined, " ")},`);
2277
2621
  sections.push(" },");
2278
2622
  sections.push("");
2279
2623
  }
@@ -2335,25 +2679,28 @@ function generateEslintConfig(input) {
2335
2679
  sections.push(" },");
2336
2680
  sections.push("");
2337
2681
  }
2338
- sections.push("];");
2682
+ sections.push("]);");
2339
2683
  sections.push("");
2340
2684
  return sections.join("\n");
2341
2685
  }
2686
+ var warnedPresetNames;
2342
2687
  var init_eslint_generator = __esm({
2343
2688
  "src/lib/eslint-generator.ts"() {
2344
2689
  "use strict";
2690
+ warnedPresetNames = /* @__PURE__ */ new Set();
2345
2691
  }
2346
2692
  });
2347
2693
 
2348
2694
  // src/cli/eslint.ts
2349
2695
  var eslint_exports = {};
2350
2696
  __export(eslint_exports, {
2697
+ autoDetectIgnorePatterns: () => autoDetectIgnorePatterns,
2351
2698
  checkEslintDrift: () => checkEslintDrift,
2352
2699
  eslintInit: () => eslintInit,
2353
2700
  eslintSync: () => eslintSync,
2354
2701
  runEslint: () => runEslint
2355
2702
  });
2356
- import { readFile as readFile8, writeFile as writeFile3, access as access2 } from "node:fs/promises";
2703
+ import { readFile as readFile8, writeFile as writeFile3, access as access2, readdir as readdir5 } from "node:fs/promises";
2357
2704
  import { join as join7, relative as relative2 } from "node:path";
2358
2705
  async function fileExists2(filePath) {
2359
2706
  try {
@@ -2363,6 +2710,46 @@ async function fileExists2(filePath) {
2363
2710
  return false;
2364
2711
  }
2365
2712
  }
2713
+ async function autoDetectIgnorePatterns(absPath) {
2714
+ const patterns = [];
2715
+ if (await fileExists2(join7(absPath, "esbuild.js"))) {
2716
+ patterns.push("esbuild.js");
2717
+ }
2718
+ let entries = [];
2719
+ try {
2720
+ entries = await readdir5(absPath);
2721
+ } catch (err) {
2722
+ console.error(
2723
+ ` autoDetectIgnorePatterns: failed to read ${absPath}: ${err instanceof Error ? err.message : String(err)}`
2724
+ );
2725
+ entries = [];
2726
+ }
2727
+ const esbuildVariant = /^esbuild\.[^.]+\.(mjs|cjs|js)$/;
2728
+ for (const name of entries) {
2729
+ if (esbuildVariant.test(name)) {
2730
+ patterns.push(name);
2731
+ }
2732
+ }
2733
+ for (const ext of ["ts", "mts", "js", "mjs"]) {
2734
+ const candidate = `vitest.config.${ext}`;
2735
+ if (await fileExists2(join7(absPath, candidate))) {
2736
+ patterns.push(candidate);
2737
+ break;
2738
+ }
2739
+ }
2740
+ for (const ext of ["ts", "mts", "js", "mjs"]) {
2741
+ const candidate = `vite.config.${ext}`;
2742
+ if (await fileExists2(join7(absPath, candidate))) {
2743
+ patterns.push(candidate);
2744
+ break;
2745
+ }
2746
+ }
2747
+ if (await fileExists2(join7(absPath, "tauri.conf.json"))) {
2748
+ patterns.push("src-tauri/**");
2749
+ patterns.push("**/*.d.ts");
2750
+ }
2751
+ return patterns;
2752
+ }
2366
2753
  function detectPackageManager(projectPath) {
2367
2754
  return (async () => {
2368
2755
  if (await fileExists2(join7(projectPath, "pnpm-lock.yaml"))) return "pnpm";
@@ -2393,11 +2780,13 @@ function buildInstallCommand(pm, packages, workspaceRoot) {
2393
2780
  return `npm install -D ${pkgStr}`;
2394
2781
  }
2395
2782
  }
2396
- async function resolvePresetsForTechStack(techNames) {
2783
+ async function resolvePresetsForTechStack(techNames, capabilities = []) {
2397
2784
  const techParam = techNames.join(",");
2398
- const res = await apiGet("/eslint-presets", {
2399
- tech_stack: techParam
2400
- });
2785
+ const query = { tech_stack: techParam };
2786
+ if (capabilities.length > 0) {
2787
+ query.capabilities = capabilities.join(",");
2788
+ }
2789
+ const res = await apiGet("/eslint-presets", query);
2401
2790
  return res.data ?? [];
2402
2791
  }
2403
2792
  async function eslintInit(repoId, projectPath) {
@@ -2432,19 +2821,27 @@ async function eslintInit(repoId, projectPath) {
2432
2821
  const allRequiredDeps = /* @__PURE__ */ new Map();
2433
2822
  const configsToWrite = [];
2434
2823
  for (const target of targets) {
2435
- const techNames = target.techStack.map((t) => t.name);
2824
+ const techNames = target.techStack.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
2825
+ const capabilities = collectCapabilities(target.techStack);
2436
2826
  console.log(
2437
2827
  ` ${target.name}: ${techNames.length > 0 ? techNames.join(", ") : "(no tech detected)"}`
2438
2828
  );
2439
- if (techNames.length === 0) {
2440
- console.log(` Skipping \u2014 no tech stack detected.
2441
- `);
2829
+ if (capabilities.length > 0) {
2830
+ console.log(` Capabilities: ${capabilities.join(", ")}`);
2831
+ }
2832
+ if (techNames.length === 0 && capabilities.length === 0) {
2833
+ console.log(
2834
+ ` Skipping ${target.name}: no tech or capabilities detected
2835
+ `
2836
+ );
2442
2837
  continue;
2443
2838
  }
2444
- const presets = await resolvePresetsForTechStack(techNames);
2839
+ const presets = await resolvePresetsForTechStack(techNames, capabilities);
2445
2840
  if (presets.length === 0) {
2446
- console.log(` No matching presets found.
2447
- `);
2841
+ console.log(
2842
+ ` No preset matches ${target.name}; skipping (override via --force-preset if needed)
2843
+ `
2844
+ );
2448
2845
  continue;
2449
2846
  }
2450
2847
  console.log(` Presets: ${presets.map((p) => p.name).join(", ")}`);
@@ -2465,9 +2862,14 @@ async function eslintInit(repoId, projectPath) {
2465
2862
  }
2466
2863
  } catch {
2467
2864
  }
2865
+ const detectedIgnores = await autoDetectIgnorePatterns(target.absPath);
2866
+ if (detectedIgnores.length > 0) {
2867
+ console.log(` Auto-ignore: ${detectedIgnores.join(", ")}`);
2868
+ }
2468
2869
  const content = generateEslintConfig({
2469
2870
  presets,
2470
- ruleOverrides: userOverrides
2871
+ ruleOverrides: userOverrides,
2872
+ ignorePatterns: detectedIgnores
2471
2873
  });
2472
2874
  const hash = hashConfig(content);
2473
2875
  const configPath = join7(target.absPath, "eslint.config.mjs");
@@ -2621,8 +3023,12 @@ async function eslintSync(repoId, projectPath) {
2621
3023
  const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
2622
3024
  const configPath = join7(absPath, "eslint.config.mjs");
2623
3025
  const detected = await detectTechStack(absPath);
2624
- const techNames = detected.flat.map((t) => t.name);
2625
- const currentPresets = await resolvePresetsForTechStack(techNames);
3026
+ const techNames = detected.flat.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
3027
+ const capabilities = collectCapabilities(detected.flat);
3028
+ const currentPresets = await resolvePresetsForTechStack(
3029
+ techNames,
3030
+ capabilities
3031
+ );
2626
3032
  const currentPresetIds = currentPresets.map((p) => p.id).sort();
2627
3033
  const savedPresetIds = [...config.active_preset_ids ?? []].sort();
2628
3034
  const presetsChanged = currentPresetIds.length !== savedPresetIds.length || currentPresetIds.some((id) => !savedPresetIds.includes(id));
@@ -2651,11 +3057,15 @@ async function eslintSync(repoId, projectPath) {
2651
3057
  );
2652
3058
  }
2653
3059
  }
2654
- console.log(` ${config.source_path}: presets changed, regenerating...`);
3060
+ if (presetsChanged) {
3061
+ console.log(` ${config.source_path}: presets changed, regenerating...`);
3062
+ }
2655
3063
  const userOverrides = config.rule_overrides;
3064
+ const detectedIgnores = await autoDetectIgnorePatterns(absPath);
2656
3065
  const content = generateEslintConfig({
2657
3066
  presets: currentPresets,
2658
- ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0
3067
+ ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0,
3068
+ ignorePatterns: detectedIgnores
2659
3069
  });
2660
3070
  try {
2661
3071
  await writeFile3(configPath, content, "utf-8");
@@ -2823,7 +3233,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
2823
3233
  let localFiles = /* @__PURE__ */ new Map();
2824
3234
  try {
2825
3235
  localFiles = await scanLocalFiles(claudeDir, projectPath);
2826
- } catch {
3236
+ } catch (err) {
3237
+ console.warn(
3238
+ ` Local file scan incomplete: ${err instanceof Error ? err.message : String(err)}`
3239
+ );
2827
3240
  }
2828
3241
  const [defaultsRes, repoSyncRes, repoRes, , fileReposRes] = await Promise.all(
2829
3242
  [
@@ -3142,7 +3555,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
3142
3555
  repo_id: repoId,
3143
3556
  file_repos: fileRepoUpdates
3144
3557
  });
3145
- } catch {
3558
+ } catch (err) {
3559
+ console.warn(
3560
+ ` Warning: failed to update file-repo tracking for ${fileRepoUpdates.length} files: ${err instanceof Error ? err.message : String(err)}`
3561
+ );
3146
3562
  }
3147
3563
  }
3148
3564
  console.log(
@@ -3298,7 +3714,10 @@ async function syncConfig(repoId, projectPath, dryRun) {
3298
3714
  }
3299
3715
  return clean;
3300
3716
  });
3301
- } catch {
3717
+ } catch (err) {
3718
+ console.warn(
3719
+ ` Warning: failed to fetch port allocations: ${err instanceof Error ? err.message : String(err)}`
3720
+ );
3302
3721
  }
3303
3722
  const worktreeId = currentConfig.worktree_id;
3304
3723
  const matchingAlloc = portAllocations[0];
@@ -3363,8 +3782,10 @@ async function syncTechStack(repoId, projectPath, dryRun) {
3363
3782
  }
3364
3783
  }
3365
3784
  }
3366
- } catch {
3367
- console.log(" Tech stack detection skipped.");
3785
+ } catch (err) {
3786
+ console.warn(
3787
+ ` Tech stack detection skipped: ${err instanceof Error ? err.message : String(err)}`
3788
+ );
3368
3789
  }
3369
3790
  }
3370
3791
  async function syncEslintDriftCheck(repoId, projectPath) {
@@ -3442,8 +3863,10 @@ async function syncPortVerification(repoId, projectPath, dryRun, fix) {
3442
3863
  if (mismatches.length === 0 && unallocated.length === 0) {
3443
3864
  console.log(" Ports verified.");
3444
3865
  }
3445
- } catch {
3446
- console.log(" Port verification skipped.");
3866
+ } catch (err) {
3867
+ console.warn(
3868
+ ` Port verification skipped: ${err instanceof Error ? err.message : String(err)}`
3869
+ );
3447
3870
  }
3448
3871
  }
3449
3872
  function groupByType(items) {
@@ -3498,7 +3921,7 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
3498
3921
  }
3499
3922
  function getSyncVersion() {
3500
3923
  try {
3501
- return "1.4.1";
3924
+ return "1.4.2";
3502
3925
  } catch {
3503
3926
  return "unknown";
3504
3927
  }
@@ -3554,68 +3977,69 @@ var init_sync = __esm({
3554
3977
  // src/index.ts
3555
3978
  init_version();
3556
3979
  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;
3980
+ import { resolve as resolve2 } from "node:path";
3981
+ void (async () => {
3982
+ if (!process.env.CODEBYPLAN_API_KEY) {
3983
+ try {
3984
+ const envPath = resolve2(process.cwd(), ".env.local");
3985
+ const content = readFileSync(envPath, "utf-8");
3986
+ for (const line of content.split("\n")) {
3987
+ const trimmed = line.trim();
3988
+ if (!trimmed || trimmed.startsWith("#")) continue;
3989
+ const eq = trimmed.indexOf("=");
3990
+ if (eq === -1) continue;
3991
+ const key = trimmed.slice(0, eq).trim();
3992
+ const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
3993
+ if (!process.env[key]) process.env[key] = val;
3994
+ }
3995
+ } catch {
3570
3996
  }
3571
- } catch {
3572
3997
  }
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);
3998
+ if (process.env.CODEBYPLAN_API_KEY?.startsWith("CODEBYPLAN_API_KEY=")) {
3999
+ process.env.CODEBYPLAN_API_KEY = process.env.CODEBYPLAN_API_KEY.slice(
4000
+ "CODEBYPLAN_API_KEY=".length
4001
+ );
4002
+ }
4003
+ const arg = process.argv[2];
4004
+ if (arg === "--version" || arg === "-v") {
4005
+ console.log(VERSION);
4006
+ process.exit(0);
4007
+ }
4008
+ if (arg === "setup") {
4009
+ const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
4010
+ await runSetup2();
4011
+ process.exit(0);
4012
+ }
4013
+ if (arg === "sync") {
4014
+ const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
4015
+ const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
4016
+ try {
4017
+ await runSync2();
4018
+ } catch (err) {
4019
+ if (err instanceof SyncCancelledError2) {
4020
+ console.log("\n Sync cancelled.\n");
4021
+ process.exit(0);
4022
+ }
4023
+ throw err;
3598
4024
  }
3599
- throw err;
4025
+ process.exit(0);
3600
4026
  }
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);
4027
+ if (arg === "eslint") {
4028
+ const { runEslint: runEslint2 } = await Promise.resolve().then(() => (init_eslint(), eslint_exports));
4029
+ const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
4030
+ try {
4031
+ await runEslint2();
4032
+ } catch (err) {
4033
+ if (err instanceof SyncCancelledError2) {
4034
+ console.log("\n Cancelled.\n");
4035
+ process.exit(0);
4036
+ }
4037
+ throw err;
3612
4038
  }
3613
- throw err;
4039
+ process.exit(0);
3614
4040
  }
3615
- process.exit(0);
3616
- }
3617
- if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
3618
- console.log(`
4041
+ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
4042
+ console.log(`
3619
4043
  CodeByPlan CLI v${VERSION}
3620
4044
 
3621
4045
  Usage:
@@ -3638,13 +4062,14 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
3638
4062
 
3639
4063
  MCP Server:
3640
4064
  Claude Code connects to CodeByPlan via remote MCP:
3641
- URL: https://codebyplan.com/mcp
4065
+ URL: https://www.codebyplan.com/mcp
3642
4066
  Auth: x-api-key header (configured during setup)
3643
4067
 
3644
4068
  Learn more: https://codebyplan.com
3645
4069
  `);
3646
- process.exit(0);
3647
- }
3648
- console.error(`Unknown command: ${arg}`);
3649
- console.error("Run 'codebyplan help' for usage.");
3650
- process.exit(1);
4070
+ process.exit(0);
4071
+ }
4072
+ console.error(`Unknown command: ${arg}`);
4073
+ console.error("Run 'codebyplan help' for usage.");
4074
+ process.exit(1);
4075
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
- "build": "tsc",
14
+ "build": "tsc --project tsconfig.build.json",
15
15
  "build:npm": "node esbuild.npm.mjs",
16
16
  "prepublishOnly": "npm run build:npm",
17
17
  "lint": "eslint",
@@ -47,7 +47,7 @@
47
47
  "@eslint/js": "^9.18.0",
48
48
  "@types/node": "^20",
49
49
  "@vitest/eslint-plugin": "^1.1.44",
50
- "esbuild": "^0.25",
50
+ "esbuild": "^0.28",
51
51
  "eslint": "^9.18.0",
52
52
  "eslint-config-prettier": "^10.0.1",
53
53
  "eslint-plugin-no-secrets": "^2.2.1",