sticky-note-cli 3.1.42 → 3.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1071,10 +1071,11 @@ async function cmdInit() {
1071
1071
  print(" ⏭️ .gitignore already configured");
1072
1072
  }
1073
1073
 
1074
- // Add .gitattributes merge strategy
1074
+ // Add .gitattributes merge strategy for both thread data and config
1075
1075
  const gitattrsPath = path.join(process.cwd(), ".gitattributes");
1076
1076
  const oldMergeRule = ".sticky-note/sticky-note.json merge=union";
1077
1077
  const mergeRule = ".sticky-note/sticky-note.json merge=sticky-note";
1078
+ const configMergeRule = ".sticky-note/sticky-note-config.json merge=sticky-note";
1078
1079
  let gitattrsContent = "";
1079
1080
  if (fs.existsSync(gitattrsPath)) {
1080
1081
  gitattrsContent = fs.readFileSync(gitattrsPath, "utf-8");
@@ -1089,10 +1090,18 @@ async function cmdInit() {
1089
1090
  ? `\n# Sticky Note - JSON-aware thread merge\n${mergeRule}\n`
1090
1091
  : `\n\n# Sticky Note - JSON-aware thread merge\n${mergeRule}\n`;
1091
1092
  fs.appendFileSync(gitattrsPath, addition);
1093
+ gitattrsContent += addition;
1092
1094
  print(" [OK] .gitattributes updated (merge=sticky-note)");
1093
1095
  } else {
1094
1096
  print(" ⏭️ .gitattributes already configured");
1095
1097
  }
1098
+ // Also protect sticky-note-config.json — merge conflicts here silently break
1099
+ // cloud transport and hook loading for the entire team.
1100
+ if (!gitattrsContent.includes(configMergeRule)) {
1101
+ const addition = `${configMergeRule}\n`;
1102
+ fs.appendFileSync(gitattrsPath, addition);
1103
+ print(" [OK] .gitattributes: sticky-note-config.json protected from merge conflicts");
1104
+ }
1096
1105
 
1097
1106
  // Install merge driver script and git config
1098
1107
  const mergeDriverSrc = path.join(TEMPLATES_DIR, "merge-driver.js");
@@ -1452,14 +1461,25 @@ async function cmdUpdate() {
1452
1461
  }
1453
1462
  }
1454
1463
 
1455
- if (fs.existsSync(gitattrsPath)) {
1456
- let gitattrsContent = fs.readFileSync(gitattrsPath, "utf-8");
1464
+ {
1465
+ let gitattrsContent = fs.existsSync(gitattrsPath)
1466
+ ? fs.readFileSync(gitattrsPath, "utf-8") : "";
1457
1467
  const oldRule = ".sticky-note/sticky-note.json merge=union";
1458
1468
  const newRule = ".sticky-note/sticky-note.json merge=sticky-note";
1469
+ const configRule = ".sticky-note/sticky-note-config.json merge=sticky-note";
1459
1470
  if (gitattrsContent.includes(oldRule)) {
1460
1471
  gitattrsContent = gitattrsContent.replace(oldRule, newRule);
1461
1472
  fs.writeFileSync(gitattrsPath, gitattrsContent, "utf-8");
1462
1473
  print(" [OK] .gitattributes upgraded (merge=union -> merge=sticky-note)");
1474
+ } else if (!gitattrsContent.includes(newRule)) {
1475
+ const addition = `\n# Sticky Note - JSON-aware thread merge\n${newRule}\n`;
1476
+ fs.appendFileSync(gitattrsPath, addition);
1477
+ gitattrsContent += addition;
1478
+ print(" [OK] .gitattributes updated (merge=sticky-note)");
1479
+ }
1480
+ if (!gitattrsContent.includes(configRule)) {
1481
+ fs.appendFileSync(gitattrsPath, `${configRule}\n`);
1482
+ print(" [OK] .gitattributes: sticky-note-config.json protected from merge conflicts");
1463
1483
  }
1464
1484
  }
1465
1485
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sticky-note-cli",
3
- "version": "3.1.42",
3
+ "version": "3.1.43",
4
4
  "description": "Human-to-human handoff for AI coding assistants. Git-backed shared memory for Claude Code and Copilot CLI.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -954,7 +954,7 @@ function _collectDirFiles(dir, ext, prefix, fileMap) {
954
954
  }
