@codebyplan/cli 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +50 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -37,7 +37,7 @@ var VERSION, PACKAGE_NAME;
37
37
  var init_version = __esm({
38
38
  "src/lib/version.ts"() {
39
39
  "use strict";
40
- VERSION = "3.3.0";
40
+ VERSION = "3.4.0";
41
41
  PACKAGE_NAME = "@codebyplan/cli";
42
42
  }
43
43
  });
@@ -1751,8 +1751,12 @@ var sync_exports = {};
1751
1751
  __export(sync_exports, {
1752
1752
  runSync: () => runSync
1753
1753
  });
1754
+ import { createHash } from "node:crypto";
1754
1755
  import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
1755
1756
  import { join as join7, dirname as dirname2 } from "node:path";
1757
+ function contentHash(content) {
1758
+ return createHash("sha256").update(content).digest("hex");
1759
+ }
1756
1760
  async function runSync() {
1757
1761
  const flags = parseFlags(3);
1758
1762
  const dryRun = hasFlag("dry-run", 3);
@@ -1817,17 +1821,27 @@ async function runSyncInner(repoId, projectPath, dryRun, force) {
1817
1821
  localFiles = await scanLocalFiles(claudeDir, projectPath);
1818
1822
  } catch {
1819
1823
  }
1820
- const [defaultsRes, repoSyncRes, repoRes, syncStateRes] = await Promise.all([
1824
+ const [defaultsRes, repoSyncRes, repoRes, syncStateRes, fileReposRes] = await Promise.all([
1821
1825
  apiGet("/sync/defaults"),
1822
1826
  apiGet("/sync/files", { repo_id: repoId }),
1823
1827
  apiGet(`/repos/${repoId}`),
1824
- apiGet("/sync/state", { repo_id: repoId })
1828
+ apiGet("/sync/state", { repo_id: repoId }),
1829
+ apiGet("/sync/file-repos", { repo_id: repoId })
1825
1830
  ]);
1826
1831
  const syncStartTime = Date.now();
1827
1832
  const repoData = repoRes.data;
1828
1833
  const remoteDefaults = flattenSyncData(defaultsRes.data);
1829
1834
  const remoteRepoFiles = flattenSyncData(repoSyncRes.data);
1830
1835
  const syncState = syncStateRes.data;
1836
+ const fileRepoHashes = /* @__PURE__ */ new Map();
1837
+ const fileRepoByClaudeFileId = /* @__PURE__ */ new Map();
1838
+ for (const entry of fileReposRes.data ?? []) {
1839
+ if (entry.claude_files) {
1840
+ const key = compositeKey(entry.claude_files.type, entry.claude_files.name, entry.claude_files.category);
1841
+ fileRepoHashes.set(key, entry.last_synced_content_hash);
1842
+ }
1843
+ fileRepoByClaudeFileId.set(entry.claude_file_id, entry.last_synced_content_hash);
1844
+ }
1831
1845
  const remoteFiles = new Map([...remoteDefaults, ...remoteRepoFiles]);
1832
1846
  console.log(` Local: ${localFiles.size} files, Remote: ${remoteFiles.size} files
1833
1847
  `);
@@ -1854,9 +1868,8 @@ async function runSyncInner(repoId, projectPath, dryRun, force) {
1854
1868
  });
1855
1869
  } else if (!local && remote) {
1856
1870
  const resolvedContent = substituteVariables(remote.content, repoData);
1857
- const isDefaultOnly = remoteDefaults.has(key) && !remoteRepoFiles.has(key);
1858
- const hasSyncedBefore = syncState?.last_synced_at != null;
1859
- const recommended = !isDefaultOnly && hasSyncedBefore ? "delete" : "pull";
1871
+ const hadSyncedThisFile = remote.id ? fileRepoByClaudeFileId.has(remote.id) : fileRepoHashes.has(key);
1872
+ const recommended = hadSyncedThisFile ? "delete" : "pull";
1860
1873
  plan.push({
1861
1874
  key,
1862
1875
  displayPath: `${remote.type}/${remote.category ? remote.category + "/" : ""}${remote.name}`,
@@ -1877,16 +1890,24 @@ async function runSyncInner(repoId, projectPath, dryRun, force) {
1877
1890
  if (local.content === resolvedRemote) {
1878
1891
  continue;
1879
1892
  }
1880
- const lastSyncedAt = syncState?.last_synced_at;
1881
- const remoteUpdatedAt = remote.updated_at;
1882
- const remoteChanged = remoteUpdatedAt && lastSyncedAt ? new Date(remoteUpdatedAt) > new Date(lastSyncedAt) : true;
1893
+ const localHash = contentHash(local.content);
1894
+ const lastSyncedHash = fileRepoHashes.get(key) ?? null;
1895
+ const localChanged = lastSyncedHash ? localHash !== lastSyncedHash : true;
1883
1896
  let action;
1884
- if (remoteChanged && force) {
1897
+ if (force) {
1885
1898
  action = "pull";
1886
- } else if (!remoteChanged) {
1887
- action = "push";
1888
- } else {
1899
+ } else if (!localChanged) {
1900
+ action = "pull";
1901
+ } else if (lastSyncedHash === null) {
1889
1902
  action = "conflict";
1903
+ } else {
1904
+ const remoteDbHash = remote.content_hash ?? null;
1905
+ const remoteChanged = remoteDbHash ? remoteDbHash !== lastSyncedHash : true;
1906
+ if (remoteChanged) {
1907
+ action = "conflict";
1908
+ } else {
1909
+ action = "push";
1910
+ }
1890
1911
  }
1891
1912
  plan.push({
1892
1913
  key,
@@ -2062,11 +2083,22 @@ async function runSyncInner(repoId, projectPath, dryRun, force) {
2062
2083
  });
2063
2084
  const syncTimestamp = (/* @__PURE__ */ new Date()).toISOString();
2064
2085
  const fileRepoUpdates = [];
2065
- for (const p of [...toPull, ...toPush]) {
2066
- if (p.claudeFileId) {
2086
+ for (const p of toPull) {
2087
+ if (p.claudeFileId && p.remoteContent !== null) {
2088
+ fileRepoUpdates.push({
2089
+ claude_file_id: p.claudeFileId,
2090
+ last_synced_at: syncTimestamp,
2091
+ last_synced_content_hash: contentHash(p.remoteContent),
2092
+ sync_status: "synced"
2093
+ });
2094
+ }
2095
+ }
2096
+ for (const p of toPush) {
2097
+ if (p.claudeFileId && p.localContent !== null) {
2067
2098
  fileRepoUpdates.push({
2068
2099
  claude_file_id: p.claudeFileId,
2069
2100
  last_synced_at: syncTimestamp,
2101
+ last_synced_content_hash: contentHash(p.localContent),
2070
2102
  sync_status: "synced"
2071
2103
  });
2072
2104
  }
@@ -2263,7 +2295,7 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
2263
2295
  }
2264
2296
  function getSyncVersion() {
2265
2297
  try {
2266
- return "3.3.0";
2298
+ return "3.4.0";
2267
2299
  } catch {
2268
2300
  return "unknown";
2269
2301
  }
@@ -2289,7 +2321,8 @@ function flattenSyncData(data) {
2289
2321
  name: file.name,
2290
2322
  content: file.content,
2291
2323
  category: file.category,
2292
- updated_at: file.updated_at
2324
+ updated_at: file.updated_at,
2325
+ content_hash: file.content_hash
2293
2326
  });
2294
2327
  }
2295
2328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebyplan/cli",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "MCP server for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {