sticky-note-cli 3.1.42 → 3.1.44
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 +59 -3
- package/package.json +1 -1
- package/templates/hooks/session-end.js +17 -12
- package/templates/hooks/session-start.js +30 -7
- package/templates/hooks/sticky-utils.js +14 -0
package/bin/cli.js
CHANGED
|
@@ -744,6 +744,29 @@ async function cmdInit() {
|
|
|
744
744
|
v3Mode ? "y" : "N"
|
|
745
745
|
);
|
|
746
746
|
wantsCloud = cloudAnswer.toLowerCase() === "y" || cloudAnswer.toLowerCase() === "yes";
|
|
747
|
+
|
|
748
|
+
// Always ask for STICKY_PUSH_TOKEN — needed for data branch push from hook
|
|
749
|
+
// processes regardless of whether cloud backend is configured.
|
|
750
|
+
print("\n A GitHub Personal Access Token (PAT) is needed so sticky-note can push");
|
|
751
|
+
print(" thread data in the background without requiring interactive credential access.");
|
|
752
|
+
print(" Create one at https://github.com/settings/tokens with 'repo' scope (or");
|
|
753
|
+
print(" 'contents: write' for fine-grained tokens).");
|
|
754
|
+
const pushTokenAnswer = await ask(rl, "Enter your STICKY_PUSH_TOKEN (GitHub PAT, leave blank to skip)", "");
|
|
755
|
+
if (pushTokenAnswer.trim()) {
|
|
756
|
+
const envStickyPath = path.join(process.cwd(), ".env.sticky");
|
|
757
|
+
let envContent = "";
|
|
758
|
+
try { envContent = fs.readFileSync(envStickyPath, "utf-8"); } catch (_) {}
|
|
759
|
+
if (!envContent.endsWith("\n") && envContent.length > 0) envContent += "\n";
|
|
760
|
+
if (!/^STICKY_PUSH_TOKEN=/m.test(envContent)) {
|
|
761
|
+
envContent += `STICKY_PUSH_TOKEN=${pushTokenAnswer.trim()}\n`;
|
|
762
|
+
} else {
|
|
763
|
+
envContent = envContent.replace(/^STICKY_PUSH_TOKEN=.+$/m, `STICKY_PUSH_TOKEN=${pushTokenAnswer.trim()}`);
|
|
764
|
+
}
|
|
765
|
+
fs.writeFileSync(envStickyPath, envContent, "utf-8");
|
|
766
|
+
print(" [OK] STICKY_PUSH_TOKEN written to .env.sticky");
|
|
767
|
+
} else {
|
|
768
|
+
print(" ⏭️ No PAT entered — data branch push will rely on system credential helper");
|
|
769
|
+
}
|
|
747
770
|
}
|
|
748
771
|
|
|
749
772
|
rl.close();
|
|
@@ -877,6 +900,19 @@ async function cmdInit() {
|
|
|
877
900
|
print(` wrangler secret put GITHUB_REPO # value: ${repo.trim()}`);
|
|
878
901
|
}
|
|
879
902
|
}
|
|
903
|
+
// Also write STICKY_PUSH_TOKEN to .env.sticky so session-end hook can
|
|
904
|
+
// push the data branch from background processes without a credential helper.
|
|
905
|
+
try {
|
|
906
|
+
const envStickyPath = path.join(process.cwd(), ".env.sticky");
|
|
907
|
+
let envContent = "";
|
|
908
|
+
try { envContent = fs.readFileSync(envStickyPath, "utf-8"); } catch (_) {}
|
|
909
|
+
if (!envContent.endsWith("\n") && envContent.length > 0) envContent += "\n";
|
|
910
|
+
if (!/^STICKY_PUSH_TOKEN=/m.test(envContent)) {
|
|
911
|
+
envContent += `STICKY_PUSH_TOKEN=${pat.trim()}\n`;
|
|
912
|
+
fs.writeFileSync(envStickyPath, envContent, "utf-8");
|
|
913
|
+
print(" ✓ STICKY_PUSH_TOKEN written to .env.sticky (data branch push)");
|
|
914
|
+
}
|
|
915
|
+
} catch (_) {}
|
|
880
916
|
}
|
|
881
917
|
rl2.close();
|
|
882
918
|
|
|
@@ -1071,10 +1107,11 @@ async function cmdInit() {
|
|
|
1071
1107
|
print(" ⏭️ .gitignore already configured");
|
|
1072
1108
|
}
|
|
1073
1109
|
|
|
1074
|
-
// Add .gitattributes merge strategy
|
|
1110
|
+
// Add .gitattributes merge strategy for both thread data and config
|
|
1075
1111
|
const gitattrsPath = path.join(process.cwd(), ".gitattributes");
|
|
1076
1112
|
const oldMergeRule = ".sticky-note/sticky-note.json merge=union";
|
|
1077
1113
|
const mergeRule = ".sticky-note/sticky-note.json merge=sticky-note";
|
|
1114
|
+
const configMergeRule = ".sticky-note/sticky-note-config.json merge=sticky-note";
|
|
1078
1115
|
let gitattrsContent = "";
|
|
1079
1116
|
if (fs.existsSync(gitattrsPath)) {
|
|
1080
1117
|
gitattrsContent = fs.readFileSync(gitattrsPath, "utf-8");
|
|
@@ -1089,10 +1126,18 @@ async function cmdInit() {
|
|
|
1089
1126
|
? `\n# Sticky Note - JSON-aware thread merge\n${mergeRule}\n`
|
|
1090
1127
|
: `\n\n# Sticky Note - JSON-aware thread merge\n${mergeRule}\n`;
|
|
1091
1128
|
fs.appendFileSync(gitattrsPath, addition);
|
|
1129
|
+
gitattrsContent += addition;
|
|
1092
1130
|
print(" [OK] .gitattributes updated (merge=sticky-note)");
|
|
1093
1131
|
} else {
|
|
1094
1132
|
print(" ⏭️ .gitattributes already configured");
|
|
1095
1133
|
}
|
|
1134
|
+
// Also protect sticky-note-config.json — merge conflicts here silently break
|
|
1135
|
+
// cloud transport and hook loading for the entire team.
|
|
1136
|
+
if (!gitattrsContent.includes(configMergeRule)) {
|
|
1137
|
+
const addition = `${configMergeRule}\n`;
|
|
1138
|
+
fs.appendFileSync(gitattrsPath, addition);
|
|
1139
|
+
print(" [OK] .gitattributes: sticky-note-config.json protected from merge conflicts");
|
|
1140
|
+
}
|
|
1096
1141
|
|
|
1097
1142
|
// Install merge driver script and git config
|
|
1098
1143
|
const mergeDriverSrc = path.join(TEMPLATES_DIR, "merge-driver.js");
|
|
@@ -1452,14 +1497,25 @@ async function cmdUpdate() {
|
|
|
1452
1497
|
}
|
|
1453
1498
|
}
|
|
1454
1499
|
|
|
1455
|
-
|
|
1456
|
-
let gitattrsContent = fs.
|
|
1500
|
+
{
|
|
1501
|
+
let gitattrsContent = fs.existsSync(gitattrsPath)
|
|
1502
|
+
? fs.readFileSync(gitattrsPath, "utf-8") : "";
|
|
1457
1503
|
const oldRule = ".sticky-note/sticky-note.json merge=union";
|
|
1458
1504
|
const newRule = ".sticky-note/sticky-note.json merge=sticky-note";
|
|
1505
|
+
const configRule = ".sticky-note/sticky-note-config.json merge=sticky-note";
|
|
1459
1506
|
if (gitattrsContent.includes(oldRule)) {
|
|
1460
1507
|
gitattrsContent = gitattrsContent.replace(oldRule, newRule);
|
|
1461
1508
|
fs.writeFileSync(gitattrsPath, gitattrsContent, "utf-8");
|
|
1462
1509
|
print(" [OK] .gitattributes upgraded (merge=union -> merge=sticky-note)");
|
|
1510
|
+
} else if (!gitattrsContent.includes(newRule)) {
|
|
1511
|
+
const addition = `\n# Sticky Note - JSON-aware thread merge\n${newRule}\n`;
|
|
1512
|
+
fs.appendFileSync(gitattrsPath, addition);
|
|
1513
|
+
gitattrsContent += addition;
|
|
1514
|
+
print(" [OK] .gitattributes updated (merge=sticky-note)");
|
|
1515
|
+
}
|
|
1516
|
+
if (!gitattrsContent.includes(configRule)) {
|
|
1517
|
+
fs.appendFileSync(gitattrsPath, `${configRule}\n`);
|
|
1518
|
+
print(" [OK] .gitattributes: sticky-note-config.json protected from merge conflicts");
|
|
1463
1519
|
}
|
|
1464
1520
|
}
|
|
1465
1521
|
|
package/package.json
CHANGED
|
@@ -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 : {};
|