955
955
 
956
956
  function commitAndPushDataBranch() {
957
- if (!dataBranch) return;
957
+ if (!dataBranch) return null;
958
958
  try {
959
959
  const fileMap = {};
960
960
  const memPath = getMemoryPath();
@@ -966,7 +966,7 @@ function commitAndPushDataBranch() {
966
966
  _collectDirFiles(getPresenceDir(), ".json", "presence/", fileMap);
967
967
  _collectDirFiles(getTranscriptsDir(), ".jsonl", "transcripts/", fileMap);
968
968
 
969
- if (Object.keys(fileMap).length === 0) return;
969
+ if (Object.keys(fileMap).length === 0) return null;
970
970
 
971
971
  dataBranch.commitFilesToBranch(dataBranch.DATA_BRANCH, fileMap);
972
972
 
@@ -976,16 +976,18 @@ function commitAndPushDataBranch() {
976
976
  remote, dataBranch.DATA_BRANCH, 3, getMemoryPath(), loadJson, saveJson
977
977
  );
978
978
  if (!pushResult.ok) {
979
- process.stderr.write(
980
- "[STICKY-NOTE] warning: failed to push data branch — " + pushResult.error + "\n" +
981
- "[STICKY-NOTE] your data is safe locally in .git/sticky-note/\n"
982
- );
979
+ const hint = pushResult.error && pushResult.error.includes("authentication")
980
+ ? " Add STICKY_PUSH_TOKEN=<github_pat> to .env.sticky to fix."
981
+ : "";
982
+ const msg = `[STICKY-NOTE] ⚠️ data branch push failed — ${pushResult.error}${hint}`;
983
+ logHookError("data-branch-push", new Error(pushResult.error));
984
+ return msg;
983
985
  }
984
986
  }
987
+ return null;
985
988
  } catch (err) {
986
- process.stderr.write(
987
- "[STICKY-NOTE] warning: data branch sync failed — " + err.message + "\n"
988
- );
989
+ logHookError("data-branch-sync", err);
990
+ return `[STICKY-NOTE] ⚠️ data branch sync failed — ${err.message}`;
989
991
  }
990
992
  }
991
993
 
