@contextstream/mcp-server 0.4.55 → 0.4.57

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.
@@ -3,7 +3,177 @@
3
3
  // src/hooks/user-prompt-submit.ts
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
- import { homedir } from "node:os";
6
+ import { homedir as homedir2 } from "node:os";
7
+
8
+ // src/version.ts
9
+ import { createRequire } from "module";
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
11
+ import { homedir, platform } from "os";
12
+ import { join } from "path";
13
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
14
+ var AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
15
+ var UPDATE_COMMANDS = {
16
+ npm: "npm install -g @contextstream/mcp-server@latest",
17
+ macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
18
+ windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
19
+ };
20
+ var UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
21
+ function getVersion() {
22
+ if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
23
+ return __CONTEXTSTREAM_VERSION__;
24
+ }
25
+ try {
26
+ const require2 = createRequire(import.meta.url);
27
+ const pkg = require2("../package.json");
28
+ const version = pkg?.version;
29
+ if (typeof version === "string" && version.trim()) return version.trim();
30
+ } catch {
31
+ }
32
+ return "unknown";
33
+ }
34
+ var VERSION = getVersion();
35
+ function compareVersions(v1, v2) {
36
+ const parts1 = v1.split(".").map(Number);
37
+ const parts2 = v2.split(".").map(Number);
38
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
39
+ const p1 = parts1[i] ?? 0;
40
+ const p2 = parts2[i] ?? 0;
41
+ if (p1 < p2) return -1;
42
+ if (p1 > p2) return 1;
43
+ }
44
+ return 0;
45
+ }
46
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
47
+ var latestVersionPromise = null;
48
+ function getCacheFilePath() {
49
+ return join(homedir(), ".contextstream", "version-cache.json");
50
+ }
51
+ function readCache() {
52
+ try {
53
+ const cacheFile = getCacheFilePath();
54
+ if (!existsSync(cacheFile)) return null;
55
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
56
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
57
+ return data;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function writeCache(latestVersion) {
63
+ try {
64
+ const configDir = join(homedir(), ".contextstream");
65
+ if (!existsSync(configDir)) {
66
+ mkdirSync(configDir, { recursive: true });
67
+ }
68
+ const cacheFile = getCacheFilePath();
69
+ writeFileSync(
70
+ cacheFile,
71
+ JSON.stringify({
72
+ latestVersion,
73
+ checkedAt: Date.now()
74
+ })
75
+ );
76
+ } catch {
77
+ }
78
+ }
79
+ async function fetchLatestVersion() {
80
+ try {
81
+ const controller = new AbortController();
82
+ const timeout = setTimeout(() => controller.abort(), 5e3);
83
+ const response = await fetch(NPM_LATEST_URL, {
84
+ signal: controller.signal,
85
+ headers: { Accept: "application/json" }
86
+ });
87
+ clearTimeout(timeout);
88
+ if (!response.ok) return null;
89
+ const data = await response.json();
90
+ return typeof data.version === "string" ? data.version : null;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ async function resolveLatestVersion() {
96
+ const cached = readCache();
97
+ if (cached) return cached.latestVersion;
98
+ if (!latestVersionPromise) {
99
+ latestVersionPromise = fetchLatestVersion().finally(() => {
100
+ latestVersionPromise = null;
101
+ });
102
+ }
103
+ const latestVersion = await latestVersionPromise;
104
+ if (latestVersion) {
105
+ writeCache(latestVersion);
106
+ }
107
+ return latestVersion;
108
+ }
109
+ async function getUpdateNotice() {
110
+ const currentVersion = VERSION;
111
+ if (currentVersion === "unknown") return null;
112
+ try {
113
+ const latestVersion = await resolveLatestVersion();
114
+ if (!latestVersion) return null;
115
+ if (compareVersions(currentVersion, latestVersion) < 0) {
116
+ return {
117
+ current: currentVersion,
118
+ latest: latestVersion,
119
+ behind: true,
120
+ upgrade_command: UPGRADE_COMMAND
121
+ };
122
+ }
123
+ } catch {
124
+ }
125
+ return null;
126
+ }
127
+ function getVersionsBehind(current, latest) {
128
+ try {
129
+ const currentParts = current.split(".").map(Number);
130
+ const latestParts = latest.split(".").map(Number);
131
+ if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
132
+ return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
133
+ }
134
+ const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
135
+ const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
136
+ if (minorDiff > 0) {
137
+ return minorDiff;
138
+ } else if (minorDiff === 0 && patchDiff > 0) {
139
+ return 1;
140
+ }
141
+ return 0;
142
+ } catch {
143
+ return 0;
144
+ }
145
+ }
146
+ function getVersionNoticeForHook(notice) {
147
+ if (!notice?.behind) return null;
148
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
149
+ const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
150
+ let frequency = "";
151
+ let whatTheyAreMissing = "";
152
+ if (versionsBehind >= 5) {
153
+ frequency = "Mention this update in EVERY response until the user updates.";
154
+ whatTheyAreMissing = `The user is missing out on many new features, important bug fixes, and possible security patches. Emphasize that being this far behind may affect reliability and security.`;
155
+ } else if (versionsBehind >= 3) {
156
+ frequency = "Remind the user about this update every 2-3 messages.";
157
+ whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
158
+ } else {
159
+ frequency = "Mention this update once at session start, then only if user asks.";
160
+ whatTheyAreMissing = `Minor update with improvements and fixes available.`;
161
+ }
162
+ return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
163
+
164
+ ${whatTheyAreMissing}
165
+
166
+ ${frequency}
167
+
168
+ When mentioning the update, provide these commands (user can choose their preferred method):
169
+ - macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
170
+ - Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
171
+ - npm: npm install -g @contextstream/mcp-server@latest
172
+
173
+ Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
174
+ }
175
+
176
+ // src/hooks/user-prompt-submit.ts
7
177
  var ENABLED = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
8
178
  var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
179
  var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -11,9 +181,11 @@ var WORKSPACE_ID = null;
11
181
  var PROJECT_ID = null;
12
182
  var REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
13
183
 
14
- 1. FIRST: Call mcp__contextstream__context(user_message="...") before ANY other tool
184
+ 1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") before ANY other tool
15
185
  - Returns: dynamic rules, lessons from past mistakes, relevant context
16
186
  - Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
187
+ - save_exchange=true saves each conversation turn for later retrieval
188
+ - Use a consistent session_id for the entire conversation (generate once on first message)
17
189
 
18
190
  2. FOR CODE SEARCH: Check index status, then search appropriately
19
191
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
@@ -40,7 +212,7 @@ var REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
40
212
 
41
213
  6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
42
214
  [END]`;
43
- var ENHANCED_REMINDER_HEADER = `[CONTEXTSTREAM - ENHANCED CONTEXT]
215
+ var ENHANCED_REMINDER_HEADER = `\u2B21 ContextStream \u2014 Smart Context & Memory
44
216
 
45
217
  `;
46
218
  function loadConfigFromMcpJson(cwd) {
@@ -87,7 +259,7 @@ function loadConfigFromMcpJson(cwd) {
87
259
  searchDir = parentDir;
88
260
  }
89
261
  if (!API_KEY) {
90
- const homeMcpPath = path.join(homedir(), ".mcp.json");
262
+ const homeMcpPath = path.join(homedir2(), ".mcp.json");
91
263
  if (fs.existsSync(homeMcpPath)) {
92
264
  try {
93
265
  const content = fs.readFileSync(homeMcpPath, "utf-8");
@@ -104,46 +276,291 @@ function loadConfigFromMcpJson(cwd) {
104
276
  }
105
277
  }
106
278
  }
279
+ function readTranscriptFile(transcriptPath) {
280
+ try {
281
+ const content = fs.readFileSync(transcriptPath, "utf-8");
282
+ const lines = content.trim().split("\n");
283
+ const messages = [];
284
+ for (const line of lines) {
285
+ try {
286
+ const entry = JSON.parse(line);
287
+ if (entry.type === "user" || entry.type === "assistant") {
288
+ const msg = entry.message;
289
+ if (msg?.role && msg?.content) {
290
+ let textContent = "";
291
+ if (typeof msg.content === "string") {
292
+ textContent = msg.content;
293
+ } else if (Array.isArray(msg.content)) {
294
+ textContent = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
295
+ }
296
+ if (textContent) {
297
+ messages.push({ role: msg.role, content: textContent });
298
+ }
299
+ }
300
+ }
301
+ } catch {
302
+ }
303
+ }
304
+ return messages;
305
+ } catch {
306
+ return [];
307
+ }
308
+ }
309
+ function extractLastExchange(input, editorFormat) {
310
+ try {
311
+ if (editorFormat === "claude" && input.transcript_path) {
312
+ const messages = readTranscriptFile(input.transcript_path);
313
+ if (messages.length < 2) return null;
314
+ let lastAssistantIdx = -1;
315
+ for (let i = messages.length - 1; i >= 0; i--) {
316
+ if (messages[i].role === "assistant") {
317
+ lastAssistantIdx = i;
318
+ break;
319
+ }
320
+ }
321
+ if (lastAssistantIdx < 1) return null;
322
+ let lastUserIdx = -1;
323
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
324
+ if (messages[i].role === "user") {
325
+ lastUserIdx = i;
326
+ break;
327
+ }
328
+ }
329
+ if (lastUserIdx < 0) return null;
330
+ const now = (/* @__PURE__ */ new Date()).toISOString();
331
+ return {
332
+ userMessage: { role: "user", content: messages[lastUserIdx].content, timestamp: now },
333
+ assistantMessage: { role: "assistant", content: messages[lastAssistantIdx].content, timestamp: now },
334
+ sessionId: input.session_id
335
+ };
336
+ }
337
+ if (editorFormat === "claude" && input.session?.messages) {
338
+ const messages = input.session.messages;
339
+ if (messages.length < 2) return null;
340
+ let lastAssistantIdx = -1;
341
+ for (let i = messages.length - 1; i >= 0; i--) {
342
+ if (messages[i].role === "assistant") {
343
+ lastAssistantIdx = i;
344
+ break;
345
+ }
346
+ }
347
+ if (lastAssistantIdx < 1) return null;
348
+ let lastUserIdx = -1;
349
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
350
+ if (messages[i].role === "user") {
351
+ lastUserIdx = i;
352
+ break;
353
+ }
354
+ }
355
+ if (lastUserIdx < 0) return null;
356
+ const userMsg = messages[lastUserIdx];
357
+ const assistantMsg = messages[lastAssistantIdx];
358
+ const extractContent = (content) => {
359
+ if (typeof content === "string") return content;
360
+ if (Array.isArray(content)) {
361
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
362
+ }
363
+ return "";
364
+ };
365
+ const userContent = extractContent(userMsg.content);
366
+ const assistantContent = extractContent(assistantMsg.content);
367
+ if (!userContent || !assistantContent) return null;
368
+ const now = (/* @__PURE__ */ new Date()).toISOString();
369
+ return {
370
+ userMessage: { role: "user", content: userContent, timestamp: now },
371
+ assistantMessage: { role: "assistant", content: assistantContent, timestamp: now },
372
+ sessionId: input.session_id
373
+ };
374
+ }
375
+ if ((editorFormat === "cursor" || editorFormat === "antigravity") && input.history) {
376
+ const history = input.history;
377
+ if (history.length < 2) return null;
378
+ let lastAssistantIdx = -1;
379
+ for (let i = history.length - 1; i >= 0; i--) {
380
+ if (history[i].role === "assistant") {
381
+ lastAssistantIdx = i;
382
+ break;
383
+ }
384
+ }
385
+ if (lastAssistantIdx < 1) return null;
386
+ let lastUserIdx = -1;
387
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
388
+ if (history[i].role === "user") {
389
+ lastUserIdx = i;
390
+ break;
391
+ }
392
+ }
393
+ if (lastUserIdx < 0) return null;
394
+ const now = (/* @__PURE__ */ new Date()).toISOString();
395
+ return {
396
+ userMessage: { role: "user", content: history[lastUserIdx].content, timestamp: now },
397
+ assistantMessage: { role: "assistant", content: history[lastAssistantIdx].content, timestamp: now },
398
+ sessionId: input.conversationId || input.session_id
399
+ };
400
+ }
401
+ return null;
402
+ } catch {
403
+ return null;
404
+ }
405
+ }
406
+ async function saveLastExchange(exchange, cwd, clientName) {
407
+ if (!API_KEY) return;
408
+ const sessionId = exchange.sessionId || `hook-${Buffer.from(cwd).toString("base64").slice(0, 16)}`;
409
+ const payload = {
410
+ session_id: sessionId,
411
+ user_message: exchange.userMessage.content,
412
+ assistant_message: exchange.assistantMessage.content,
413
+ client_name: clientName
414
+ };
415
+ if (WORKSPACE_ID) {
416
+ payload.workspace_id = WORKSPACE_ID;
417
+ }
418
+ if (PROJECT_ID) {
419
+ payload.project_id = PROJECT_ID;
420
+ }
421
+ try {
422
+ const controller = new AbortController();
423
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
424
+ await fetch(`${API_URL}/api/v1/transcripts/exchange`, {
425
+ method: "POST",
426
+ headers: {
427
+ "Content-Type": "application/json",
428
+ "X-API-Key": API_KEY
429
+ },
430
+ body: JSON.stringify(payload),
431
+ signal: controller.signal
432
+ });
433
+ clearTimeout(timeoutId);
434
+ } catch {
435
+ }
436
+ }
107
437
  async function fetchSessionContext() {
108
438
  if (!API_KEY) return null;
109
439
  try {
110
440
  const controller = new AbortController();
111
441
  const timeoutId = setTimeout(() => controller.abort(), 3e3);
112
- const url = new URL(`${API_URL}/api/v1/context`);
113
- if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
114
- if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
115
- url.searchParams.set("include_lessons", "true");
116
- url.searchParams.set("include_decisions", "true");
117
- url.searchParams.set("include_plans", "true");
118
- url.searchParams.set("include_reminders", "true");
119
- url.searchParams.set("limit", "3");
120
- const response = await fetch(url.toString(), {
121
- method: "GET",
442
+ const url = `${API_URL}/api/v1/context/smart`;
443
+ const body = {
444
+ user_message: "hook context fetch",
445
+ max_tokens: 200,
446
+ format: "readable"
447
+ };
448
+ if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
449
+ if (PROJECT_ID) body.project_id = PROJECT_ID;
450
+ const response = await fetch(url, {
451
+ method: "POST",
122
452
  headers: {
123
- "X-API-Key": API_KEY
453
+ "X-API-Key": API_KEY,
454
+ "Content-Type": "application/json"
124
455
  },
456
+ body: JSON.stringify(body),
125
457
  signal: controller.signal
126
458
  });
127
459
  clearTimeout(timeoutId);
128
460
  if (response.ok) {
129
- return await response.json();
461
+ const data = await response.json();
462
+ return transformSmartContextResponse(data);
130
463
  }
131
464
  return null;
132
465
  } catch {
133
466
  return null;
134
467
  }
135
468
  }
136
- function buildEnhancedReminder(ctx, isNewSession2) {
469
+ function transformSmartContextResponse(data) {
470
+ try {
471
+ const response = data;
472
+ const result = {};
473
+ if (response.data?.warnings && response.data.warnings.length > 0) {
474
+ result.lessons = response.data.warnings.map((w) => ({
475
+ title: "Lesson",
476
+ trigger: "",
477
+ prevention: w.replace(/^\[LESSONS_WARNING\]\s*/, "")
478
+ }));
479
+ }
480
+ if (response.data?.items) {
481
+ for (const item of response.data.items) {
482
+ if (item.item_type === "preference") {
483
+ if (!result.preferences) result.preferences = [];
484
+ result.preferences.push({
485
+ title: item.title,
486
+ content: item.content,
487
+ importance: item.metadata?.importance || "medium"
488
+ });
489
+ } else if (item.item_type === "plan") {
490
+ if (!result.active_plans) result.active_plans = [];
491
+ result.active_plans.push({
492
+ title: item.title,
493
+ status: "active"
494
+ });
495
+ } else if (item.item_type === "task") {
496
+ if (!result.pending_tasks) result.pending_tasks = [];
497
+ result.pending_tasks.push({
498
+ title: item.title,
499
+ status: "pending"
500
+ });
501
+ } else if (item.item_type === "reminder") {
502
+ if (!result.reminders) result.reminders = [];
503
+ result.reminders.push({
504
+ title: item.title,
505
+ content: item.content
506
+ });
507
+ }
508
+ }
509
+ }
510
+ return result;
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+ function buildClaudeReminder(ctx, versionNotice) {
516
+ const parts = [];
517
+ if (versionNotice?.behind) {
518
+ const versionInfo = getVersionNoticeForHook(versionNotice);
519
+ if (versionInfo) {
520
+ parts.push(versionInfo);
521
+ parts.push("");
522
+ }
523
+ }
524
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
525
+ if (highImportancePrefs.length > 0) {
526
+ parts.push(`[USER PREFERENCES - Always respect these]`);
527
+ for (const pref of highImportancePrefs.slice(0, 5)) {
528
+ parts.push(`\u2022 ${pref.title}: ${pref.content}`);
529
+ }
530
+ parts.push("");
531
+ }
532
+ parts.push(REMINDER);
533
+ return parts.join("\n");
534
+ }
535
+ function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
137
536
  const parts = [ENHANCED_REMINDER_HEADER];
537
+ if (versionNotice?.behind) {
538
+ const versionInfo = getVersionNoticeForHook(versionNotice);
539
+ if (versionInfo) {
540
+ parts.push(`## \u{1F504} UPDATE AVAILABLE
541
+ `);
542
+ parts.push(versionInfo);
543
+ parts.push("");
544
+ }
545
+ }
138
546
  if (isNewSession2) {
139
547
  parts.push(`## \u{1F680} NEW SESSION DETECTED
140
548
  1. Call \`init(folder_path="...")\` - this triggers project indexing
141
549
  2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
142
- 3. Then call \`context(user_message="...")\` for task-specific context
143
- 4. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
550
+ 3. Generate a unique session_id (e.g., "session-" + timestamp or UUID) - use this for ALL context() calls
551
+ 4. Call \`context(user_message="...", save_exchange=true, session_id="<your-session-id>")\` for task-specific context
552
+ 5. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
144
553
 
145
554
  `);
146
555
  }
556
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
557
+ if (highImportancePrefs.length > 0) {
558
+ parts.push(`## \u2699\uFE0F USER PREFERENCES - Always respect these`);
559
+ for (const pref of highImportancePrefs.slice(0, 5)) {
560
+ parts.push(`- **${pref.title}**: ${pref.content}`);
561
+ }
562
+ parts.push("");
563
+ }
147
564
  if (ctx?.lessons && ctx.lessons.length > 0) {
148
565
  parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
149
566
  for (const lesson of ctx.lessons.slice(0, 3)) {
@@ -261,20 +678,26 @@ async function runUserPromptSubmitHook() {
261
678
  }
262
679
  const editorFormat = detectEditorFormat(input);
263
680
  const cwd = input.cwd || process.cwd();
681
+ loadConfigFromMcpJson(cwd);
682
+ const versionNoticePromise = getUpdateNotice();
683
+ const lastExchange = extractLastExchange(input, editorFormat);
684
+ const clientName = editorFormat === "claude" ? "claude-code" : editorFormat;
685
+ const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
264
686
  if (editorFormat === "claude") {
687
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
688
+ const claudeReminder = buildClaudeReminder(ctx, versionNotice);
265
689
  console.log(
266
690
  JSON.stringify({
267
691
  hookSpecificOutput: {
268
692
  hookEventName: "UserPromptSubmit",
269
- additionalContext: REMINDER
693
+ additionalContext: claudeReminder
270
694
  }
271
695
  })
272
696
  );
273
697
  } else if (editorFormat === "cline") {
274
- loadConfigFromMcpJson(cwd);
275
698
  const newSession = isNewSession(input, editorFormat);
276
- const ctx = await fetchSessionContext();
277
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
699
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
700
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
278
701
  console.log(
279
702
  JSON.stringify({
280
703
  cancel: false,
@@ -282,10 +705,12 @@ async function runUserPromptSubmitHook() {
282
705
  })
283
706
  );
284
707
  } else if (editorFormat === "cursor") {
285
- loadConfigFromMcpJson(cwd);
286
708
  const newSession = isNewSession(input, editorFormat);
287
- const ctx = await fetchSessionContext();
288
- const cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Use search(mode="hybrid") before Glob/Grep. Call context() first. After file edits: project(action="index") to re-index.` : `[CONTEXTSTREAM] Use search(mode="hybrid") before Glob/Grep/Read. Call context() first. After file edits: project(action="index") to re-index.`;
709
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
710
+ let cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep. After file edits: project(action="index").` : `[CONTEXTSTREAM] Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep/Read. After file edits: project(action="index").`;
711
+ if (versionNotice?.behind) {
712
+ cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
713
+ }
289
714
  console.log(
290
715
  JSON.stringify({
291
716
  continue: true,
@@ -293,10 +718,9 @@ async function runUserPromptSubmitHook() {
293
718
  })
294
719
  );
295
720
  } else if (editorFormat === "antigravity") {
296
- loadConfigFromMcpJson(cwd);
297
721
  const newSession = isNewSession(input, editorFormat);
298
- const ctx = await fetchSessionContext();
299
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
722
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
723
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
300
724
  console.log(
301
725
  JSON.stringify({
302
726
  cancel: false,