sticky-note-cli 2.9.1 → 3.0.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.
@@ -33,13 +33,82 @@ function _safeExit() {
33
33
  process.exit(0);
34
34
  }
35
35
 
36
+ // Issue #12: warn (non-blocking) when the running CLI is below the project's
37
+ // configured min_version. Returns "" when nothing to say. Also writes a
38
+ // stderr banner so the user sees it directly even if the hook output is
39
+ // swallowed by the host tool.
40
+ function _parseSemver(v) {
41
+ if (typeof v !== "string") return null;
42
+ const m = v.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)(?:\.(\d+))?/);
43
+ if (!m) return null;
44
+ return [Number(m[1]), Number(m[2]), Number(m[3] || 0)];
45
+ }
46
+ function _cmpSemver(a, b) {
47
+ const pa = _parseSemver(a), pb = _parseSemver(b);
48
+ if (!pa || !pb) return 0;
49
+ for (let i = 0; i < 3; i++) {
50
+ if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
51
+ }
52
+ return 0;
53
+ }
54
+ function _installedCliVersion() {
55
+ // Try the package.json shipped alongside this hook script's CLI.
56
+ try {
57
+ const candidates = [
58
+ path.join(__dirname, "..", "..", "package.json"),
59
+ path.join(__dirname, "..", "..", "..", "sticky-note-cli", "package.json"),
60
+ ];
61
+ for (const p of candidates) {
62
+ try {
63
+ const pkg = JSON.parse(require("fs").readFileSync(p, "utf-8"));
64
+ if (pkg && pkg.name === "sticky-note-cli" && pkg.version) return pkg.version;
65
+ } catch (_) { /* try next */ }
66
+ }
67
+ } catch (_) { /* ignore */ }
68
+ return null;
69
+ }
70
+ function formatVersionWarning(config) {
71
+ try {
72
+ const min = config && config.min_version;
73
+ if (!min) return "";
74
+ const installed = _installedCliVersion();
75
+ 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.`;
81
+ try { process.stderr.write(msg + "\n"); } catch (_) { /* non-fatal */ }
82
+ return msg;
83
+ } catch (_) {
84
+ return "";
85
+ }
86
+ }
87
+
36
88
  let utils;
37
89
  try {
38
90
  utils = require("./sticky-utils.js");
39
- } catch (_) {
91
+ } catch (err) {
92
+ try {
93
+ process.stderr.write(
94
+ `[STICKY-NOTE] session-start hook error: failed to load sticky-utils.js — ${err.message}\n` +
95
+ `[STICKY-NOTE] hint: re-run \`npx sticky-note init\` to restore .claude/hooks/.\n`
96
+ );
97
+ } catch (_) { /* ignore */ }
40
98
  _safeExit();
41
99
  }
42
100
 
