@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.
@@ -282,6 +282,180 @@ var init_pre_tool_use = __esm({
282
282
  }
283
283
  });
284
284
 
285
+ // src/version.ts
286
+ import { createRequire } from "module";
287
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
288
+ import { homedir as homedir2, platform } from "os";
289
+ import { join as join2 } from "path";
290
+ function getVersion() {
291
+ if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
292
+ return __CONTEXTSTREAM_VERSION__;
293
+ }
294
+ try {
295
+ const require2 = createRequire(import.meta.url);
296
+ const pkg = require2("../package.json");
297
+ const version = pkg?.version;
298
+ if (typeof version === "string" && version.trim()) return version.trim();
299
+ } catch {
300
+ }
301
+ return "unknown";
302
+ }
303
+ function compareVersions(v1, v2) {
304
+ const parts1 = v1.split(".").map(Number);
305
+ const parts2 = v2.split(".").map(Number);
306
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
307
+ const p1 = parts1[i] ?? 0;
308
+ const p2 = parts2[i] ?? 0;
309
+ if (p1 < p2) return -1;
310
+ if (p1 > p2) return 1;
311
+ }
312
+ return 0;
313
+ }
314
+ function getCacheFilePath() {
315
+ return join2(homedir2(), ".contextstream", "version-cache.json");
316
+ }
317
+ function readCache() {
318
+ try {
319
+ const cacheFile = getCacheFilePath();
320
+ if (!existsSync2(cacheFile)) return null;
321
+ const data = JSON.parse(readFileSync2(cacheFile, "utf-8"));
322
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
323
+ return data;
324
+ } catch {
325
+ return null;
326
+ }
327
+ }
328
+ function writeCache(latestVersion) {
329
+ try {
330
+ const configDir = join2(homedir2(), ".contextstream");
331
+ if (!existsSync2(configDir)) {
332
+ mkdirSync(configDir, { recursive: true });
333
+ }
334
+ const cacheFile = getCacheFilePath();
335
+ writeFileSync(
336
+ cacheFile,
337
+ JSON.stringify({
338
+ latestVersion,
339
+ checkedAt: Date.now()
340
+ })
341
+ );
342
+ } catch {
343
+ }
344
+ }
345
+ async function fetchLatestVersion() {
346
+ try {
347
+ const controller = new AbortController();
348
+ const timeout = setTimeout(() => controller.abort(), 5e3);
349
+ const response = await fetch(NPM_LATEST_URL, {
350
+ signal: controller.signal,
351
+ headers: { Accept: "application/json" }
352
+ });
353
+ clearTimeout(timeout);
354
+ if (!response.ok) return null;
355
+ const data = await response.json();
356
+ return typeof data.version === "string" ? data.version : null;
357
+ } catch {
358
+ return null;
359
+ }
360
+ }
361
+ async function resolveLatestVersion() {
362
+ const cached = readCache();
363
+ if (cached) return cached.latestVersion;
364
+ if (!latestVersionPromise) {
365
+ latestVersionPromise = fetchLatestVersion().finally(() => {
366
+ latestVersionPromise = null;
367
+ });
368
+ }
369
+ const latestVersion = await latestVersionPromise;
370
+ if (latestVersion) {
371
+ writeCache(latestVersion);
372
+ }
373
+ return latestVersion;
374
+ }
375
+ async function getUpdateNotice() {
376
+ const currentVersion = VERSION;
377
+ if (currentVersion === "unknown") return null;
378
+ try {
379
+ const latestVersion = await resolveLatestVersion();
380
+ if (!latestVersion) return null;
381
+ if (compareVersions(currentVersion, latestVersion) < 0) {
382
+ return {
383
+ current: currentVersion,
384
+ latest: latestVersion,
385
+ behind: true,
386
+ upgrade_command: UPGRADE_COMMAND
387
+ };
388
+ }
389
+ } catch {
390
+ }
391
+ return null;
392
+ }
393
+ function getVersionsBehind(current, latest) {
394
+ try {
395
+ const currentParts = current.split(".").map(Number);
396
+ const latestParts = latest.split(".").map(Number);
397
+ if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
398
+ return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
399
+ }
400
+ const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
401
+ const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
402
+ if (minorDiff > 0) {
403
+ return minorDiff;
404
+ } else if (minorDiff === 0 && patchDiff > 0) {
405
+ return 1;
406
+ }
407
+ return 0;
408
+ } catch {
409
+ return 0;
410
+ }
411
+ }
412
+ function getVersionNoticeForHook(notice) {
413
+ if (!notice?.behind) return null;
414
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
415
+ const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
416
+ let frequency = "";
417
+ let whatTheyAreMissing = "";
418
+ if (versionsBehind >= 5) {
419
+ frequency = "Mention this update in EVERY response until the user updates.";
420
+ 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.`;
421
+ } else if (versionsBehind >= 3) {
422
+ frequency = "Remind the user about this update every 2-3 messages.";
423
+ whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
424
+ } else {
425
+ frequency = "Mention this update once at session start, then only if user asks.";
426
+ whatTheyAreMissing = `Minor update with improvements and fixes available.`;
427
+ }
428
+ return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
429
+
430
+ ${whatTheyAreMissing}
431
+
432
+ ${frequency}
433
+
434
+ When mentioning the update, provide these commands (user can choose their preferred method):
435
+ - macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
436
+ - Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
437
+ - npm: npm install -g @contextstream/mcp-server@latest
438
+
439
+ Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
440
+ }
441
+ var NPM_LATEST_URL, AUTO_UPDATE_ENABLED, UPDATE_COMMANDS, UPGRADE_COMMAND, VERSION, CACHE_TTL_MS, latestVersionPromise;
442
+ var init_version = __esm({
443
+ "src/version.ts"() {
444
+ "use strict";
445
+ NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
446
+ AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
447
+ UPDATE_COMMANDS = {
448
+ npm: "npm install -g @contextstream/mcp-server@latest",
449
+ macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
450
+ windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
451
+ };
452
+ UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
453
+ VERSION = getVersion();
454
+ CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
455
+ latestVersionPromise = null;
456
+ }
457
+ });
458
+
285
459
  // src/hooks/user-prompt-submit.ts
286
460
  var user_prompt_submit_exports = {};
287
461
  __export(user_prompt_submit_exports, {
@@ -289,7 +463,7 @@ __export(user_prompt_submit_exports, {
289
463
  });
290
464
  import * as fs2 from "node:fs";
291
465
  import * as path2 from "node:path";
292
- import { homedir as homedir2 } from "node:os";
466
+ import { homedir as homedir3 } from "node:os";
293
467
  function loadConfigFromMcpJson(cwd) {
294
468
  let searchDir = path2.resolve(cwd);
295
469
  for (let i = 0; i < 5; i++) {
@@ -334,7 +508,7 @@ function loadConfigFromMcpJson(cwd) {
334
508
  searchDir = parentDir;
335
509
  }
336
510
  if (!API_KEY) {
337
- const homeMcpPath = path2.join(homedir2(), ".mcp.json");
511
+ const homeMcpPath = path2.join(homedir3(), ".mcp.json");
338
512
  if (fs2.existsSync(homeMcpPath)) {
339
513
  try {
340
514
  const content = fs2.readFileSync(homeMcpPath, "utf-8");
@@ -351,46 +525,291 @@ function loadConfigFromMcpJson(cwd) {
351
525
  }
352
526
  }
353
527
  }
528
+ function readTranscriptFile(transcriptPath) {
529
+ try {
530
+ const content = fs2.readFileSync(transcriptPath, "utf-8");
531
+ const lines = content.trim().split("\n");
532
+ const messages = [];
533
+ for (const line of lines) {
534
+ try {
535
+ const entry = JSON.parse(line);
536
+ if (entry.type === "user" || entry.type === "assistant") {
537
+ const msg = entry.message;
538
+ if (msg?.role && msg?.content) {
539
+ let textContent = "";
540
+ if (typeof msg.content === "string") {
541
+ textContent = msg.content;
542
+ } else if (Array.isArray(msg.content)) {
543
+ textContent = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
544
+ }
545
+ if (textContent) {
546
+ messages.push({ role: msg.role, content: textContent });
547
+ }
548
+ }
549
+ }
550
+ } catch {
551
+ }
552
+ }
553
+ return messages;
554
+ } catch {
555
+ return [];
556
+ }
557
+ }
558
+ function extractLastExchange(input, editorFormat) {
559
+ try {
560
+ if (editorFormat === "claude" && input.transcript_path) {
561
+ const messages = readTranscriptFile(input.transcript_path);
562
+ if (messages.length < 2) return null;
563
+ let lastAssistantIdx = -1;
564
+ for (let i = messages.length - 1; i >= 0; i--) {
565
+ if (messages[i].role === "assistant") {
566
+ lastAssistantIdx = i;
567
+ break;
568
+ }
569
+ }
570
+ if (lastAssistantIdx < 1) return null;
571
+ let lastUserIdx = -1;
572
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
573
+ if (messages[i].role === "user") {
574
+ lastUserIdx = i;
575
+ break;
576
+ }
577
+ }
578
+ if (lastUserIdx < 0) return null;
579
+ const now = (/* @__PURE__ */ new Date()).toISOString();
580
+ return {
581
+ userMessage: { role: "user", content: messages[lastUserIdx].content, timestamp: now },
582
+ assistantMessage: { role: "assistant", content: messages[lastAssistantIdx].content, timestamp: now },
583
+ sessionId: input.session_id
584
+ };
585
+ }
586
+ if (editorFormat === "claude" && input.session?.messages) {
587
+ const messages = input.session.messages;
588
+ if (messages.length < 2) return null;
589
+ let lastAssistantIdx = -1;
590
+ for (let i = messages.length - 1; i >= 0; i--) {
591
+ if (messages[i].role === "assistant") {
592
+ lastAssistantIdx = i;
593
+ break;
594
+ }
595
+ }
596
+ if (lastAssistantIdx < 1) return null;
597
+ let lastUserIdx = -1;
598
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
599
+ if (messages[i].role === "user") {
600
+ lastUserIdx = i;
601
+ break;
602
+ }
603
+ }
604
+ if (lastUserIdx < 0) return null;
605
+ const userMsg = messages[lastUserIdx];
606
+ const assistantMsg = messages[lastAssistantIdx];
607
+ const extractContent = (content) => {
608
+ if (typeof content === "string") return content;
609
+ if (Array.isArray(content)) {
610
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
611
+ }
612
+ return "";
613
+ };
614
+ const userContent = extractContent(userMsg.content);
615
+ const assistantContent = extractContent(assistantMsg.content);
616
+ if (!userContent || !assistantContent) return null;
617
+ const now = (/* @__PURE__ */ new Date()).toISOString();
618
+ return {
619
+ userMessage: { role: "user", content: userContent, timestamp: now },
620
+ assistantMessage: { role: "assistant", content: assistantContent, timestamp: now },
621
+ sessionId: input.session_id
622
+ };
623
+ }
624
+ if ((editorFormat === "cursor" || editorFormat === "antigravity") && input.history) {
625
+ const history = input.history;
626
+ if (history.length < 2) return null;
627
+ let lastAssistantIdx = -1;
628
+ for (let i = history.length - 1; i >= 0; i--) {
629
+ if (history[i].role === "assistant") {
630
+ lastAssistantIdx = i;
631
+ break;
632
+ }
633
+ }
634
+ if (lastAssistantIdx < 1) return null;
635
+ let lastUserIdx = -1;
636
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
637
+ if (history[i].role === "user") {
638
+ lastUserIdx = i;
639
+ break;
640
+ }
641
+ }
642
+ if (lastUserIdx < 0) return null;
643
+ const now = (/* @__PURE__ */ new Date()).toISOString();
644
+ return {
645
+ userMessage: { role: "user", content: history[lastUserIdx].content, timestamp: now },
646
+ assistantMessage: { role: "assistant", content: history[lastAssistantIdx].content, timestamp: now },
647
+ sessionId: input.conversationId || input.session_id
648
+ };
649
+ }
650
+ return null;
651
+ } catch {
652
+ return null;
653
+ }
654
+ }
655
+ async function saveLastExchange(exchange, cwd, clientName) {
656
+ if (!API_KEY) return;
657
+ const sessionId = exchange.sessionId || `hook-${Buffer.from(cwd).toString("base64").slice(0, 16)}`;
658
+ const payload = {
659
+ session_id: sessionId,
660
+ user_message: exchange.userMessage.content,
661
+ assistant_message: exchange.assistantMessage.content,
662
+ client_name: clientName
663
+ };
664
+ if (WORKSPACE_ID) {
665
+ payload.workspace_id = WORKSPACE_ID;
666
+ }
667
+ if (PROJECT_ID) {
668
+ payload.project_id = PROJECT_ID;
669
+ }
670
+ try {
671
+ const controller = new AbortController();
672
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
673
+ await fetch(`${API_URL}/api/v1/transcripts/exchange`, {
674
+ method: "POST",
675
+ headers: {
676
+ "Content-Type": "application/json",
677
+ "X-API-Key": API_KEY
678
+ },
679
+ body: JSON.stringify(payload),
680
+ signal: controller.signal
681
+ });
682
+ clearTimeout(timeoutId);
683
+ } catch {
684
+ }
685
+ }
354
686
  async function fetchSessionContext() {
355
687
  if (!API_KEY) return null;
356
688
  try {
357
689
  const controller = new AbortController();
358
690
  const timeoutId = setTimeout(() => controller.abort(), 3e3);
359
- const url = new URL(`${API_URL}/api/v1/context`);
360
- if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
361
- if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
362
- url.searchParams.set("include_lessons", "true");
363
- url.searchParams.set("include_decisions", "true");
364
- url.searchParams.set("include_plans", "true");
365
- url.searchParams.set("include_reminders", "true");
366
- url.searchParams.set("limit", "3");
367
- const response = await fetch(url.toString(), {
368
- method: "GET",
691
+ const url = `${API_URL}/api/v1/context/smart`;
692
+ const body = {
693
+ user_message: "hook context fetch",
694
+ max_tokens: 200,
695
+ format: "readable"
696
+ };
697
+ if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
698
+ if (PROJECT_ID) body.project_id = PROJECT_ID;
699
+ const response = await fetch(url, {
700
+ method: "POST",
369
701
  headers: {
370
- "X-API-Key": API_KEY
702
+ "X-API-Key": API_KEY,
703
+ "Content-Type": "application/json"
371
704
  },
705
+ body: JSON.stringify(body),
372
706
  signal: controller.signal
373
707
  });
374
708
  clearTimeout(timeoutId);
375
709
  if (response.ok) {
376
- return await response.json();
710
+ const data = await response.json();
711
+ return transformSmartContextResponse(data);
377
712
  }
378
713
  return null;
379
714
  } catch {
380
715
  return null;
381
716
  }
382
717
  }
383
- function buildEnhancedReminder(ctx, isNewSession2) {
718
+ function transformSmartContextResponse(data) {
719
+ try {
720
+ const response = data;
721
+ const result = {};
722
+ if (response.data?.warnings && response.data.warnings.length > 0) {
723
+ result.lessons = response.data.warnings.map((w) => ({
724
+ title: "Lesson",
725
+ trigger: "",
726
+ prevention: w.replace(/^\[LESSONS_WARNING\]\s*/, "")
727
+ }));
728
+ }
729
+ if (response.data?.items) {
730
+ for (const item of response.data.items) {
731
+ if (item.item_type === "preference") {
732
+ if (!result.preferences) result.preferences = [];
733
+ result.preferences.push({
734
+ title: item.title,
735
+ content: item.content,
736
+ importance: item.metadata?.importance || "medium"
737
+ });
738
+ } else if (item.item_type === "plan") {
739
+ if (!result.active_plans) result.active_plans = [];
740
+ result.active_plans.push({
741
+ title: item.title,
742
+ status: "active"
743
+ });
744
+ } else if (item.item_type === "task") {
745
+ if (!result.pending_tasks) result.pending_tasks = [];
746
+ result.pending_tasks.push({
747
+ title: item.title,
748
+ status: "pending"
749
+ });
750
+ } else if (item.item_type === "reminder") {
751
+ if (!result.reminders) result.reminders = [];
752
+ result.reminders.push({
753
+ title: item.title,
754
+ content: item.content
755
+ });
756
+ }
757
+ }
758
+ }
759
+ return result;
760
+ } catch {
761
+ return null;
762
+ }
763
+ }
764
+ function buildClaudeReminder(ctx, versionNotice) {
765
+ const parts = [];
766
+ if (versionNotice?.behind) {
767
+ const versionInfo = getVersionNoticeForHook(versionNotice);
768
+ if (versionInfo) {
769
+ parts.push(versionInfo);
770
+ parts.push("");
771
+ }
772
+ }
773
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
774
+ if (highImportancePrefs.length > 0) {
775
+ parts.push(`[USER PREFERENCES - Always respect these]`);
776
+ for (const pref of highImportancePrefs.slice(0, 5)) {
777
+ parts.push(`\u2022 ${pref.title}: ${pref.content}`);
778
+ }
779
+ parts.push("");
780
+ }
781
+ parts.push(REMINDER);
782
+ return parts.join("\n");
783
+ }
784
+ function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
384
785
  const parts = [ENHANCED_REMINDER_HEADER];
786
+ if (versionNotice?.behind) {
787
+ const versionInfo = getVersionNoticeForHook(versionNotice);
788
+ if (versionInfo) {
789
+ parts.push(`## \u{1F504} UPDATE AVAILABLE
790
+ `);
791
+ parts.push(versionInfo);
792
+ parts.push("");
793
+ }
794
+ }
385
795
  if (isNewSession2) {
386
796
  parts.push(`## \u{1F680} NEW SESSION DETECTED
387
797
  1. Call \`init(folder_path="...")\` - this triggers project indexing
388
798
  2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
389
- 3. Then call \`context(user_message="...")\` for task-specific context
390
- 4. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
799
+ 3. Generate a unique session_id (e.g., "session-" + timestamp or UUID) - use this for ALL context() calls
800
+ 4. Call \`context(user_message="...", save_exchange=true, session_id="<your-session-id>")\` for task-specific context
801
+ 5. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
391
802
 
392
803
  `);
393
804
  }