@@ -1306,7 +1308,9 @@ async function main() {
1306
1308
  if (cloud) {
1307
1309
  const threadToSync = threads.find(t => t.session_id === sessionId);
1308
1310
  if (threadToSync) {
1309
- cloudWriteThread(threadToSync).catch(() => {});
1311
+ cloudWriteThread(threadToSync).catch(err => {
1312
+ logHookError("session-end:cloud-write", err);
1313
+ });
1310
1314
  }
1311
1315
  }
1312
1316
 
@@ -1320,14 +1324,15 @@ async function main() {
1320
1324
  }
1321
1325
 
1322
1326
  // Commit current data to sticky-note/data branch and push
1323
- commitAndPushDataBranch();
1327
+ const dataBranchWarning = commitAndPushDataBranch();
1324
1328
 
1325
1329
  const fileCount = filesTouched.length;
1326
1330
  const commitCount = commitShas.length;
1327
1331
  const statusLabel = isCopilotCli ? "updated" : "closed";
1328
1332
  const statusMsg = `[STICKY-NOTE] Session ${statusLabel} - thread ${isCopilotCli ? "updated" : "created"} (${fileCount} file${fileCount !== 1 ? "s" : ""}${commitCount > 0 ? ", " + commitCount + " commit" + (commitCount !== 1 ? "s" : "") : ""})`;
1333
+ const fullOutput = dataBranchWarning ? `${statusMsg}\n${dataBranchWarning}` : statusMsg;
1329
1334
  try {
1330
- process.stdout.write(JSON.stringify({ output: statusMsg }) + "\n");
1335
+ process.stdout.write(JSON.stringify({ output: fullOutput }) + "\n");
1331
1336
  } catch (_) {
1332
1337
  process.stdout.write('{"output":""}\n');
1333
1338
  }
@@ -69,15 +69,38 @@ function _installedCliVersion() {
69
69
  }
70
70
  function formatVersionWarning(config) {
71
71
  try {
72
- const min = config && config.min_version;
73
- if (!min) return "";
74
72
  const installed = _installedCliVersion();
75
73
  if (!installed) return "";
76
- if (_cmpSemver(installed, min) >= 0) return "";
77
- const msg =
78
- `[STICKY-NOTE] ⚠️ Installed sticky-note-cli v${installed} is below ` +
79
- `this project's min_version v${min}. Run \`npm i -D sticky-note-cli@latest\` ` +
80
- `to update — features and schema may differ until you do.`;
74
+ const warnings = [];
75
+
76
+ // Check 1: installed version is below the project's required minimum.
77
+ const min = config && config.min_version;
78
+ if (min && _cmpSemver(installed, min) < 0) {
79
+ warnings.push(
80
+ `[STICKY-NOTE] ⚠️ sticky-note-cli v${installed} is below this project's ` +
81
+ `min_version v${min}. Run \`npm i -g sticky-note-cli@latest\` to update — ` +
82
+ `features and bug fixes are missing until you do.`
83
+ );
84
+ }
85
+
86
+ // Check 2: hook scripts in .claude/hooks/ are older than the installed CLI.
87
+ // This means the teammate installed a newer CLI but never ran `npx sticky-note update`
88
+ // to refresh the hook files — they're running old code with new expectations.
89
+ const hookVer = config && config.hook_version;
90
+ if (hookVer && _cmpSemver(installed, hookVer) > 0) {
91
+ const iv = _parseSemver(installed), hv = _parseSemver(hookVer);
92
+ // Warn on any minor or major gap — patch-only differences are usually safe.
93
+ if (iv && hv && (iv[0] !== hv[0] || iv[1] !== hv[1])) {
94
+ warnings.push(
95
+ `[STICKY-NOTE] ⚠️ Hook scripts are v${hookVer} but sticky-note-cli v${installed} ` +
96
+ `is installed. Run \`npx sticky-note update\` to get the latest fixes ` +
97
+ `(data branch push, identity, credential handling).`
98
+ );
99
+ }
100
+ }
101
+
102
+ if (warnings.length === 0) return "";
103
+ const msg = warnings.join("\n");
81
104
  try { process.stderr.write(msg + "\n"); } catch (_) { /* non-fatal */ }
82
105
  return msg;
83
106
  } catch (_) {
@@ -450,6 +450,20 @@ function clearResumeSignal() {
450
450
  function loadJson(filePath, defaultVal) {
451
451
  try {
452
452
  const raw = fs.readFileSync(filePath, "utf-8");
453
+ // Detect unresolved git merge conflict markers — JSON.parse would silently
454
+ // swallow the SyntaxError and return the default, hiding the real problem.
455
+ // This breaks useCloud(), thread loading, and config reading with no trace.
456
+ if (raw.includes("<<<<<<<") || raw.includes(">>>>>>>")) {
457
+ logHookError(
458
+ "loadJson",
459
+ new Error(
460
+ `Unresolved git merge conflict in ${filePath}. ` +
461
+ `Resolve the conflict and commit, or restore the file with ` +
462
+ `\`git checkout HEAD -- ${path.relative(process.cwd(), filePath) || filePath}\``
463
+ )
464
+ );
465
+ return defaultVal !== undefined ? defaultVal : {};
466
+ }
453
467
  return JSON.parse(raw);
454
468
  } catch (_) {
455
469
  return defaultVal !== undefined ? defaultVal : {};