sticky-note-cli 3.1.41 → 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
|
-
|
|
1456
|
-
let gitattrsContent = fs.
|
|
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
|
@@ -237,22 +237,28 @@ function pushDataBranch(remote, branchName, maxRetries, localMemPath, loadJsonFn
|
|
|
237
237
|
).trim();
|
|
238
238
|
|
|
239
239
|
// Build merged file map: start with remote files as base, then
|
|
240
|
-
// overlay local files so this user's audit/presence are preserved
|
|
240
|
+
// overlay local files so this user's audit/presence are preserved.
|
|
241
|
+
// Capture remote sticky-note.json BEFORE the local overlay so we
|
|
242
|
+
// have the actual remote content to merge (not the local overwrite).
|
|
241
243
|
const fileMap = {};
|
|
242
244
|
const localRef = "refs/heads/" + branchName;
|
|
245
|
+
let remoteStickyContent = null;
|
|
243
246
|
for (const f of listFilesInBranch(fetchResult.remoteRef)) {
|
|
244
247
|
const content = readFileFromBranch(fetchResult.remoteRef, f);
|
|
245
|
-
if (content !== null)
|
|
248
|
+
if (content !== null) {
|
|
249
|
+
fileMap[f] = content;
|
|
250
|
+
if (f === "sticky-note.json") remoteStickyContent = content;
|
|
251
|
+
}
|
|
246
252
|
}
|
|
247
253
|
for (const f of listFilesInBranch(localRef)) {
|
|
248
254
|
const content = readFileFromBranch(localRef, f);
|
|
249
255
|
if (content !== null) fileMap[f] = content;
|
|
250
256
|
}
|
|
251
257
|
|
|
252
|
-
// Merge sticky-note.json from both sides
|
|
253
|
-
if (localMemPath &&
|
|
258
|
+
// Merge sticky-note.json from both sides using captured remote content
|
|
259
|
+
if (localMemPath && remoteStickyContent) {
|
|
254
260
|
mergeAndSaveFromRemote(
|
|
255
|
-
localMemPath,
|
|
261
|
+
localMemPath, remoteStickyContent, loadJsonFn, saveJsonFn
|
|
256
262
|
);
|
|
257
263
|
fileMap["sticky-note.json"] = fs.readFileSync(localMemPath, "utf-8");
|
|
258
264
|
}
|
|
@@ -263,9 +269,12 @@ function pushDataBranch(remote, branchName, maxRetries, localMemPath, loadJsonFn
|
|
|
263
269
|
}
|
|
264
270
|
} catch (_) {}
|
|
265
271
|
|
|
266
|
-
// Brief exponential backoff
|
|
267
|
-
|
|
268
|
-
|
|
272
|
+
// Brief exponential backoff — use Atomics.wait to block without CPU spin
|
|
273
|
+
try {
|
|
274
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.pow(2, attempt) * 300);
|
|
275
|
+
} catch (_) {
|
|
276
|
+
// Atomics.wait unavailable (e.g. SharedArrayBuffer disabled) — skip delay
|
|
277
|
+
}
|
|
269
278
|
}
|
|
270
279
|
}
|
|
271
280
|
return { ok: false, error: "max retries exceeded" };
|
|
@@ -313,9 +322,16 @@ function mergeAndSaveFromRemote(localMemPath, remoteContent, loadJsonFn, saveJso
|
|
|
313
322
|
return; // corrupt remote — skip
|
|
314
323
|
}
|
|
315
324
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
let localMemory;
|
|
326
|
+
if (loadJsonFn) {
|
|
327
|
+
localMemory = loadJsonFn(localMemPath, { ...EMPTY_MEMORY });
|
|
328
|
+
} else {
|
|
329
|
+
try {
|
|
330
|
+
localMemory = JSON.parse(fs.readFileSync(localMemPath, "utf-8"));
|
|
331
|
+
} catch (_) {
|
|
332
|
+
localMemory = { ...EMPTY_MEMORY };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
319
335
|
|
|
320
336
|
const localThreads = Array.isArray(localMemory.threads) ? localMemory.threads.filter(Boolean) : [];
|
|
321
337
|
const remoteThreads = Array.isArray(remoteMemory.threads) ? remoteMemory.threads.filter(Boolean) : [];
|
|
@@ -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
|
-
|
|
980
|
-
"
|
|
981
|
-
|
|
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
|
-
|
|
987
|
-
|
|
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:
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 : {};
|
|
@@ -644,7 +658,6 @@ function getUser() {
|
|
|
644
658
|
// Prefer git config user.name — consistent across machines regardless of OS login name.
|
|
645
659
|
// Fall back to OS env vars, then "unknown".
|
|
646
660
|
try {
|
|
647
|
-
const { execFileSync } = require("child_process");
|
|
648
661
|
const name = execFileSync("git", ["config", "user.name"], {
|
|
649
662
|
encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"],
|
|
650
663
|
}).trim();
|
|
@@ -1281,6 +1294,7 @@ function getCloudConfig() {
|
|
|
1281
1294
|
return {
|
|
1282
1295
|
url: process.env.STICKY_URL || envFile.STICKY_URL || "",
|
|
1283
1296
|
apiKey: process.env.STICKY_API_KEY || envFile.STICKY_API_KEY || "",
|
|
1297
|
+
pushToken: process.env.STICKY_PUSH_TOKEN || envFile.STICKY_PUSH_TOKEN || "",
|
|
1284
1298
|
};
|
|
1285
1299
|
}
|
|
1286
1300
|
|