805
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
806
+ if (highImportancePrefs.length > 0) {
807
+ parts.push(`## \u2699\uFE0F USER PREFERENCES - Always respect these`);
808
+ for (const pref of highImportancePrefs.slice(0, 5)) {
809
+ parts.push(`- **${pref.title}**: ${pref.content}`);
810
+ }
811
+ parts.push("");
812
+ }
394
813
  if (ctx?.lessons && ctx.lessons.length > 0) {
395
814
  parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
396
815
  for (const lesson of ctx.lessons.slice(0, 3)) {
@@ -508,20 +927,26 @@ async function runUserPromptSubmitHook() {
508
927
  }
509
928
  const editorFormat = detectEditorFormat2(input);
510
929
  const cwd = input.cwd || process.cwd();
930
+ loadConfigFromMcpJson(cwd);
931
+ const versionNoticePromise = getUpdateNotice();
932
+ const lastExchange = extractLastExchange(input, editorFormat);
933
+ const clientName = editorFormat === "claude" ? "claude-code" : editorFormat;
934
+ const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
511
935
  if (editorFormat === "claude") {
936
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
937
+ const claudeReminder = buildClaudeReminder(ctx, versionNotice);
512
938
  console.log(
513
939
  JSON.stringify({
514
940
  hookSpecificOutput: {
515
941
  hookEventName: "UserPromptSubmit",
516
- additionalContext: REMINDER
942
+ additionalContext: claudeReminder
517
943
  }
518
944
  })
519
945
  );
520
946
  } else if (editorFormat === "cline") {
521
- loadConfigFromMcpJson(cwd);
522
947
  const newSession = isNewSession(input, editorFormat);
523
- const ctx = await fetchSessionContext();
524
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
948
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
949
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
525
950
  console.log(
526
951
  JSON.stringify({
527
952
  cancel: false,
@@ -529,10 +954,12 @@ async function runUserPromptSubmitHook() {
529
954
  })
530
955
  );
531
956
  } else if (editorFormat === "cursor") {
532
- loadConfigFromMcpJson(cwd);
533
957
  const newSession = isNewSession(input, editorFormat);
534
- const ctx = await fetchSessionContext();
535
- 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.`;
958
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
959
+ 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").`;
960
+ if (versionNotice?.behind) {
961
+ cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
962
+ }
536
963
  console.log(
537
964
  JSON.stringify({
538
965
  continue: true,
@@ -540,10 +967,9 @@ async function runUserPromptSubmitHook() {
540
967
  })
541
968
  );
542
969
  } else if (editorFormat === "antigravity") {
543
- loadConfigFromMcpJson(cwd);
544
970
  const newSession = isNewSession(input, editorFormat);
545
- const ctx = await fetchSessionContext();
546
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
971
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
972
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
547
973
  console.log(
548
974
  JSON.stringify({
549
975
  cancel: false,
@@ -557,6 +983,7 @@ var ENABLED2, API_URL, API_KEY, WORKSPACE_ID, PROJECT_ID, REMINDER, ENHANCED_REM
557
983
  var init_user_prompt_submit = __esm({
558
984
  "src/hooks/user-prompt-submit.ts"() {
559
985
  "use strict";
986
+ init_version();
560
987
  ENABLED2 = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
561
988
  API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
562
989
  API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -564,9 +991,11 @@ var init_user_prompt_submit = __esm({
564
991
  PROJECT_ID = null;
565
992
  REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
566
993
 
567
- 1. FIRST: Call mcp__contextstream__context(user_message="...") before ANY other tool
994
+ 1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") before ANY other tool
568
995
  - Returns: dynamic rules, lessons from past mistakes, relevant context
569
996
  - Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
997
+ - save_exchange=true saves each conversation turn for later retrieval
998
+ - Use a consistent session_id for the entire conversation (generate once on first message)
570
999
 
571
1000
  2. FOR CODE SEARCH: Check index status, then search appropriately
572
1001
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
@@ -593,7 +1022,7 @@ var init_user_prompt_submit = __esm({
593
1022
 
594
1023
  6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
595
1024
  [END]`;
596
- ENHANCED_REMINDER_HEADER = `[CONTEXTSTREAM - ENHANCED CONTEXT]
1025
+ ENHANCED_REMINDER_HEADER = `\u2B21 ContextStream \u2014 Smart Context & Memory
597
1026
 
598
1027
  `;
599
1028
  isDirectRun2 = process.argv[1]?.includes("user-prompt-submit") || process.argv[2] === "user-prompt-submit";
@@ -719,7 +1148,7 @@ __export(pre_compact_exports, {
719
1148
  });
720
1149
  import * as fs3 from "node:fs";
721
1150
  import * as path3 from "node:path";
722
- import { homedir as homedir3 } from "node:os";
1151
+ import { homedir as homedir4 } from "node:os";
723
1152
  function loadConfigFromMcpJson2(cwd) {
724
1153
  let searchDir = path3.resolve(cwd);
725
1154
  for (let i = 0; i < 5; i++) {
@@ -758,7 +1187,7 @@ function loadConfigFromMcpJson2(cwd) {
758
1187
  searchDir = parentDir;
759
1188
  }
760
1189
  if (!API_KEY2) {
761
- const homeMcpPath = path3.join(homedir3(), ".mcp.json");
1190
+ const homeMcpPath = path3.join(homedir4(), ".mcp.json");
762
1191
  if (fs3.existsSync(homeMcpPath)) {
763
1192
  try {
764
1193
  const content = fs3.readFileSync(homeMcpPath, "utf-8");
@@ -1045,7 +1474,7 @@ __export(post_write_exports, {
1045
1474
  });
1046
1475
  import * as fs4 from "node:fs";
1047
1476
  import * as path4 from "node:path";
1048
- import { homedir as homedir4 } from "node:os";
1477
+ import { homedir as homedir5 } from "node:os";
1049
1478
  function extractFilePath(input) {
1050
1479
  if (input.tool_input) {
1051
1480
  const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
@@ -1115,7 +1544,7 @@ function loadApiConfig(startDir) {
1115
1544
  currentDir = parentDir;
1116
1545
  }
1117
1546
  if (!apiKey) {
1118
- const homeMcpPath = path4.join(homedir4(), ".mcp.json");
1547
+ const homeMcpPath = path4.join(homedir5(), ".mcp.json");
1119
1548
  if (fs4.existsSync(homeMcpPath)) {
1120
1549
  try {
1121
1550
  const content = fs4.readFileSync(homeMcpPath, "utf-8");
@@ -1429,7 +1858,7 @@ __export(hooks_config_exports, {
1429
1858
  });
1430
1859
  import * as fs5 from "node:fs/promises";
1431
1860
  import * as path5 from "node:path";
1432
- import { homedir as homedir5 } from "node:os";
1861
+ import { homedir as homedir6 } from "node:os";
1433
1862
  import { fileURLToPath } from "node:url";
1434
1863
  function getHookCommand(hookName2) {
1435
1864
  const fs7 = __require("node:fs");
@@ -1449,7 +1878,7 @@ function getHookCommand(hookName2) {
1449
1878
  }
1450
1879
  function getClaudeSettingsPath(scope, projectPath) {
1451
1880
  if (scope === "user") {
1452
- return path5.join(homedir5(), ".claude", "settings.json");
1881
+ return path5.join(homedir6(), ".claude", "settings.json");
1453
1882
  }
1454
1883
  if (!projectPath) {
1455
1884
  throw new Error("projectPath required for project scope");
@@ -1457,7 +1886,7 @@ function getClaudeSettingsPath(scope, projectPath) {
1457
1886
  return path5.join(projectPath, ".claude", "settings.json");
1458
1887
  }
1459
1888
  function getHooksDir() {
1460
- return path5.join(homedir5(), ".claude", "hooks");
1889
+ return path5.join(homedir6(), ".claude", "hooks");
1461
1890
  }
1462
1891
  function buildHooksConfig(options) {
1463
1892
  const userPromptHooks = [
@@ -1812,7 +2241,7 @@ If you prefer to configure manually, add to \`~/.claude/settings.json\`:
1812
2241
  `.trim();
1813
2242
  }
1814
2243
  function getIndexStatusPath() {
1815
- return path5.join(homedir5(), ".contextstream", "indexed-projects.json");
2244
+ return path5.join(homedir6(), ".contextstream", "indexed-projects.json");
1816
2245
  }
1817
2246
  async function readIndexStatus() {
1818
2247
  const statusPath = getIndexStatusPath();
@@ -1847,7 +2276,7 @@ async function unmarkProjectIndexed(projectPath) {
1847
2276
  }
1848
2277
  function getClineHooksDir(scope, projectPath) {
1849
2278
  if (scope === "global") {
1850
- return path5.join(homedir5(), "Documents", "Cline", "Rules", "Hooks");
2279
+ return path5.join(homedir6(), "Documents", "Cline", "Rules", "Hooks");
1851
2280
  }
1852
2281
  if (!projectPath) {
1853
2282
  throw new Error("projectPath required for project scope");
@@ -1874,7 +2303,7 @@ async function installClineHookScripts(options) {
1874
2303
  }
1875
2304
  function getRooCodeHooksDir(scope, projectPath) {
1876
2305
  if (scope === "global") {
1877
- return path5.join(homedir5(), ".roo", "hooks");
2306
+ return path5.join(homedir6(), ".roo", "hooks");
1878
2307
  }
1879
2308
  if (!projectPath) {
1880
2309
  throw new Error("projectPath required for project scope");
@@ -1901,7 +2330,7 @@ async function installRooCodeHookScripts(options) {
1901
2330
  }
1902
2331
  function getKiloCodeHooksDir(scope, projectPath) {
1903
2332
  if (scope === "global") {
1904
- return path5.join(homedir5(), ".kilocode", "hooks");
2333
+ return path5.join(homedir6(), ".kilocode", "hooks");
1905
2334
  }
1906
2335
  if (!projectPath) {
1907
2336
  throw new Error("projectPath required for project scope");
@@ -1928,7 +2357,7 @@ async function installKiloCodeHookScripts(options) {
1928
2357
  }
1929
2358
  function getCursorHooksConfigPath(scope, projectPath) {
1930
2359
  if (scope === "global") {
1931
- return path5.join(homedir5(), ".cursor", "hooks.json");
2360
+ return path5.join(homedir6(), ".cursor", "hooks.json");
1932
2361
  }
1933
2362
  if (!projectPath) {
1934
2363
  throw new Error("projectPath required for project scope");
@@ -1937,7 +2366,7 @@ function getCursorHooksConfigPath(scope, projectPath) {
1937
2366
  }
1938
2367
  function getCursorHooksDir(scope, projectPath) {
1939
2368
  if (scope === "global") {
1940
- return path5.join(homedir5(), ".cursor", "hooks");
2369
+ return path5.join(homedir6(), ".cursor", "hooks");
1941
2370
  }
1942
2371
  if (!projectPath) {
1943
2372
  throw new Error("projectPath required for project scope");
@@ -2986,7 +3415,7 @@ __export(auto_rules_exports, {
2986
3415
  });
2987
3416
  import * as fs6 from "node:fs";
2988
3417
  import * as path6 from "node:path";
2989
- import { homedir as homedir6 } from "node:os";
3418
+ import { homedir as homedir7 } from "node:os";
2990
3419
  function hasRunRecently() {
2991
3420
  try {
2992
3421
  if (!fs6.existsSync(MARKER_FILE)) return false;
@@ -3057,7 +3486,7 @@ function hasPythonHooks(settingsPath) {
3057
3486
  }
3058
3487
  }
3059
3488
  function detectPythonHooks(cwd) {
3060
- const globalSettingsPath = path6.join(homedir6(), ".claude", "settings.json");
3489
+ const globalSettingsPath = path6.join(homedir7(), ".claude", "settings.json");
3061
3490
  const projectSettingsPath = path6.join(cwd, ".claude", "settings.json");
3062
3491
  return {
3063
3492
  global: hasPythonHooks(globalSettingsPath),
@@ -3123,7 +3552,7 @@ var init_auto_rules = __esm({
3123
3552
  API_URL4 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3124
3553
  API_KEY4 = process.env.CONTEXTSTREAM_API_KEY || "";
3125
3554
  ENABLED6 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
3126
- MARKER_FILE = path6.join(homedir6(), ".contextstream", ".auto-rules-ran");
3555
+ MARKER_FILE = path6.join(homedir7(), ".contextstream", ".auto-rules-ran");
3127
3556
  COOLDOWN_MS = 4 * 60 * 60 * 1e3;
3128
3557
  isDirectRun6 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
3129
3558
  if (isDirectRun6) {