101
+ // Issue #13: self-heal stale absolute hook paths in .claude/settings.json
102
+ // before doing anything else, so the next session loads cleanly even if some
103
+ // other hook (e.g. on-stop, inject-context) was pointing at a path that no
104
+ // longer exists. Failures here are non-fatal — we still want the rest of
105
+ // session-start to run.
106
+ try { utils.selfHealHookPaths && utils.selfHealHookPaths(); } catch (_) { /* non-fatal */ }
107
+
108
+ // Non-fatal: data-branch sync is disabled if the module is missing.
109
+ let dataBranch = null;
110
+ try { dataBranch = require("./data-branch.js"); } catch (_) {}
111
+
43
112
  const {
44
113
  getMemoryPath,
45
114
  getConfigPath,
@@ -121,6 +190,59 @@ function autoCloseCopilotCliThreads(memory, autoCloseHours) {
121
190
 
122
191
  // ── Formatting helpers ────────────────────────────────────
123
192
 
193
+ function formatStuckBanner(threads) {
194
+ const stuck = (threads || []).filter((t) => t && t.status === "stuck");
195
+ if (stuck.length === 0) return "";
196
+
197
+ const R = "\x1b[0m";
198
+ const Y = "\x1b[33m";
199
+ const BY = "\x1b[1;33m";
200
+ const BR = "\x1b[1;31m";
201
+ const B = "\x1b[1m";
202
+ const C = "\x1b[36m";
203
+ const D = "\x1b[2m";
204
+ const bar = "━".repeat(52);
205
+
206
+ const lines = [
207
+ "",
208
+ `${Y}${bar}${R}`,
209
+ `${BY} ⚠️ STUCK THREADS${R}`,
210
+ `${Y}${bar}${R}`,
211
+ ];
212
+
213
+ for (const t of stuck.slice(0, 5)) {
214
+ const user = t.user || t.author || "unknown";
215
+ const branch = t.branch ? ` ${D}·${R} ${C}${t.branch}${R}` : "";
216
+ const files = (t.files_touched || []).slice(0, 3).join(", ");
217
+ const narrative = t.narrative || t.last_note || "";
218
+ const snip =
219
+ narrative.length > 80 ? narrative.substring(0, 80) + "…" : narrative;
220
+ const threadId = (t.id || "").substring(0, 8);
221
+ const failed = t.failed_approaches || [];
222
+
223
+ lines.push(
224
+ `${Y} ┃${R} ${BR}[STUCK]${R} ${B}${user}${R}${branch}`
225
+ );
226
+ if (files) lines.push(`${Y} ┃${R} Files: ${files}`);
227
+ if (snip) lines.push(`${Y} ┃${R} ${D}"${snip}"${R}`);
228
+ if (failed.length > 0) {
229
+ lines.push(
230
+ `${Y} ┃${R} ${BR}⚠️ ${failed.length} failed approach(es)${R}`
231
+ );
232
+ }
233
+ lines.push(
234
+ `${Y} ┃${R} → Resume: ${C}npx sticky-note resume ${threadId}${R}`
235
+ );
236
+ }
237
+
238
+ if (stuck.length > 5) {
239
+ lines.push(`${Y} ┃${R} ... and ${stuck.length - 5} more`);
240
+ }
241
+
242
+ lines.push(`${Y}${bar}${R}`, "");
243
+ return lines.join("\n") + "\n";
244
+ }
245
+
124
246
  function formatThreadsForInjection(threads, maxThreads, maxTokens) {
125
247
  maxThreads = maxThreads || 10;
126
248
  maxTokens = maxTokens || 500;
@@ -251,7 +373,7 @@ function formatPresence(presenceData, threads) {
251
373
  try {
252
374
  const ts = new Date(lastSeen).getTime();
253
375
  if (isNaN(ts)) continue;
254
- if (now - ts < 15 * 60 * 1000) {
376
+ if (now - ts < 60 * 60 * 1000) {
255
377
  const files = (info.active_files || []).slice(0, 3).join(", ");
256
378
  const thread = userThreads[user];
257
379
  let line = `- **${user}**`;
@@ -761,6 +883,47 @@ function ensureEnvironmentProvisioned() {
761
883
  if (debug) process.stderr.write("[sticky-note] environment provisioning complete\n");
762
884
  }
763
885
 
886
+ // ── Data branch fetch-and-merge ───────────────────────────
887
+
888
+ /**
889
+ * Fetches the remote sticky-note/data branch and merges into local memory.
890
+ * Also reads from the LOCAL data branch (fresh clones, offline, tests).
891
+ * Non-blocking — the session continues with local data on any failure.
892
+ */
893
+ function fetchAndMergeDataBranch() {
894
+ if (!dataBranch) return;
895
+
896
+ const memPath = getMemoryPath();
897
+ const mergeFrom = (ref) => {
898
+ const content = dataBranch.readFileFromBranch(ref, "sticky-note.json");
899
+ if (content) {
900
+ dataBranch.mergeAndSaveFromRemote(memPath, content, loadJson, saveJson);
901
+ }
902
+ };
903
+
904
+ try {
905
+ const remote = dataBranch.getDefaultRemote();
906
+
907
+ if (remote) {
908
+ const fetchResult = dataBranch.fetchDataBranch(remote);
909
+ if (!fetchResult.ok) {
910
+ process.stderr.write(
911
+ "[STICKY-NOTE] offline — using local thread cache (" +
912
+ (fetchResult.error || "fetch failed") + ")\n"
913
+ );
914
+ } else if (fetchResult.remoteRef) {
915
+ mergeFrom(fetchResult.remoteRef);
916
+ }
917
+ }
918
+
919
+ mergeFrom(dataBranch.DATA_REF);
920
+ } catch (err) {
921
+ process.stderr.write(
922
+ "[STICKY-NOTE] offline — using local thread cache (" + err.message + ")\n"
923
+ );
924
+ }
925
+ }
926
+
764
927
  // ── Main ──────────────────────────────────────────────────
765
928
 
766
929
  function main() {
@@ -774,6 +937,9 @@ function main() {
774
937
  hookInput = {};
775
938
  }
776
939
 
940
+ // Fetch remote data branch and merge into local cache — before reading memory
941
+ fetchAndMergeDataBranch();
942
+
777
943
  let sessionId = getSessionId(hookInput);
778
944
  const aiTool = detectTool(hookInput);
779
945
  const isCopilotCli = aiTool === "copilot-cli" || process.argv.includes("--copilot-cli") || !!process.env.COPILOT_CLI;
@@ -911,6 +1077,7 @@ function main() {
911
1077
  const overlapContext = formatOverlapWarnings(
912
1078
  (memory.threads || []).filter(Boolean), getUser(), memory
913
1079
  );
1080
+ const versionWarning = formatVersionWarning(config);
914
1081
 
915
1082
  // V2.5: Mark eagerly-injected stuck threads so PreToolUse won't re-inject.
916
1083
  // Skip for Copilot CLI — its sessionStart output is not surfaced to the AI,
@@ -930,6 +1097,41 @@ function main() {
930
1097
 
931
1098
  saveMemoryMerged(memoryPath, memory);
932
1099
 
1100
+ // ── Stuck threads startup banner ────────────────────────
1101
+ // Write a visible banner to stderr so the user sees stuck threads
1102
+ // immediately when the session starts, before any AI interaction.
1103
+ const stuckThreads = (memory.threads || [])
1104
+ .filter((t) => t && t.status === "stuck");
1105
+ if (stuckThreads.length > 0) {
1106
+ const fs = require("fs");
1107
+ const bannerShownPath = path.join(
1108
+ path.dirname(memoryPath),
1109
+ ".sticky-banner-shown"
1110
+ );
1111
+ let showBanner = true;
1112
+
1113
+ // Copilot CLI fires sessionStart per-turn — only show once per session.
1114
+ if (isCopilotCli) {
1115
+ try {
1116
+ const shown = fs.readFileSync(bannerShownPath, "utf-8").trim();
1117
+ if (shown === sessionId) showBanner = false;
1118
+ } catch (_) {
1119
+ /* file doesn't exist yet */
1120
+ }
1121
+ }
1122
+
1123
+ if (showBanner) {
1124
+ process.stderr.write(formatStuckBanner(stuckThreads));
1125
+ if (isCopilotCli) {
1126
+ try {
1127
+ fs.writeFileSync(bannerShownPath, sessionId, "utf-8");
1128
+ } catch (_) {
1129
+ /* non-fatal */
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+
933
1135
  // ── Assemble output ───────────────────────────────────
934
1136
  const parts = [];
935
1137
 
@@ -1013,6 +1215,7 @@ function main() {
1013
1215
  }
1014
1216
 
1015
1217
  // Overlap warning comes first — most urgent signal
1218
+ if (versionWarning) parts.push(versionWarning);
1016
1219
  if (overlapContext) parts.push(overlapContext);
1017
1220
 
1018
1221
  if (threadContext) parts.push(threadContext);
@@ -1028,6 +1231,8 @@ function main() {
1028
1231
  try {
1029
1232
  main();
1030
1233
  } catch (err) {
1031
- try { utils.logHookError("session-start", err); } catch (_) {}
1234
+ try { utils.reportHookError("session-start", err); } catch (_) {
1235
+ try { utils.logHookError("session-start", err); } catch (_) {}
1236
+ }
1032
1237
  _safeExit();
1033
1238
  }
@@ -38,8 +38,16 @@ const FILE_PATH_PATTERN = /[\w./\\-]+\.\w{1,10}/g;
38
38
  // ── Path helpers ──────────────────────────────────────────
39
39
 
40
40
  function _stickyDir() {
41
- const scriptDir = path.dirname(path.resolve(__filename));
42
- return path.join(scriptDir, "..", "..", ".sticky-note");
41
+ try {
42
+ const gitDir = execFileSync("git", ["rev-parse", "--absolute-git-dir"], {
43
+ encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"],
44
+ }).trim();
45
+ return path.resolve(gitDir, "sticky-note");
46
+ } catch (_) {
47
+ // Fallback for non-git environments (e.g., unit tests without git init)
48
+ const scriptDir = path.dirname(path.resolve(__filename));
49
+ return path.join(scriptDir, "..", "..", ".sticky-note");
50
+ }
43
51
  }
44
52
 
45
53
  // ── Error logging ─────────────────────────────────────────
@@ -57,6 +65,101 @@ function logHookError(hookName, error) {
57
65
  }
58
66
  }
59
67
 
68
+ // Issue #14: Surface hook failures to stderr so the host tool's generic
69
+ // "<HookName> hook error" message has actionable detail next to it. Earlier
70
+ // versions silently swallowed `require()` failures and top-level exceptions,
71
+ // which made stale-path issues (#13) and other crashes impossible to diagnose
72
+ // — particularly under UserPromptSubmit, which Claude Code reports without
73
+ // a stack trace. This helper prefixes every line with `[STICKY-NOTE]` so the
74
+ // user can spot it in mixed hook output.
75
+ function reportHookError(hookName, error, hint) {
76
+ const msg = error instanceof Error ? (error.stack || error.message) : String(error);
77
+ const lines = [`[STICKY-NOTE] ${hookName} hook error: ${msg}`];
78
+ if (hint) lines.push(`[STICKY-NOTE] hint: ${hint}`);
79
+ try { process.stderr.write(lines.join("\n") + "\n"); } catch (_) { /* ignore */ }
80
+ try { logHookError(hookName, error); } catch (_) { /* ignore */ }
81
+ }
82
+
83
+ // Issue #13: When a teammate copies `.claude/settings.json` from another
84
+ // machine (or runs an older sticky-note-cli that wrote absolute paths), the
85
+ // hook commands point at non-existent absolute paths and every hook fails
86
+ // with MODULE_NOT_FOUND. Re-running `npx sticky-note init` fixes it, but
87
+ // users typically don't know that — they just see "<Hook> hook error" each
88
+ // session. This helper detects and rewrites those stale commands in-place
89
+ // the first time any hook (e.g. session-start, inject-context) successfully
90
+ // loads, so the next session starts clean.
91
+ //
92
+ // The rewritten command uses `npx sticky-note run-hook <basename>` which walks
93
+ // up the directory tree to find `.claude/hooks/<basename>.js`, so it works even
94
+ // when Claude Code is launched from a subdirectory of the project root.
95
+ // We extract `<basename>` from the existing command so we never invent hook
96
+ // scripts that don't exist.
97
+ //
98
+ // Returns the list of (hookEvent, oldCommand, newCommand) tuples that were
99
+ // rewritten, or [] if nothing was stale.
100
+ function selfHealHookPaths(settingsPath) {
101
+ const result = [];
102
+ try {
103
+ if (!settingsPath) {
104
+ settingsPath = path.join(process.cwd(), ".claude", "settings.json");
105
+ }
106
+ if (!fs.existsSync(settingsPath)) return result;
107
+
108
+ const raw = fs.readFileSync(settingsPath, "utf-8");
109
+ let settings;
110
+ try { settings = JSON.parse(raw); } catch (_) { return result; }
111
+ if (!settings || !settings.hooks) return result;
112
+
113
+ // Two stale patterns:
114
+ // 1. Absolute paths (written by older sticky-note-cli or copied from another machine)
115
+ // 2. Old relative form `node ".claude/hooks/foo.js"` — fails when Claude Code
116
+ // launches from a subdirectory, because the path resolves relative to the
117
+ // launch cwd, not the directory containing .claude/settings.json.
118
+ const STALE_ABSOLUTE = /["'][A-Za-z]:[\\/]|["']\/(?:Users|home|root|tmp)\/|\$\(git rev-parse/;
119
+ const STALE_RELATIVE = /^node\s+["']\.claude[\\/]hooks[\\/][^"']+\.js["']\s*$/;
120
+ // Match the script basename in either quoted or bare form.
121
+ const BASENAME = /([A-Za-z0-9_.-]+)\.(?:js|sh|cjs|mjs)(?:["'`\s]|$)/;
122
+
123
+ let changed = false;
124
+ for (const [event, entries] of Object.entries(settings.hooks)) {
125
+ if (!Array.isArray(entries)) continue;
126
+ for (const entry of entries) {
127
+ for (const h of (entry && entry.hooks) || []) {
128
+ const cmd = h && h.command;
129
+ if (typeof cmd !== "string") continue;
130
+ const isStale = STALE_ABSOLUTE.test(cmd) || STALE_RELATIVE.test(cmd);
131
+ if (!isStale) continue;
132
+ const m = cmd.match(BASENAME);
133
+ if (!m) continue;
134
+ const basename = m[1];
135
+ const newCmd = `npx sticky-note run-hook ${basename}`;
136
+ result.push({ event, oldCommand: cmd, newCommand: newCmd });
137
+ h.command = newCmd;
138
+ changed = true;
139
+ }
140
+ }
141
+ }
142
+
143
+ if (!changed) return result;
144
+
145
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
146
+ try {
147
+ const summary =
148
+ `[STICKY-NOTE] ⚠️ Detected ${result.length} stale hook path(s) in ` +
149
+ `${path.relative(process.cwd(), settingsPath) || settingsPath} — rewrote to ` +
150
+ `"npx sticky-note run-hook <name>" so hooks work from any subdirectory. ` +
151
+ `Commit the change to share the fix with your team.`;
152
+ process.stderr.write(summary + "\n");
153
+ for (const { event, oldCommand, newCommand } of result.slice(0, 5)) {
154
+ process.stderr.write(`[STICKY-NOTE] ${event}: ${oldCommand} → ${newCommand}\n`);
155
+ }
156
+ } catch (_) { /* non-fatal */ }
157
+ } catch (err) {
158
+ try { logHookError("selfHealHookPaths", err); } catch (_) { /* ignore */ }
159
+ }
160
+ return result;
161
+ }
162
+
60
163
  const _paths = {
61
164
  memory: "sticky-note.json",
62
165
  config: "sticky-note-config.json",
@@ -947,6 +1050,8 @@ module.exports = {
947
1050
  RETRY_PATTERNS,
948
1051
  FILE_PATH_PATTERN,
949
1052
  logHookError,
1053
+ reportHookError,
1054
+ selfHealHookPaths,
950
1055
  getMemoryPath,
951
1056
  getConfigPath,
952
1057
  getAuditPath,
@@ -1018,5 +1123,10 @@ module.exports = {
1018
1123
  rotateBackup,
1019
1124
  acquireLock,
1020
1125
  releaseLock,
1126
+ // Cloud sync stubs (feature not yet implemented — on-stop.js uses these)
1127
+ useCloud: () => false,
1128
+ cloudReadThreads: async () => null,
1129
+ cloudWriteThread: async () => {},
1130
+ cloudAppendAudit: async () => {},
1021
1131
  };
1022
1132
  // tier1 test
@@ -4,43 +4,43 @@
4
4
  "sessionStart": [
5
5
  {
6
6
  "type": "command",
7
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\" --copilot-cli",
8
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\" --copilot-cli"
7
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/session-start.js\" --copilot-cli",
8
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/session-start.js\" --copilot-cli"
9
9
  }
10
10
  ],
11
11
  "sessionEnd": [
12
12
  {
13
13
  "type": "command",
14
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\" --copilot-cli",
15
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\" --copilot-cli"
14
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/session-end.js\" --copilot-cli",
15
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/session-end.js\" --copilot-cli"
16
16
  }
17
17
  ],
18
18
  "userPromptSubmitted": [
19
19
  {
20
20
  "type": "command",
21
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\" --copilot-cli",
22
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\" --copilot-cli"
21
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/inject-context.js\" --copilot-cli",
22
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/inject-context.js\" --copilot-cli"
23
23
  }
24
24
  ],
25
25
  "preToolUse": [
26
26
  {
27
27
  "type": "command",
28
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\" --copilot-cli",
29
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\" --copilot-cli"
28
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/pre-tool-use.js\" --copilot-cli",
29
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/pre-tool-use.js\" --copilot-cli"
30
30
  }
31
31
  ],
32
32
  "postToolUse": [
33
33
  {
34
34
  "type": "command",
35
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\" --copilot-cli",
36
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\" --copilot-cli"
35
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/track-work.js\" --copilot-cli",
36
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/track-work.js\" --copilot-cli"
37
37
  }
38
38
  ],
39
39
  "errorOccurred": [
40
40
  {
41
41
  "type": "command",
42
- "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\" --copilot-cli",
43
- "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\" --copilot-cli"
42
+ "bash": "COPILOT_CLI=1 node \".claude/hooks/on-error.js\" --copilot-cli",
43
+ "powershell": "$env:COPILOT_CLI='1'; node \".claude/hooks/on-error.js\" --copilot-cli"
44
44
  }
45
45
  ]
46
46
  }
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\""
9
+ "command": "npx sticky-note run-hook session-start"
10
10
  }
11
11
  ]
12
12
  }
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\""
20
+ "command": "npx sticky-note run-hook session-end"
21
21
  }
22
22
  ]
23
23
  }
@@ -28,7 +28,7 @@
28
28
  "hooks": [
29
29
  {
30
30
  "type": "command",
31
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\""
31
+ "command": "npx sticky-note run-hook inject-context"
32
32
  }
33
33
  ]
34
34
  }
@@ -39,7 +39,7 @@
39
39
  "hooks": [
40
40
  {
41
41
  "type": "command",
42
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\""
42
+ "command": "npx sticky-note run-hook pre-tool-use"
43
43
  }
44
44
  ]
45
45
  }
@@ -50,7 +50,7 @@
50
50
  "hooks": [
51
51
  {
52
52
  "type": "command",
53
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\""
53
+ "command": "npx sticky-note run-hook track-work"
54
54
  }
55
55
  ]
56
56
  }
@@ -61,7 +61,7 @@
61
61
  "hooks": [
62
62
  {
63
63
  "type": "command",
64
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-stop.js\""
64
+ "command": "npx sticky-note run-hook on-stop"
65
65
  }
66
66
  ]
67
67
  }
@@ -72,7 +72,7 @@
72
72
  "hooks": [
73
73
  {
74
74
  "type": "command",
75
- "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\""
75
+ "command": "npx sticky-note run-hook on-error"
76
76
  }
77
77
  ]
78
78
  }
@@ -7,5 +7,6 @@
7
7
  "mcp_servers": [],
8
8
  "skills": [],
9
9
  "conventions": [],
10
- "hook_version": "2.5.0"
10
+ "hook_version": "2.5.0",
11
+ "min_version": "2.9.1"
11
12
  }
@@ -1,5 +1,5 @@
1
- {
2
- "version": "2",
3
- "project": "",
4
- "threads": []
5
- }
1
+ {
2
+ "version": "2",
3
+ "project": "",
4
+ "threads": []
5
+ }