@contextstream/mcp-server 0.4.41 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +1704 -378
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -4542,7 +4542,7 @@ import { createRequire } from "module";
4542
4542
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4543
4543
  import { homedir } from "os";
4544
4544
  import { join } from "path";
4545
- var UPGRADE_COMMAND = "npm update -g @contextstream/mcp-server";
4545
+ var UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
4546
4546
  var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
4547
4547
  function getVersion() {
4548
4548
  try {
@@ -7689,6 +7689,7 @@ var ContextStreamClient = class {
7689
7689
  project_id: withDefaults.project_id,
7690
7690
  ...versionNotice2 ? { version_notice: versionNotice2 } : {},
7691
7691
  ...Array.isArray(data?.errors) ? { errors: data.errors } : {},
7692
+ ...Array.isArray(data?.warnings) && data.warnings.length > 0 ? { warnings: data.warnings } : {},
7692
7693
  ...this.indexRefreshInProgress ? { index_status: "refreshing" } : {},
7693
7694
  ...data?.context_pressure ? { context_pressure: data.context_pressure } : {}
7694
7695
  };
@@ -8872,16 +8873,15 @@ function applyMcpToolPrefix(markdown, toolPrefix) {
8872
8873
  return markdown.replace(toolRegex, `${toolPrefix}$1`);
8873
8874
  }
8874
8875
  var CONTEXTSTREAM_RULES_FULL = `
8875
- ## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
8876
+ ## ContextStream Rules
8876
8877
 
8877
8878
  <contextstream_rules>
8878
- 1. **SEARCH FIRST:** Before Glob/Grep/Search/Read \u2192 \`search(mode="hybrid", query="...")\` FIRST
8879
- 2. **EVERY MESSAGE:** Call \`context_smart(user_message="...")\` at start of response
8880
- 3. **LOCAL TOOLS:** ONLY if ContextStream search returns 0 results after retry
8879
+ 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
8880
+ 2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
8881
+ 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
8881
8882
  </contextstream_rules>
8882
8883
 
8883
- **You MUST display the <contextstream_rules> block verbatim at the start of EVERY response.**
8884
- This is a self-referential rule that ensures these instructions stay in recent context.
8884
+ **Display this block at the start of responses to keep rules in context.**
8885
8885
 
8886
8886
  ---
8887
8887
 
@@ -8940,8 +8940,10 @@ This applies to **EVERY search** throughout the **ENTIRE conversation**, not jus
8940
8940
  - **Apply the prevention steps** from each lesson to avoid repeating mistakes
8941
8941
 
8942
8942
  ### On \`context_smart\`:
8943
- - Check for any lessons in the returned context
8944
- - Lessons may be included based on semantic relevance to the user's message
8943
+ - Check for \`[LESSONS_WARNING]\` tag in the response
8944
+ - If present, you **MUST** tell the user about the lessons before proceeding
8945
+ - Lessons are proactively fetched when risky actions are detected (refactor, migrate, deploy, etc.)
8946
+ - **Do not skip or bury this warning** - lessons represent real past mistakes
8945
8947
 
8946
8948
  ### Before ANY Non-Trivial Work:
8947
8949
  **ALWAYS call \`session(action="get_lessons", query="<topic>")\`** where \`<topic>\` matches what you're about to do:
@@ -8965,22 +8967,43 @@ You have access to ContextStream MCP tools for persistent memory and context.
8965
8967
  v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
8966
8968
  Rules Version: ${RULES_VERSION}
8967
8969
 
8968
- ## TL;DR - REQUIRED EVERY MESSAGE
8970
+ ## TL;DR - WHEN TO USE CONTEXT
8969
8971
 
8970
- | Message | What to Call |
8971
- |---------|--------------|
8972
- | **1st message** | \`session_init(folder_path="...", context_hint="<user's message>")\`, then \`context_smart(...)\` |
8973
- | **\u26A0\uFE0F After session_init** | **CHECK \`lessons\` field** - if present, read and apply them BEFORE any work |
8974
- | **2nd+ messages** | \`context_smart(user_message="<user's message>", format="minified", max_tokens=400)\` |
8975
- | **\u{1F50D} ANY code search** | \`search(mode="hybrid", query="...")\` \u2014 ALWAYS before Glob/Grep/Search/Read |
8976
- | **\u26A0\uFE0F Before ANY risky work** | \`session(action="get_lessons", query="<topic>")\` \u2014 **MANDATORY, not optional** |
8977
- | **After completing task** | \`session(action="capture", event_type="decision", ...)\` - MUST capture |
8978
- | **User frustration/correction** | \`session(action="capture_lesson", ...)\` - MUST capture lessons |
8979
- | **Command/tool error + fix** | \`session(action="capture_lesson", ...)\` - MUST capture lessons |
8972
+ | Request Type | What to Do |
8973
+ |--------------|------------|
8974
+ | **\u{1F680} Simple utility** (list workspaces, show version) | **Just execute directly** - skip session_init, context_smart, capture |
8975
+ | **\u{1F4BB} Coding task** (edit, create, refactor) | Full context: session_init \u2192 context_smart \u2192 work \u2192 capture |
8976
+ | **\u{1F50D} Code search/discovery** | session_init \u2192 context_smart \u2192 search() |
8977
+ | **\u26A0\uFE0F Risky work** (deploy, migrate, refactor) | Check lessons first: \`session(action="get_lessons")\` |
8978
+ | **User frustration/correction** | Capture lesson: \`session(action="capture_lesson", ...)\` |
8980
8979
 
8981
- **NO EXCEPTIONS.** Do not skip even if you think you have enough context.
8980
+ ### Simple Utility Operations - FAST PATH
8982
8981
 
8983
- **First message rule:** After \`session_init\`:
8982
+ **For simple queries, just execute and respond:**
8983
+ - "list workspaces" \u2192 \`workspace(action="list")\` \u2192 done
8984
+ - "list projects" \u2192 \`project(action="list")\` \u2192 done
8985
+ - "show version" \u2192 \`help(action="version")\` \u2192 done
8986
+ - "what reminders do I have" \u2192 \`reminder(action="list")\` \u2192 done
8987
+
8988
+ **No session_init. No context_smart. No capture.** These add noise, not value.
8989
+
8990
+ ### Coding Tasks - FULL CONTEXT
8991
+
8992
+ | Step | What to Call |
8993
+ |------|--------------|
8994
+ | **1st message** | \`session_init(folder_path="...", context_hint="<msg>")\`, then \`context_smart(...)\` |
8995
+ | **2nd+ messages** | \`context_smart(user_message="<msg>", format="minified", max_tokens=400)\` |
8996
+ | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
8997
+ | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
8998
+ | **User correction** | \`session(action="capture_lesson", ...)\` |
8999
+ | **\u26A0\uFE0F When warnings received** | **STOP**, acknowledge, explain mitigation, then proceed |
9000
+
9001
+ **How to detect simple utility operations:**
9002
+ - Single-word commands: "list", "show", "version", "help"
9003
+ - Data retrieval with no context dependency: "list my workspaces", "what projects do I have"
9004
+ - Status checks: "am I authenticated?", "what's the server version?"
9005
+
9006
+ **First message rule (for coding tasks):** After \`session_init\`:
8984
9007
  1. Check for \`lessons\` in response - if present, READ and SUMMARIZE them to user
8985
9008
  2. Then call \`context_smart\` before any other tool or response
8986
9009
 
@@ -8994,9 +9017,9 @@ Rules Version: ${RULES_VERSION}
8994
9017
 
8995
9018
  v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode dispatch:
8996
9019
 
8997
- ### Standalone Tools (Always Call)
8998
- - **\`session_init\`** - Initialize session with workspace detection + context
8999
- - **\`context_smart\`** - Semantic search for relevant context (CALL EVERY MESSAGE, including immediately after \`session_init\`)
9020
+ ### Standalone Tools
9021
+ - **\`session_init\`** - Initialize session with workspace detection + context (skip for simple utility operations)
9022
+ - **\`context_smart\`** - Semantic search for relevant context (skip for simple utility operations)
9000
9023
 
9001
9024
  ### Domain Tools (Use action/mode parameter)
9002
9025
 
@@ -9043,10 +9066,21 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
9043
9066
 
9044
9067
  ---
9045
9068
 
9046
- ### Rules Update Notices
9069
+ ### Rules, Version & Lessons Notices
9070
+
9071
+ **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
9047
9072
 
9048
- - If you see **[RULES_NOTICE]**, update rules via \`generate_rules()\` (or rerun setup).
9049
- - If you see **[VERSION_NOTICE]**, tell the user to update MCP using the provided command.
9073
+ **[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
9074
+ 1. A new MCP server version is available
9075
+ 2. The exact update command to run
9076
+ 3. That they need to restart their AI tool after updating
9077
+ **Do not skip this** - users often miss stderr warnings.
9078
+
9079
+ **[LESSONS_WARNING]** - You **MUST** before proceeding:
9080
+ 1. Read all lessons listed
9081
+ 2. Tell the user about relevant lessons
9082
+ 3. Explain how you will avoid each past mistake
9083
+ **This is critical** - ignoring lessons leads to repeated failures.
9050
9084
 
9051
9085
  ---
9052
9086
 
@@ -9106,6 +9140,24 @@ session(action="capture", event_type="session_snapshot", title="Pre-compaction s
9106
9140
 
9107
9141
  **NEVER fall back to local tools (Glob/Grep/Read) just because search returned 0 results on first try.** Retry first.
9108
9142
 
9143
+ ### Enhanced Context (Server-Side Warnings)
9144
+
9145
+ \`context_smart\` now includes **intelligent server-side filtering** that proactively surfaces relevant warnings:
9146
+
9147
+ **Response fields:**
9148
+ - \`warnings\`: Array of warning strings (displayed with \u26A0\uFE0F prefix)
9149
+
9150
+ **What triggers warnings:**
9151
+ - **Lessons**: Past mistakes relevant to the current query (via semantic matching)
9152
+ - **Risky actions**: Detected high-risk operations (deployments, migrations, destructive commands)
9153
+ - **Breaking changes**: When modifications may impact other parts of the codebase
9154
+
9155
+ **When you receive warnings:**
9156
+ 1. **STOP** and read each warning carefully
9157
+ 2. **Acknowledge** the warning to the user
9158
+ 3. **Explain** how you will avoid the issue
9159
+ 4. Only proceed after addressing the warnings
9160
+
9109
9161
  ### Search & Code Intelligence (ContextStream-first)
9110
9162
 
9111
9163
  \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
@@ -9177,16 +9229,24 @@ If ContextStream returns results, stop and use them. NEVER use local Search/Expl
9177
9229
 
9178
9230
  ---
9179
9231
 
9180
- ### When to Capture (MANDATORY)
9232
+ ### When to Capture
9181
9233
 
9182
9234
  | When | Call | Example |
9183
9235
  |------|------|---------|
9184
9236
  | User makes decision | \`session(action="capture", event_type="decision", ...)\` | "Let's use PostgreSQL" |
9185
9237
  | User states preference | \`session(action="capture", event_type="preference", ...)\` | "I prefer TypeScript" |
9186
- | You complete a task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
9238
+ | Complete significant task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
9187
9239
  | Need past context | \`session(action="recall", query="...")\` | "What did we decide about X?" |
9188
9240
 
9189
- **You MUST capture after completing any significant task.** This ensures future sessions have context.
9241
+ **DO NOT capture utility operations:**
9242
+ - \u274C "Listed workspaces" - not meaningful context
9243
+ - \u274C "Showed version" - not a decision
9244
+ - \u274C "Listed projects" - just data retrieval
9245
+
9246
+ **DO capture meaningful work:**
9247
+ - \u2705 Decisions, preferences, completed features
9248
+ - \u2705 Lessons from mistakes
9249
+ - \u2705 Insights about architecture or patterns
9190
9250
 
9191
9251
  ---
9192
9252
 
@@ -9266,16 +9326,15 @@ memory(action="create_task", title="<task title>", plan_id="<plan_id from step 1
9266
9326
  See full documentation: https://contextstream.io/docs/mcp/tools
9267
9327
  `.trim();
9268
9328
  var CONTEXTSTREAM_RULES_MINIMAL = `
9269
- ## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
9329
+ ## ContextStream Rules
9270
9330
 
9271
9331
  <contextstream_rules>
9272
- 1. **SEARCH FIRST:** Before Glob/Grep/Search/Read \u2192 \`search(mode="hybrid", query="...")\` FIRST
9273
- 2. **EVERY MESSAGE:** Call \`context_smart(user_message="...")\` at start of response
9274
- 3. **LOCAL TOOLS:** ONLY if ContextStream search returns 0 results after retry
9332
+ 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
9333
+ 2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
9334
+ 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
9275
9335
  </contextstream_rules>
9276
9336
 
9277
- **You MUST display the <contextstream_rules> block verbatim at the start of EVERY response.**
9278
- This is a self-referential rule that ensures these instructions stay in context.
9337
+ **Display this block at the start of responses to keep rules in context.**
9279
9338
 
9280
9339
  ---
9281
9340
 
@@ -9284,7 +9343,7 @@ This is a self-referential rule that ensures these instructions stay in context.
9284
9343
  Rules Version: ${RULES_VERSION}
9285
9344
  **Note:** PreToolUse hooks block Glob/Grep/Search when ContextStream is available.
9286
9345
 
9287
- ### Required Every Message
9346
+ ### For Coding Tasks
9288
9347
 
9289
9348
  | Action | Tool Call |
9290
9349
  |--------|-----------|
@@ -9318,6 +9377,33 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
9318
9377
  | \`memory\` | \`memory(action="list_events", limit=10)\` |
9319
9378
  | \`graph\` | \`graph(action="dependencies", file_path="...")\` |
9320
9379
 
9380
+ ### \u{1F680} FAST PATH: Simple Utility Operations
9381
+
9382
+ **For simple utility commands, SKIP the ceremony and just execute directly:**
9383
+
9384
+ | Command Type | Just Call | Skip |
9385
+ |--------------|-----------|------|
9386
+ | List workspaces | \`workspace(action="list")\` | session_init, context_smart, capture |
9387
+ | List projects | \`project(action="list")\` | session_init, context_smart, capture |
9388
+ | Show version | \`help(action="version")\` | session_init, context_smart, capture |
9389
+ | List reminders | \`reminder(action="list")\` | session_init, context_smart, capture |
9390
+ | Check auth | \`help(action="auth")\` | session_init, context_smart, capture |
9391
+
9392
+ **Detect simple operations by these patterns:**
9393
+ - "list ...", "show ...", "what are my ...", "get ..."
9394
+ - Single-action queries with no context dependency
9395
+ - User just wants data, not analysis or coding help
9396
+
9397
+ **DO NOT add overhead for utility operations:**
9398
+ - \u274C Don't call session_init just to list workspaces
9399
+ - \u274C Don't call context_smart for simple queries
9400
+ - \u274C Don't capture "listed workspaces" as an event (that's noise)
9401
+
9402
+ **Use full context ceremony ONLY for:**
9403
+ - Coding tasks (edit, create, refactor, debug)
9404
+ - Search/discovery (finding code, understanding architecture)
9405
+ - Tasks where past decisions or lessons matter
9406
+
9321
9407
  ### Lessons (Past Mistakes)
9322
9408
 
9323
9409
  - After \`session_init\`: Check for \`lessons\` field and apply before work
@@ -9326,8 +9412,32 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
9326
9412
 
9327
9413
  ### Context Pressure & Compaction
9328
9414
 
9329
- - If \`context_smart\` returns high/critical \`context_pressure\`: call \`session_capture_smart(...)\` to save state
9330
- - After compaction (context lost): call \`session_init(..., is_post_compact=true)\` or \`session_restore_context()\`
9415
+ - If \`context_smart\` returns high/critical \`context_pressure\`: call \`session(action="capture", ...)\` to save state
9416
+ - PreCompact hooks automatically save snapshots before compaction (if installed)
9417
+
9418
+ ### Enhanced Context (Warnings)
9419
+
9420
+ \`context_smart\` returns server-side \`warnings\` for lessons, risky actions, and breaking changes.
9421
+ When warnings are present: **STOP**, acknowledge them, explain mitigation, then proceed.
9422
+
9423
+ ### Automatic Context Restoration
9424
+
9425
+ **Context restoration is now enabled by default.** Every \`session_init\` call automatically:
9426
+ - Restores context from recent snapshots (if available)
9427
+ - Returns \`restored_context\` field with snapshot data
9428
+ - Sets \`is_post_compact=true\` in response when restoration occurs
9429
+
9430
+ **No special handling needed after compaction** - just call \`session_init\` normally.
9431
+
9432
+ To disable automatic restoration:
9433
+ - Pass \`is_post_compact=false\` in the API call
9434
+ - Or set \`CONTEXTSTREAM_RESTORE_CONTEXT=false\` environment variable
9435
+
9436
+ ### Notices - MUST HANDLE IMMEDIATELY
9437
+
9438
+ - **[VERSION_NOTICE]**: Tell the user about the update and command to run
9439
+ - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
9440
+ - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
9331
9441
 
9332
9442
  ### Plans & Tasks
9333
9443
 
@@ -9353,13 +9463,6 @@ var TEMPLATES = {
9353
9463
  description: "Codex CLI agent instructions",
9354
9464
  build: (rules) => `# Codex CLI Instructions
9355
9465
  ${rules}
9356
- `
9357
- },
9358
- windsurf: {
9359
- filename: ".windsurfrules",
9360
- description: "Windsurf AI rules",
9361
- build: (rules) => `# Windsurf Rules
9362
- ${rules}
9363
9466
  `
9364
9467
  },
9365
9468
  cursor: {
@@ -9805,7 +9908,7 @@ var PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
9805
9908
  ContextStream PreCompact Hook for Claude Code
9806
9909
 
9807
9910
  Runs BEFORE conversation context is compacted (manual via /compact or automatic).
9808
- Injects a reminder for the AI to save conversation state using session_capture_smart.
9911
+ Automatically saves conversation state to ContextStream by parsing the transcript.
9809
9912
 
9810
9913
  Input (via stdin):
9811
9914
  {
@@ -9821,7 +9924,7 @@ Output (to stdout):
9821
9924
  {
9822
9925
  "hookSpecificOutput": {
9823
9926
  "hookEventName": "PreCompact",
9824
- "additionalContext": "... instructions for AI ..."
9927
+ "additionalContext": "... status message ..."
9825
9928
  }
9826
9929
  }
9827
9930
  """
@@ -9829,8 +9932,149 @@ Output (to stdout):
9829
9932
  import json
9830
9933
  import sys
9831
9934
  import os
9935
+ import re
9936
+ import urllib.request
9937
+ import urllib.error
9832
9938
 
9833
9939
  ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
9940
+ AUTO_SAVE = os.environ.get("CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE", "true").lower() == "true"
9941
+ API_URL = os.environ.get("CONTEXTSTREAM_API_URL", "https://api.contextstream.io")
9942
+ API_KEY = os.environ.get("CONTEXTSTREAM_API_KEY", "")
9943
+
9944
+ WORKSPACE_ID = None
9945
+
9946
+ def load_config_from_mcp_json(cwd):
9947
+ """Load API config from .mcp.json if env vars not set."""
9948
+ global API_URL, API_KEY, WORKSPACE_ID
9949
+
9950
+ # Try to find .mcp.json and .contextstream/config.json in cwd or parent directories
9951
+ search_dir = cwd
9952
+ for _ in range(5): # Search up to 5 levels
9953
+ # Load API config from .mcp.json
9954
+ if not API_KEY:
9955
+ mcp_path = os.path.join(search_dir, ".mcp.json")
9956
+ if os.path.exists(mcp_path):
9957
+ try:
9958
+ with open(mcp_path, 'r') as f:
9959
+ config = json.load(f)
9960
+ servers = config.get("mcpServers", {})
9961
+ cs_config = servers.get("contextstream", {})
9962
+ env = cs_config.get("env", {})
9963
+ if env.get("CONTEXTSTREAM_API_KEY"):
9964
+ API_KEY = env["CONTEXTSTREAM_API_KEY"]
9965
+ if env.get("CONTEXTSTREAM_API_URL"):
9966
+ API_URL = env["CONTEXTSTREAM_API_URL"]
9967
+ except:
9968
+ pass
9969
+
9970
+ # Load workspace_id from .contextstream/config.json
9971
+ if not WORKSPACE_ID:
9972
+ cs_config_path = os.path.join(search_dir, ".contextstream", "config.json")
9973
+ if os.path.exists(cs_config_path):
9974
+ try:
9975
+ with open(cs_config_path, 'r') as f:
9976
+ cs_config = json.load(f)
9977
+ if cs_config.get("workspace_id"):
9978
+ WORKSPACE_ID = cs_config["workspace_id"]
9979
+ except:
9980
+ pass
9981
+
9982
+ parent = os.path.dirname(search_dir)
9983
+ if parent == search_dir:
9984
+ break
9985
+ search_dir = parent
9986
+
9987
+ def parse_transcript(transcript_path):
9988
+ """Parse transcript to extract active files, decisions, and context."""
9989
+ active_files = set()
9990
+ recent_messages = []
9991
+ tool_calls = []
9992
+
9993
+ try:
9994
+ with open(transcript_path, 'r') as f:
9995
+ for line in f:
9996
+ try:
9997
+ entry = json.loads(line.strip())
9998
+ msg_type = entry.get("type", "")
9999
+
10000
+ # Extract files from tool calls
10001
+ if msg_type == "tool_use":
10002
+ tool_name = entry.get("name", "")
10003
+ tool_input = entry.get("input", {})
10004
+ tool_calls.append({"name": tool_name, "input": tool_input})
10005
+
10006
+ # Extract file paths from common tools
10007
+ if tool_name in ["Read", "Write", "Edit", "NotebookEdit"]:
10008
+ file_path = tool_input.get("file_path") or tool_input.get("notebook_path")
10009
+ if file_path:
10010
+ active_files.add(file_path)
10011
+ elif tool_name == "Glob":
10012
+ pattern = tool_input.get("pattern", "")
10013
+ if pattern:
10014
+ active_files.add(f"[glob:{pattern}]")
10015
+
10016
+ # Collect recent assistant messages for summary
10017
+ if msg_type == "assistant" and entry.get("content"):
10018
+ content = entry.get("content", "")
10019
+ if isinstance(content, str) and len(content) > 50:
10020
+ recent_messages.append(content[:500])
10021
+
10022
+ except json.JSONDecodeError:
10023
+ continue
10024
+ except Exception as e:
10025
+ pass
10026
+
10027
+ return {
10028
+ "active_files": list(active_files)[-20:], # Last 20 files
10029
+ "tool_call_count": len(tool_calls),
10030
+ "message_count": len(recent_messages),
10031
+ "last_tools": [t["name"] for t in tool_calls[-10:]], # Last 10 tool names
10032
+ }
10033
+
10034
+ def save_snapshot(session_id, transcript_data, trigger):
10035
+ """Save snapshot to ContextStream API."""
10036
+ if not API_KEY:
10037
+ return False, "No API key configured"
10038
+
10039
+ snapshot_content = {
10040
+ "session_id": session_id,
10041
+ "trigger": trigger,
10042
+ "captured_at": None, # API will set timestamp
10043
+ "active_files": transcript_data.get("active_files", []),
10044
+ "tool_call_count": transcript_data.get("tool_call_count", 0),
10045
+ "last_tools": transcript_data.get("last_tools", []),
10046
+ "auto_captured": True,
10047
+ }
10048
+
10049
+ payload = {
10050
+ "event_type": "session_snapshot",
10051
+ "title": f"Auto Pre-compaction Snapshot ({trigger})",
10052
+ "content": json.dumps(snapshot_content),
10053
+ "importance": "high",
10054
+ "tags": ["session_snapshot", "pre_compaction", "auto_captured"],
10055
+ "source_type": "hook",
10056
+ }
10057
+
10058
+ # Add workspace_id if available
10059
+ if WORKSPACE_ID:
10060
+ payload["workspace_id"] = WORKSPACE_ID
10061
+
10062
+ try:
10063
+ req = urllib.request.Request(
10064
+ f"{API_URL}/api/v1/memory/events",
10065
+ data=json.dumps(payload).encode('utf-8'),
10066
+ headers={
10067
+ "Content-Type": "application/json",
10068
+ "X-API-Key": API_KEY,
10069
+ },
10070
+ method="POST"
10071
+ )
10072
+ with urllib.request.urlopen(req, timeout=5) as resp:
10073
+ return True, "Snapshot saved"
10074
+ except urllib.error.URLError as e:
10075
+ return False, str(e)
10076
+ except Exception as e:
10077
+ return False, str(e)
9834
10078
 
9835
10079
  def main():
9836
10080
  if not ENABLED:
@@ -9841,26 +10085,38 @@ def main():
9841
10085
  except:
9842
10086
  sys.exit(0)
9843
10087
 
10088
+ # Load config from .mcp.json if env vars not set
10089
+ cwd = data.get("cwd", os.getcwd())
10090
+ load_config_from_mcp_json(cwd)
10091
+
10092
+ session_id = data.get("session_id", "unknown")
10093
+ transcript_path = data.get("transcript_path", "")
9844
10094
  trigger = data.get("trigger", "unknown")
9845
10095
  custom_instructions = data.get("custom_instructions", "")
9846
10096
 
9847
- # Build context injection for the AI
9848
- context = f"""[CONTEXT COMPACTION IMMINENT - {trigger.upper()}]
9849
- CRITICAL: Before context is compacted, you MUST save conversation state:
10097
+ # Parse transcript for context
10098
+ transcript_data = {}
10099
+ if transcript_path and os.path.exists(transcript_path):
10100
+ transcript_data = parse_transcript(transcript_path)
9850
10101
 
9851
- 1. IMMEDIATELY call: mcp__contextstream__session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="<JSON with: conversation_summary, active_goals, recent_decisions, active_files, unfinished_work>")
10102
+ # Auto-save snapshot if enabled
10103
+ auto_save_status = ""
10104
+ if AUTO_SAVE and API_KEY:
10105
+ success, msg = save_snapshot(session_id, transcript_data, trigger)
10106
+ if success:
10107
+ auto_save_status = f"\\n[ContextStream: Auto-saved snapshot with {len(transcript_data.get('active_files', []))} active files]"
10108
+ else:
10109
+ auto_save_status = f"\\n[ContextStream: Auto-save failed - {msg}]"
9852
10110
 
9853
- 2. Include in the snapshot:
9854
- - conversation_summary: Brief summary of what was discussed
9855
- - active_goals: List of goals/tasks in progress
9856
- - recent_decisions: Key decisions made in this session
9857
- - active_files: Files currently being worked on
9858
- - unfinished_work: Any incomplete tasks
10111
+ # Build context injection for the AI (backup in case auto-save fails)
10112
+ files_list = ", ".join(transcript_data.get("active_files", [])[:5]) or "none detected"
10113
+ context = f"""[CONTEXT COMPACTION - {trigger.upper()}]{auto_save_status}
9859
10114
 
9860
- 3. After compaction, call session_init(is_post_compact=true) to restore context.
10115
+ Active files detected: {files_list}
10116
+ Tool calls in session: {transcript_data.get('tool_call_count', 0)}
9861
10117
 
9862
- {f"User instructions: {custom_instructions}" if custom_instructions else ""}
9863
- [END COMPACTION WARNING]"""
10118
+ After compaction, call session_init(is_post_compact=true) to restore context.
10119
+ {f"User instructions: {custom_instructions}" if custom_instructions else ""}"""
9864
10120
 
9865
10121
  output = {
9866
10122
  "hookSpecificOutput": {
@@ -9982,45 +10238,6 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
9982
10238
  settings.hooks = existingHooks;
9983
10239
  return settings;
9984
10240
  }
9985
- async function installClaudeCodeHooks(options) {
9986
- const result = { scripts: [], settings: [] };
9987
- if (!options.dryRun) {
9988
- const scripts = await installHookScripts({ includePreCompact: options.includePreCompact });
9989
- result.scripts.push(scripts.preToolUse, scripts.userPrompt);
9990
- if (scripts.preCompact) {
9991
- result.scripts.push(scripts.preCompact);
9992
- }
9993
- } else {
9994
- const hooksDir = getHooksDir();
9995
- result.scripts.push(
9996
- path5.join(hooksDir, "contextstream-redirect.py"),
9997
- path5.join(hooksDir, "contextstream-reminder.py")
9998
- );
9999
- if (options.includePreCompact) {
10000
- result.scripts.push(path5.join(hooksDir, "contextstream-precompact.py"));
10001
- }
10002
- }
10003
- const hooksConfig = buildHooksConfig({ includePreCompact: options.includePreCompact });
10004
- if (options.scope === "user" || options.scope === "both") {
10005
- const settingsPath = getClaudeSettingsPath("user");
10006
- if (!options.dryRun) {
10007
- const existing = await readClaudeSettings("user");
10008
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
10009
- await writeClaudeSettings(merged, "user");
10010
- }
10011
- result.settings.push(settingsPath);
10012
- }
10013
- if ((options.scope === "project" || options.scope === "both") && options.projectPath) {
10014
- const settingsPath = getClaudeSettingsPath("project", options.projectPath);
10015
- if (!options.dryRun) {
10016
- const existing = await readClaudeSettings("project", options.projectPath);
10017
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
10018
- await writeClaudeSettings(merged, "project", options.projectPath);
10019
- }
10020
- result.settings.push(settingsPath);
10021
- }
10022
- return result;
10023
- }
10024
10241
  function getIndexStatusPath() {
10025
10242
  return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
10026
10243
  }
@@ -10049,6 +10266,547 @@ async function markProjectIndexed(projectPath, options) {
10049
10266
  };
10050
10267
  await writeIndexStatus(status);
10051
10268
  }
10269
+ var CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
10270
+ """
10271
+ ContextStream PreToolUse Hook for Cline
10272
+ Blocks discovery tools and redirects to ContextStream search.
10273
+
10274
+ Cline hooks use JSON output format:
10275
+ {
10276
+ "cancel": true/false,
10277
+ "errorMessage": "optional error description",
10278
+ "contextModification": "optional text to inject"
10279
+ }
10280
+ """
10281
+
10282
+ import json
10283
+ import sys
10284
+ import os
10285
+ from pathlib import Path
10286
+ from datetime import datetime, timedelta
10287
+
10288
+ ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
10289
+ INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
10290
+ STALE_THRESHOLD_DAYS = 7
10291
+
10292
+ DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
10293
+
10294
+ def is_discovery_glob(pattern):
10295
+ pattern_lower = pattern.lower()
10296
+ for p in DISCOVERY_PATTERNS:
10297
+ if p in pattern_lower:
10298
+ return True
10299
+ if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
10300
+ return True
10301
+ if "**" in pattern or "*/" in pattern:
10302
+ return True
10303
+ return False
10304
+
10305
+ def is_discovery_grep(file_path):
10306
+ if not file_path or file_path in [".", "./", "*", "**"]:
10307
+ return True
10308
+ if "*" in file_path or "**" in file_path:
10309
+ return True
10310
+ return False
10311
+
10312
+ def is_project_indexed(workspace_roots):
10313
+ """Check if any workspace root is in an indexed project."""
10314
+ if not INDEX_STATUS_FILE.exists():
10315
+ return False, False
10316
+
10317
+ try:
10318
+ with open(INDEX_STATUS_FILE, "r") as f:
10319
+ data = json.load(f)
10320
+ except:
10321
+ return False, False
10322
+
10323
+ projects = data.get("projects", {})
10324
+
10325
+ for workspace in workspace_roots:
10326
+ cwd_path = Path(workspace).resolve()
10327
+ for project_path, info in projects.items():
10328
+ try:
10329
+ indexed_path = Path(project_path).resolve()
10330
+ if cwd_path == indexed_path or indexed_path in cwd_path.parents:
10331
+ indexed_at = info.get("indexed_at")
10332
+ if indexed_at:
10333
+ try:
10334
+ indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
10335
+ if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
10336
+ return True, True
10337
+ except:
10338
+ pass
10339
+ return True, False
10340
+ except:
10341
+ continue
10342
+ return False, False
10343
+
10344
+ def output_allow(context_mod=None):
10345
+ result = {"cancel": False}
10346
+ if context_mod:
10347
+ result["contextModification"] = context_mod
10348
+ print(json.dumps(result))
10349
+ sys.exit(0)
10350
+
10351
+ def output_block(error_msg, context_mod=None):
10352
+ result = {"cancel": True, "errorMessage": error_msg}
10353
+ if context_mod:
10354
+ result["contextModification"] = context_mod
10355
+ print(json.dumps(result))
10356
+ sys.exit(0)
10357
+
10358
+ def main():
10359
+ if not ENABLED:
10360
+ output_allow()
10361
+
10362
+ try:
10363
+ data = json.load(sys.stdin)
10364
+ except:
10365
+ output_allow()
10366
+
10367
+ hook_name = data.get("hookName", "")
10368
+ if hook_name != "PreToolUse":
10369
+ output_allow()
10370
+
10371
+ tool = data.get("toolName", "")
10372
+ params = data.get("toolParameters", {})
10373
+ workspace_roots = data.get("workspaceRoots", [])
10374
+
10375
+ # Check if project is indexed
10376
+ is_indexed, is_stale = is_project_indexed(workspace_roots)
10377
+ if not is_indexed:
10378
+ output_allow()
10379
+
10380
+ # Check for discovery patterns
10381
+ if tool == "list_files" or tool == "search_files":
10382
+ pattern = params.get("path", "") or params.get("regex", "")
10383
+ if is_discovery_glob(pattern) or is_discovery_grep(pattern):
10384
+ output_block(
10385
+ f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
10386
+ "ContextStream search is indexed and faster. Only use local tools if ContextStream returns 0 results.",
10387
+ "[CONTEXTSTREAM] Use ContextStream search for code discovery."
10388
+ )
10389
+
10390
+ elif tool == "read_file":
10391
+ # Allow read_file by default - blocking discovery at search level is enough
10392
+ pass
10393
+
10394
+ output_allow()
10395
+
10396
+ if __name__ == "__main__":
10397
+ main()
10398
+ `;
10399
+ var CLINE_USER_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env python3
10400
+ """
10401
+ ContextStream UserPromptSubmit Hook for Cline
10402
+ Injects reminder about ContextStream rules on every message.
10403
+ """
10404
+
10405
+ import json
10406
+ import sys
10407
+ import os
10408
+
10409
+ ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
10410
+
10411
+ REMINDER = """[CONTEXTSTREAM RULES]
10412
+ 1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="hybrid") FIRST
10413
+ 2. Call context_smart at start of EVERY response
10414
+ 3. Local tools ONLY if ContextStream returns 0 results
10415
+ [END RULES]"""
10416
+
10417
+ def main():
10418
+ if not ENABLED:
10419
+ print(json.dumps({"cancel": False}))
10420
+ sys.exit(0)
10421
+
10422
+ try:
10423
+ json.load(sys.stdin)
10424
+ except:
10425
+ print(json.dumps({"cancel": False}))
10426
+ sys.exit(0)
10427
+
10428
+ print(json.dumps({
10429
+ "cancel": False,
10430
+ "contextModification": REMINDER
10431
+ }))
10432
+ sys.exit(0)
10433
+
10434
+ if __name__ == "__main__":
10435
+ main()
10436
+ `;
10437
+ function getClineHooksDir(scope, projectPath) {
10438
+ if (scope === "global") {
10439
+ return path5.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
10440
+ }
10441
+ if (!projectPath) {
10442
+ throw new Error("projectPath required for project scope");
10443
+ }
10444
+ return path5.join(projectPath, ".clinerules", "hooks");
10445
+ }
10446
+ async function installClineHookScripts(options) {
10447
+ const hooksDir = getClineHooksDir(options.scope, options.projectPath);
10448
+ await fs4.mkdir(hooksDir, { recursive: true });
10449
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10450
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10451
+ await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10452
+ await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10453
+ return {
10454
+ preToolUse: preToolUsePath,
10455
+ userPromptSubmit: userPromptPath
10456
+ };
10457
+ }
10458
+ function getRooCodeHooksDir(scope, projectPath) {
10459
+ if (scope === "global") {
10460
+ return path5.join(homedir2(), ".roo", "hooks");
10461
+ }
10462
+ if (!projectPath) {
10463
+ throw new Error("projectPath required for project scope");
10464
+ }
10465
+ return path5.join(projectPath, ".roo", "hooks");
10466
+ }
10467
+ async function installRooCodeHookScripts(options) {
10468
+ const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
10469
+ await fs4.mkdir(hooksDir, { recursive: true });
10470
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10471
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10472
+ await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10473
+ await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10474
+ return {
10475
+ preToolUse: preToolUsePath,
10476
+ userPromptSubmit: userPromptPath
10477
+ };
10478
+ }
10479
+ function getKiloCodeHooksDir(scope, projectPath) {
10480
+ if (scope === "global") {
10481
+ return path5.join(homedir2(), ".kilocode", "hooks");
10482
+ }
10483
+ if (!projectPath) {
10484
+ throw new Error("projectPath required for project scope");
10485
+ }
10486
+ return path5.join(projectPath, ".kilocode", "hooks");
10487
+ }
10488
+ async function installKiloCodeHookScripts(options) {
10489
+ const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
10490
+ await fs4.mkdir(hooksDir, { recursive: true });
10491
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10492
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10493
+ await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10494
+ await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10495
+ return {
10496
+ preToolUse: preToolUsePath,
10497
+ userPromptSubmit: userPromptPath
10498
+ };
10499
+ }
10500
+ var CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
10501
+ """
10502
+ ContextStream PreToolUse Hook for Cursor
10503
+ Blocks discovery tools and redirects to ContextStream search.
10504
+
10505
+ Cursor hooks use JSON output format:
10506
+ {
10507
+ "decision": "allow" | "deny",
10508
+ "reason": "optional error description"
10509
+ }
10510
+ """
10511
+
10512
+ import json
10513
+ import sys
10514
+ import os
10515
+ from pathlib import Path
10516
+ from datetime import datetime, timedelta
10517
+
10518
+ ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
10519
+ INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
10520
+ STALE_THRESHOLD_DAYS = 7
10521
+
10522
+ DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
10523
+
10524
+ def is_discovery_glob(pattern):
10525
+ pattern_lower = pattern.lower()
10526
+ for p in DISCOVERY_PATTERNS:
10527
+ if p in pattern_lower:
10528
+ return True
10529
+ if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
10530
+ return True
10531
+ if "**" in pattern or "*/" in pattern:
10532
+ return True
10533
+ return False
10534
+
10535
+ def is_discovery_grep(file_path):
10536
+ if not file_path or file_path in [".", "./", "*", "**"]:
10537
+ return True
10538
+ if "*" in file_path or "**" in file_path:
10539
+ return True
10540
+ return False
10541
+
10542
+ def is_project_indexed(workspace_roots):
10543
+ """Check if any workspace root is in an indexed project."""
10544
+ if not INDEX_STATUS_FILE.exists():
10545
+ return False, False
10546
+
10547
+ try:
10548
+ with open(INDEX_STATUS_FILE, "r") as f:
10549
+ data = json.load(f)
10550
+ except:
10551
+ return False, False
10552
+
10553
+ projects = data.get("projects", {})
10554
+
10555
+ for workspace in workspace_roots:
10556
+ cwd_path = Path(workspace).resolve()
10557
+ for project_path, info in projects.items():
10558
+ try:
10559
+ indexed_path = Path(project_path).resolve()
10560
+ if cwd_path == indexed_path or indexed_path in cwd_path.parents:
10561
+ indexed_at = info.get("indexed_at")
10562
+ if indexed_at:
10563
+ try:
10564
+ indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
10565
+ if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
10566
+ return True, True
10567
+ except:
10568
+ pass
10569
+ return True, False
10570
+ except:
10571
+ continue
10572
+ return False, False
10573
+
10574
+ def output_allow():
10575
+ print(json.dumps({"decision": "allow"}))
10576
+ sys.exit(0)
10577
+
10578
+ def output_deny(reason):
10579
+ print(json.dumps({"decision": "deny", "reason": reason}))
10580
+ sys.exit(0)
10581
+
10582
+ def main():
10583
+ if not ENABLED:
10584
+ output_allow()
10585
+
10586
+ try:
10587
+ data = json.load(sys.stdin)
10588
+ except:
10589
+ output_allow()
10590
+
10591
+ hook_name = data.get("hook_event_name", "")
10592
+ if hook_name != "preToolUse":
10593
+ output_allow()
10594
+
10595
+ tool = data.get("tool_name", "")
10596
+ params = data.get("tool_input", {}) or data.get("parameters", {})
10597
+ workspace_roots = data.get("workspace_roots", [])
10598
+
10599
+ # Check if project is indexed
10600
+ is_indexed, _ = is_project_indexed(workspace_roots)
10601
+ if not is_indexed:
10602
+ output_allow()
10603
+
10604
+ # Check for Cursor tools
10605
+ if tool in ["Glob", "glob", "list_files"]:
10606
+ pattern = params.get("pattern", "") or params.get("path", "")
10607
+ if is_discovery_glob(pattern):
10608
+ output_deny(
10609
+ f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
10610
+ "ContextStream search is indexed and faster."
10611
+ )
10612
+
10613
+ elif tool in ["Grep", "grep", "search_files", "ripgrep"]:
10614
+ pattern = params.get("pattern", "") or params.get("regex", "")
10615
+ file_path = params.get("path", "")
10616
+ if is_discovery_grep(file_path):
10617
+ output_deny(
10618
+ f"Use mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") instead of {tool}. "
10619
+ "ContextStream search is indexed and faster."
10620
+ )
10621
+
10622
+ output_allow()
10623
+
10624
+ if __name__ == "__main__":
10625
+ main()
10626
+ `;
10627
+ var CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT = `#!/usr/bin/env python3
10628
+ """
10629
+ ContextStream BeforeSubmitPrompt Hook for Cursor
10630
+ Injects reminder about ContextStream rules.
10631
+ """
10632
+
10633
+ import json
10634
+ import sys
10635
+ import os
10636
+
10637
+ ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
10638
+
10639
+ def main():
10640
+ if not ENABLED:
10641
+ print(json.dumps({"continue": True}))
10642
+ sys.exit(0)
10643
+
10644
+ try:
10645
+ json.load(sys.stdin)
10646
+ except:
10647
+ print(json.dumps({"continue": True}))
10648
+ sys.exit(0)
10649
+
10650
+ print(json.dumps({
10651
+ "continue": True,
10652
+ "user_message": "[CONTEXTSTREAM] Search with mcp__contextstream__search before using Glob/Grep/Read"
10653
+ }))
10654
+ sys.exit(0)
10655
+
10656
+ if __name__ == "__main__":
10657
+ main()
10658
+ `;
10659
+ function getCursorHooksConfigPath(scope, projectPath) {
10660
+ if (scope === "global") {
10661
+ return path5.join(homedir2(), ".cursor", "hooks.json");
10662
+ }
10663
+ if (!projectPath) {
10664
+ throw new Error("projectPath required for project scope");
10665
+ }
10666
+ return path5.join(projectPath, ".cursor", "hooks.json");
10667
+ }
10668
+ function getCursorHooksDir(scope, projectPath) {
10669
+ if (scope === "global") {
10670
+ return path5.join(homedir2(), ".cursor", "hooks");
10671
+ }
10672
+ if (!projectPath) {
10673
+ throw new Error("projectPath required for project scope");
10674
+ }
10675
+ return path5.join(projectPath, ".cursor", "hooks");
10676
+ }
10677
+ async function readCursorHooksConfig(scope, projectPath) {
10678
+ const configPath = getCursorHooksConfigPath(scope, projectPath);
10679
+ try {
10680
+ const content = await fs4.readFile(configPath, "utf-8");
10681
+ return JSON.parse(content);
10682
+ } catch {
10683
+ return { version: 1, hooks: {} };
10684
+ }
10685
+ }
10686
+ async function writeCursorHooksConfig(config, scope, projectPath) {
10687
+ const configPath = getCursorHooksConfigPath(scope, projectPath);
10688
+ const dir = path5.dirname(configPath);
10689
+ await fs4.mkdir(dir, { recursive: true });
10690
+ await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
10691
+ }
10692
+ async function installCursorHookScripts(options) {
10693
+ const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
10694
+ await fs4.mkdir(hooksDir, { recursive: true });
10695
+ const preToolUsePath = path5.join(hooksDir, "contextstream-pretooluse.py");
10696
+ const beforeSubmitPath = path5.join(hooksDir, "contextstream-beforesubmit.py");
10697
+ await fs4.writeFile(preToolUsePath, CURSOR_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10698
+ await fs4.writeFile(beforeSubmitPath, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT, { mode: 493 });
10699
+ const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
10700
+ const filterContextStreamHooks = (hooks) => {
10701
+ if (!hooks) return [];
10702
+ return hooks.filter((h) => !h.command?.includes("contextstream"));
10703
+ };
10704
+ const config = {
10705
+ version: 1,
10706
+ hooks: {
10707
+ ...existingConfig.hooks,
10708
+ preToolUse: [
10709
+ ...filterContextStreamHooks(existingConfig.hooks.preToolUse),
10710
+ {
10711
+ command: `python3 "${preToolUsePath}"`,
10712
+ type: "command",
10713
+ timeout: 5,
10714
+ matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
10715
+ }
10716
+ ],
10717
+ beforeSubmitPrompt: [
10718
+ ...filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt),
10719
+ {
10720
+ command: `python3 "${beforeSubmitPath}"`,
10721
+ type: "command",
10722
+ timeout: 5
10723
+ }
10724
+ ]
10725
+ }
10726
+ };
10727
+ await writeCursorHooksConfig(config, options.scope, options.projectPath);
10728
+ const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
10729
+ return {
10730
+ preToolUse: preToolUsePath,
10731
+ beforeSubmitPrompt: beforeSubmitPath,
10732
+ config: configPath
10733
+ };
10734
+ }
10735
+ async function installEditorHooks(options) {
10736
+ const { editor, scope, projectPath, includePreCompact } = options;
10737
+ switch (editor) {
10738
+ case "claude": {
10739
+ if (scope === "project" && !projectPath) {
10740
+ throw new Error("projectPath required for project scope");
10741
+ }
10742
+ const scripts = await installHookScripts({ includePreCompact });
10743
+ const hooksConfig = buildHooksConfig({ includePreCompact });
10744
+ const settingsScope = scope === "global" ? "user" : "project";
10745
+ const existing = await readClaudeSettings(settingsScope, projectPath);
10746
+ const merged = mergeHooksIntoSettings(existing, hooksConfig);
10747
+ await writeClaudeSettings(merged, settingsScope, projectPath);
10748
+ const installed = [scripts.preToolUse, scripts.userPrompt];
10749
+ if (scripts.preCompact) installed.push(scripts.preCompact);
10750
+ return {
10751
+ editor: "claude",
10752
+ installed,
10753
+ hooksDir: getHooksDir()
10754
+ };
10755
+ }
10756
+ case "cline": {
10757
+ const scripts = await installClineHookScripts({ scope, projectPath });
10758
+ return {
10759
+ editor: "cline",
10760
+ installed: [scripts.preToolUse, scripts.userPromptSubmit],
10761
+ hooksDir: getClineHooksDir(scope, projectPath)
10762
+ };
10763
+ }
10764
+ case "roo": {
10765
+ const scripts = await installRooCodeHookScripts({ scope, projectPath });
10766
+ return {
10767
+ editor: "roo",
10768
+ installed: [scripts.preToolUse, scripts.userPromptSubmit],
10769
+ hooksDir: getRooCodeHooksDir(scope, projectPath)
10770
+ };
10771
+ }
10772
+ case "kilo": {
10773
+ const scripts = await installKiloCodeHookScripts({ scope, projectPath });
10774
+ return {
10775
+ editor: "kilo",
10776
+ installed: [scripts.preToolUse, scripts.userPromptSubmit],
10777
+ hooksDir: getKiloCodeHooksDir(scope, projectPath)
10778
+ };
10779
+ }
10780
+ case "cursor": {
10781
+ const scripts = await installCursorHookScripts();
10782
+ return {
10783
+ editor: "cursor",
10784
+ installed: [scripts.preToolUse, scripts.beforeSubmit],
10785
+ hooksDir: getCursorHooksDir()
10786
+ };
10787
+ }
10788
+ default:
10789
+ throw new Error(`Unsupported editor: ${editor}`);
10790
+ }
10791
+ }
10792
+ async function installAllEditorHooks(options) {
10793
+ const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
10794
+ const results = [];
10795
+ for (const editor of editors) {
10796
+ try {
10797
+ const result = await installEditorHooks({
10798
+ editor,
10799
+ scope: options.scope,
10800
+ projectPath: options.projectPath,
10801
+ includePreCompact: options.includePreCompact
10802
+ });
10803
+ results.push(result);
10804
+ } catch (error) {
10805
+ console.error(`Failed to install hooks for ${editor}:`, error);
10806
+ }
10807
+ }
10808
+ return results;
10809
+ }
10052
10810
 
10053
10811
  // src/token-savings.ts
10054
10812
  var TOKEN_SAVINGS_FORMULA_VERSION = 1;
@@ -10114,6 +10872,54 @@ function trackToolTokenSavings(client, tool, contextText, params, extraMetadata)
10114
10872
  }
10115
10873
 
10116
10874
  // src/tools.ts
10875
+ var LOG_LEVEL = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
10876
+ var LOG_QUIET = LOG_LEVEL === "quiet";
10877
+ var LOG_VERBOSE = LOG_LEVEL === "verbose";
10878
+ var TOOL_DISPLAY_NAMES = {
10879
+ session_init: "init",
10880
+ session: "session",
10881
+ context_smart: "context",
10882
+ search: "search",
10883
+ memory: "memory",
10884
+ graph: "graph",
10885
+ ai: "ai",
10886
+ generate_rules: "rules",
10887
+ generate_editor_rules: "rules",
10888
+ help: "help",
10889
+ workspace_bootstrap: "bootstrap",
10890
+ workspace: "workspace",
10891
+ github: "github",
10892
+ slack: "slack",
10893
+ notion: "notion",
10894
+ jira: "jira",
10895
+ ingest: "ingest",
10896
+ // Hide internal tools
10897
+ session_capture_smart: "",
10898
+ session_restore_context: ""
10899
+ };
10900
+ function getToolDisplayName(toolName) {
10901
+ if (toolName in TOOL_DISPLAY_NAMES) {
10902
+ return TOOL_DISPLAY_NAMES[toolName];
10903
+ }
10904
+ return toolName.replace(/_/g, " ").replace(/^mcp__contextstream__/, "");
10905
+ }
10906
+ function logTool(toolName, status, details) {
10907
+ if (LOG_QUIET && status !== "error") return;
10908
+ const displayName = getToolDisplayName(toolName);
10909
+ if (!displayName) return;
10910
+ const icon = status === "start" ? "\u25E6" : status === "done" ? "\u2713" : "\u2717";
10911
+ const message = details ? `${icon} ${displayName}: ${details}` : `${icon} ${displayName}`;
10912
+ console.error(message);
10913
+ }
10914
+ function logSystem(message, level = "info") {
10915
+ if (LOG_QUIET && level === "info") return;
10916
+ const prefix = level === "error" ? "\u2717" : level === "warn" ? "!" : "\u2022";
10917
+ console.error(`${prefix} ${message}`);
10918
+ }
10919
+ function logDebug(message) {
10920
+ if (!LOG_VERBOSE) return;
10921
+ console.error(`[DEBUG] ${message}`);
10922
+ }
10117
10923
  var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
10118
10924
  var recentLessonCaptures = /* @__PURE__ */ new Map();
10119
10925
  var SEARCH_RULES_REMINDER_ENABLED = process.env.CONTEXTSTREAM_SEARCH_REMINDER?.toLowerCase() !== "false";
@@ -10124,9 +10930,75 @@ BEFORE using EnterPlanMode/Task(Plan) \u2192 call mcp__contextstream__session(ac
10124
10930
  Local tools ONLY if ContextStream returns 0 results after retry.
10125
10931
  `.trim();
10126
10932
  var LESSONS_REMINDER_PREFIX = `
10127
- \u26A0\uFE0F [LESSONS - REVIEW BEFORE CHANGES]
10128
- Past mistakes found that may be relevant. STOP and review before proceeding:
10933
+ \u{1F6A8} [LESSONS_WARNING] Past Mistakes Found - READ BEFORE PROCEEDING!
10934
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
10935
+ \u26A0\uFE0F IMPORTANT: You MUST review these lessons and tell the user about relevant ones.
10936
+ These are mistakes from past sessions that you should NOT repeat.
10129
10937
  `.trim();
10938
+ var RISKY_ACTION_KEYWORDS = [
10939
+ // Code changes
10940
+ "refactor",
10941
+ "rewrite",
10942
+ "restructure",
10943
+ "reorganize",
10944
+ "migrate",
10945
+ "delete",
10946
+ "remove",
10947
+ "drop",
10948
+ "deprecate",
10949
+ // Database
10950
+ "database",
10951
+ "migration",
10952
+ "schema",
10953
+ "sql",
10954
+ // Deployment
10955
+ "deploy",
10956
+ "release",
10957
+ "production",
10958
+ "prod",
10959
+ // API changes
10960
+ "api",
10961
+ "endpoint",
10962
+ "breaking change",
10963
+ // Architecture
10964
+ "architecture",
10965
+ "design",
10966
+ "pattern",
10967
+ // Testing
10968
+ "test",
10969
+ "testing",
10970
+ // Security
10971
+ "auth",
10972
+ "security",
10973
+ "permission",
10974
+ "credential",
10975
+ "access",
10976
+ "token",
10977
+ "secret",
10978
+ // Version control
10979
+ "git",
10980
+ "commit",
10981
+ "merge",
10982
+ "rebase",
10983
+ "push",
10984
+ "force",
10985
+ // Infrastructure
10986
+ "config",
10987
+ "environment",
10988
+ "env",
10989
+ "docker",
10990
+ "kubernetes",
10991
+ "k8s",
10992
+ // Performance
10993
+ "performance",
10994
+ "optimize",
10995
+ "cache",
10996
+ "memory"
10997
+ ];
10998
+ function detectRiskyActions(userMessage) {
10999
+ const messageLower = userMessage.toLowerCase();
11000
+ return RISKY_ACTION_KEYWORDS.filter((keyword) => messageLower.includes(keyword));
11001
+ }
10130
11002
  function generateLessonsReminder(result) {
10131
11003
  const lessons = result.lessons;
10132
11004
  if (!lessons || lessons.length === 0) {
@@ -10141,7 +11013,8 @@ function generateLessonsReminder(result) {
10141
11013
  return `
10142
11014
 
10143
11015
  ${LESSONS_REMINDER_PREFIX}
10144
- ${lessonLines.join("\n")}`;
11016
+ ${lessonLines.join("\n")}
11017
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
10145
11018
  }
10146
11019
  function generateRulesUpdateWarning(rulesNotice) {
10147
11020
  if (!rulesNotice || rulesNotice.status !== "behind" && rulesNotice.status !== "missing") {
@@ -10164,12 +11037,16 @@ function generateVersionUpdateWarning(versionNotice) {
10164
11037
  return "";
10165
11038
  }
10166
11039
  return `
10167
- \u{1F6A8} [MCP SERVER OUTDATED - UPDATE RECOMMENDED]
10168
- Current: ${versionNotice.current} \u2192 Latest: ${versionNotice.latest}
10169
- New version may include critical bug fixes, performance improvements, and new features.
11040
+ \u{1F6A8} [VERSION_NOTICE] MCP Server Update Available!
11041
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
11042
+ Version: ${versionNotice.current} \u2192 ${versionNotice.latest}
11043
+
11044
+ \u26A0\uFE0F IMPORTANT: You MUST tell the user about this update IMMEDIATELY.
11045
+ Do not proceed with other work until you have informed the user.
10170
11046
 
10171
- **UPDATE NOW:** Run \`${versionNotice.upgrade_command || "npm update @contextstream/mcp-server"}\`
10172
- Then restart Claude Code to use the new version.
11047
+ Update command: \`${versionNotice.upgrade_command || "npm update -g @contextstream/mcp-server"}\`
11048
+ After updating, restart the AI tool to use the new version.
11049
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
10173
11050
  `.trim();
10174
11051
  }
10175
11052
  var DEFAULT_PARAM_DESCRIPTIONS = {
@@ -10225,7 +11102,6 @@ var RULES_PROJECT_FILES = {
10225
11102
  codex: "AGENTS.md",
10226
11103
  claude: "CLAUDE.md",
10227
11104
  cursor: ".cursorrules",
10228
- windsurf: ".windsurfrules",
10229
11105
  cline: ".clinerules",
10230
11106
  kilo: path6.join(".kilocode", "rules", "contextstream.md"),
10231
11107
  roo: path6.join(".roo", "rules", "contextstream.md"),
@@ -10233,7 +11109,6 @@ var RULES_PROJECT_FILES = {
10233
11109
  };
10234
11110
  var RULES_GLOBAL_FILES = {
10235
11111
  codex: [path6.join(homedir3(), ".codex", "AGENTS.md")],
10236
- windsurf: [path6.join(homedir3(), ".codeium", "windsurf", "memories", "global_rules.md")],
10237
11112
  kilo: [path6.join(homedir3(), ".kilocode", "rules", "contextstream.md")],
10238
11113
  roo: [path6.join(homedir3(), ".roo", "rules", "contextstream.md")]
10239
11114
  };
@@ -10273,7 +11148,6 @@ function detectEditorFromClientName(clientName) {
10273
11148
  if (!clientName) return null;
10274
11149
  const normalized = clientName.toLowerCase().trim();
10275
11150
  if (normalized.includes("cursor")) return "cursor";
10276
- if (normalized.includes("windsurf") || normalized.includes("codeium")) return "windsurf";
10277
11151
  if (normalized.includes("claude")) return "claude";
10278
11152
  if (normalized.includes("cline")) return "cline";
10279
11153
  if (normalized.includes("kilo")) return "kilo";
@@ -10435,7 +11309,6 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
10435
11309
  /^#\s+codex cli instructions$/i,
10436
11310
  /^#\s+claude code instructions$/i,
10437
11311
  /^#\s+cursor rules$/i,
10438
- /^#\s+windsurf rules$/i,
10439
11312
  /^#\s+cline rules$/i,
10440
11313
  /^#\s+kilo code rules$/i,
10441
11314
  /^#\s+roo code rules$/i,
@@ -11030,6 +11903,7 @@ var ALL_INTEGRATION_TOOLS = /* @__PURE__ */ new Set([
11030
11903
  ...CROSS_INTEGRATION_TOOLS
11031
11904
  ]);
11032
11905
  var AUTO_HIDE_INTEGRATIONS = process.env.CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS !== "false";
11906
+ var RESTORE_CONTEXT_DEFAULT = process.env.CONTEXTSTREAM_RESTORE_CONTEXT !== "false";
11033
11907
  var TOKEN_SENSITIVE_CLIENTS = /* @__PURE__ */ new Set([
11034
11908
  "claude",
11035
11909
  "claude-code",
@@ -11419,9 +12293,7 @@ function resolveToolFilter() {
11419
12293
  if (allowlistRaw) {
11420
12294
  const allowlist = parseToolList(allowlistRaw);
11421
12295
  if (allowlist.size === 0) {
11422
- console.error(
11423
- "[ContextStream] CONTEXTSTREAM_TOOL_ALLOWLIST is empty; using standard toolset."
11424
- );
12296
+ logDebug("TOOL_ALLOWLIST is empty; using standard toolset");
11425
12297
  return { allowlist: STANDARD_TOOLSET, source: "standard", autoDetected: false };
11426
12298
  }
11427
12299
  return { allowlist, source: "allowlist", autoDetected: false };
@@ -11432,9 +12304,7 @@ function resolveToolFilter() {
11432
12304
  if (clientDetectedFromEnv && AUTO_TOOLSET_ENABLED) {
11433
12305
  const recommended = getRecommendedToolset(void 0, true);
11434
12306
  if (recommended) {
11435
- console.error(
11436
- "[ContextStream] Detected Claude Code via environment. Using light toolset for optimal token usage."
11437
- );
12307
+ logDebug("Detected Claude Code, using light toolset");
11438
12308
  return { allowlist: recommended, source: "auto-claude", autoDetected: true };
11439
12309
  }
11440
12310
  }
@@ -11443,12 +12313,10 @@ function resolveToolFilter() {
11443
12313
  if (toolsetRaw.trim().toLowerCase() === "auto") {
11444
12314
  clientDetectedFromEnv = detectClaudeCodeFromEnv();
11445
12315
  if (clientDetectedFromEnv) {
11446
- console.error("[ContextStream] TOOLSET=auto: Detected Claude Code, using light toolset.");
12316
+ logDebug("TOOLSET=auto: Detected Claude Code, using light toolset");
11447
12317
  return { allowlist: LIGHT_TOOLSET, source: "auto-claude", autoDetected: true };
11448
12318
  }
11449
- console.error(
11450
- "[ContextStream] TOOLSET=auto: Will adjust toolset based on MCP client (currently standard)."
11451
- );
12319
+ logDebug("TOOLSET=auto: Will adjust based on MCP client");
11452
12320
  return { allowlist: STANDARD_TOOLSET, source: "auto-pending", autoDetected: true };
11453
12321
  }
11454
12322
  const key = toolsetRaw.trim().toLowerCase();
@@ -11459,9 +12327,7 @@ function resolveToolFilter() {
11459
12327
  }
11460
12328
  return { allowlist: resolved, source: key, autoDetected: false };
11461
12329
  }
11462
- console.error(
11463
- `[ContextStream] Unknown CONTEXTSTREAM_TOOLSET "${toolsetRaw}". Using standard toolset.`
11464
- );
12330
+ logDebug(`Unknown TOOLSET "${toolsetRaw}"; using standard`);
11465
12331
  return { allowlist: STANDARD_TOOLSET, source: "standard", autoDetected: false };
11466
12332
  }
11467
12333
  function formatContent(data, forceFormat) {
@@ -11559,22 +12425,16 @@ function isDuplicateLessonCapture(signature) {
11559
12425
  }
11560
12426
  function setupClientDetection(server) {
11561
12427
  if (!AUTO_TOOLSET_ENABLED) {
11562
- console.error(
11563
- "[ContextStream] Auto-toolset: DISABLED (set CONTEXTSTREAM_AUTO_TOOLSET=true to enable)"
11564
- );
12428
+ logDebug("Auto-toolset: DISABLED");
11565
12429
  return;
11566
12430
  }
11567
12431
  if (clientDetectedFromEnv) {
11568
- console.error(
11569
- "[ContextStream] Client detection: Already detected Claude Code from environment"
11570
- );
12432
+ logDebug("Client detection: Already detected from environment");
11571
12433
  return;
11572
12434
  }
11573
12435
  const lowLevelServer = server.server;
11574
12436
  if (!lowLevelServer) {
11575
- console.error(
11576
- "[ContextStream] Warning: Could not access low-level MCP server for client detection"
11577
- );
12437
+ logDebug("Warning: Could not access low-level MCP server for client detection");
11578
12438
  return;
11579
12439
  }
11580
12440
  lowLevelServer.oninitialized = () => {
@@ -11584,23 +12444,21 @@ function setupClientDetection(server) {
11584
12444
  detectedClientInfo = clientVersion;
11585
12445
  const clientName = clientVersion.name || "unknown";
11586
12446
  const clientVer = clientVersion.version || "unknown";
11587
- console.error(`[ContextStream] MCP Client detected: ${clientName} v${clientVer}`);
12447
+ logDebug(`MCP Client detected: ${clientName} v${clientVer}`);
11588
12448
  if (isTokenSensitiveClient(clientName)) {
11589
- console.error(
11590
- "[ContextStream] Token-sensitive client detected. Consider using CONTEXTSTREAM_TOOLSET=light for optimal performance."
11591
- );
12449
+ logDebug("Token-sensitive client detected");
11592
12450
  try {
11593
12451
  lowLevelServer.sendToolsListChanged?.();
11594
- console.error("[ContextStream] Emitted tools/list_changed notification");
12452
+ logDebug("Emitted tools/list_changed notification");
11595
12453
  } catch (error) {
11596
12454
  }
11597
12455
  }
11598
12456
  }
11599
12457
  } catch (error) {
11600
- console.error("[ContextStream] Error in client detection callback:", error);
12458
+ logSystem(`Error in client detection: ${error}`, "error");
11601
12459
  }
11602
12460
  };
11603
- console.error("[ContextStream] Client detection: Callback registered for MCP initialize");
12461
+ logDebug("Client detection callback registered");
11604
12462
  }
11605
12463
  function registerTools(server, client, sessionManager) {
11606
12464
  const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
@@ -11608,69 +12466,31 @@ function registerTools(server, client, sessionManager) {
11608
12466
  const toolAllowlist = toolFilter.allowlist;
11609
12467
  if (toolAllowlist) {
11610
12468
  const source = toolFilter.source;
11611
- const autoNote = toolFilter.autoDetected ? " (auto-detected)" : "";
11612
- const hint = source === "light" || source === "auto-claude" ? " Set CONTEXTSTREAM_TOOLSET=standard or complete for more tools." : source === "standard" ? " Set CONTEXTSTREAM_TOOLSET=complete for all tools." : source === "auto-pending" ? " Toolset may be adjusted when MCP client is detected." : "";
11613
- console.error(
11614
- `[ContextStream] Toolset: ${source} (${toolAllowlist.size} tools)${autoNote}.${hint}`
11615
- );
12469
+ logDebug(`Toolset: ${source} (${toolAllowlist.size} tools)`);
11616
12470
  } else {
11617
- console.error(`[ContextStream] Toolset: complete (all tools).`);
12471
+ logDebug("Toolset: complete (all tools)");
11618
12472
  }
11619
12473
  if (AUTO_TOOLSET_ENABLED) {
11620
- if (clientDetectedFromEnv) {
11621
- console.error("[ContextStream] Auto-toolset: ACTIVE (Claude Code detected from environment)");
11622
- } else {
11623
- console.error("[ContextStream] Auto-toolset: ENABLED (will detect MCP client on initialize)");
11624
- }
11625
- }
11626
- if (AUTO_HIDE_INTEGRATIONS) {
11627
- console.error(
11628
- `[ContextStream] Integration auto-hide: ENABLED (${ALL_INTEGRATION_TOOLS.size} tools hidden until integrations connected)`
11629
- );
11630
- console.error("[ContextStream] Set CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS=false to disable.");
11631
- } else {
11632
- console.error("[ContextStream] Integration auto-hide: disabled");
11633
- }
11634
- if (COMPACT_SCHEMA_ENABLED) {
11635
- console.error("[ContextStream] Schema mode: COMPACT (shorter descriptions, minimal params)");
11636
- } else {
11637
- console.error(
11638
- "[ContextStream] Schema mode: full (set CONTEXTSTREAM_SCHEMA_MODE=compact to reduce token overhead)"
11639
- );
11640
- }
11641
- if (PROGRESSIVE_MODE) {
11642
- const coreBundle = TOOL_BUNDLES.core;
11643
- console.error(
11644
- `[ContextStream] Progressive mode: ENABLED (starting with ${coreBundle.size} core tools)`
11645
- );
11646
- console.error(
11647
- "[ContextStream] Use tools_enable_bundle to unlock additional tool bundles dynamically."
11648
- );
11649
- }
11650
- if (ROUTER_MODE) {
11651
- console.error(
11652
- "[ContextStream] Router mode: ENABLED (all operations accessed via contextstream/contextstream_help)"
11653
- );
11654
- console.error(
11655
- "[ContextStream] Only 2 tools registered. Use contextstream_help to see available operations."
11656
- );
12474
+ logDebug(clientDetectedFromEnv ? "Auto-toolset: ACTIVE" : "Auto-toolset: ENABLED");
11657
12475
  }
11658
- if (COMPACT_OUTPUT) {
11659
- console.error(
11660
- "[ContextStream] Output format: COMPACT (minified JSON, ~30% fewer tokens per response)"
11661
- );
12476
+ if (AUTO_HIDE_INTEGRATIONS) {
12477
+ logDebug(`Integration auto-hide: ENABLED (${ALL_INTEGRATION_TOOLS.size} tools hidden)`);
12478
+ }
12479
+ if (COMPACT_SCHEMA_ENABLED) {
12480
+ logDebug("Schema mode: COMPACT");
11662
12481
  } else {
11663
- console.error(
11664
- "[ContextStream] Output format: pretty (set CONTEXTSTREAM_OUTPUT_FORMAT=compact for fewer tokens)"
11665
- );
12482
+ logDebug("Schema mode: full");
12483
+ }
12484
+ if (PROGRESSIVE_MODE) {
12485
+ const coreBundle = TOOL_BUNDLES.core;
12486
+ logDebug(`Progressive mode: ENABLED (${coreBundle.size} core tools)`);
12487
+ }
12488
+ if (ROUTER_MODE) {
12489
+ logDebug("Router mode: ENABLED");
11666
12490
  }
12491
+ logDebug(COMPACT_OUTPUT ? "Output: COMPACT" : "Output: pretty");
11667
12492
  if (CONSOLIDATED_MODE) {
11668
- console.error(
11669
- `[ContextStream] Consolidated mode: ENABLED (~${CONSOLIDATED_TOOLS.size} domain tools, ~75% token reduction)`
11670
- );
11671
- console.error("[ContextStream] Set CONTEXTSTREAM_CONSOLIDATED=false to use individual tools.");
11672
- } else {
11673
- console.error("[ContextStream] Consolidated mode: disabled (using individual tools)");
12493
+ logDebug(`Consolidated mode: ENABLED (~${CONSOLIDATED_TOOLS.size} domain tools)`);
11674
12494
  }
11675
12495
  const serverRef = server;
11676
12496
  const defaultProTools = /* @__PURE__ */ new Set([
@@ -11789,12 +12609,10 @@ function registerTools(server, client, sessionManager) {
11789
12609
  notion: notionConnected,
11790
12610
  workspaceId
11791
12611
  };
11792
- console.error(
11793
- `[ContextStream] Integration status: Slack=${slackConnected}, GitHub=${githubConnected}, Notion=${notionConnected}`
11794
- );
12612
+ logDebug(`Integrations: Slack=${slackConnected}, GitHub=${githubConnected}, Notion=${notionConnected}`);
11795
12613
  return { slack: slackConnected, github: githubConnected, notion: notionConnected };
11796
12614
  } catch (error) {
11797
- console.error("[ContextStream] Failed to check integration status:", error);
12615
+ logDebug(`Failed to check integration status: ${error}`);
11798
12616
  return { slack: false, github: false, notion: false };
11799
12617
  }
11800
12618
  }
@@ -11815,11 +12633,9 @@ function registerTools(server, client, sessionManager) {
11815
12633
  try {
11816
12634
  server.server?.sendToolsListChanged?.();
11817
12635
  toolsListChangedNotified = true;
11818
- console.error(
11819
- "[ContextStream] Emitted tools/list_changed notification (integrations detected)"
11820
- );
12636
+ logDebug("Emitted tools/list_changed notification (integrations detected)");
11821
12637
  } catch (error) {
11822
- console.error("[ContextStream] Failed to emit tools/list_changed:", error);
12638
+ logDebug(`Failed to emit tools/list_changed: ${error}`);
11823
12639
  }
11824
12640
  }
11825
12641
  }
@@ -12087,7 +12903,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
12087
12903
  lowLevelServer.sendToolsListChanged?.();
12088
12904
  } catch {
12089
12905
  }
12090
- console.error(`[ContextStream] Bundle '${bundleName}' enabled with ${toolsEnabled} tools.`);
12906
+ logSystem(`bundle '${bundleName}' enabled (${toolsEnabled} tools)`);
12091
12907
  return {
12092
12908
  success: true,
12093
12909
  message: `Enabled bundle '${bundleName}' with ${toolsEnabled} tools.`,
@@ -12196,33 +13012,27 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
12196
13012
  if (options.preflight) {
12197
13013
  const fileCheck = await countIndexableFiles(resolvedPath, { maxFiles: 1 });
12198
13014
  if (fileCheck.count === 0) {
12199
- console.error(
12200
- `[ContextStream] No indexable files found in ${resolvedPath}. Skipping ingest.`
12201
- );
13015
+ logDebug(`No indexable files in ${resolvedPath}`);
12202
13016
  return;
12203
13017
  }
12204
13018
  }
12205
13019
  let totalIndexed = 0;
12206
13020
  let batchCount = 0;
12207
- console.error(
12208
- `[ContextStream] Starting background ingestion for project ${projectId} from ${resolvedPath}`
12209
- );
13021
+ logTool("ingest", "start", `indexing ${resolvedPath}`);
12210
13022
  for await (const batch of readAllFilesInBatches(resolvedPath, { batchSize: 50 })) {
12211
13023
  const result = await client.ingestFiles(projectId, batch, ingestOptions);
12212
13024
  totalIndexed += result.data?.files_indexed ?? batch.length;
12213
13025
  batchCount++;
12214
13026
  }
12215
- console.error(
12216
- `[ContextStream] Completed background ingestion: ${totalIndexed} files in ${batchCount} batches`
12217
- );
13027
+ logTool("ingest", "done", `${totalIndexed} files`);
12218
13028
  try {
12219
13029
  await markProjectIndexed(resolvedPath, { project_id: projectId });
12220
- console.error(`[ContextStream] Marked project as indexed: ${resolvedPath}`);
13030
+ logDebug(`Marked project as indexed: ${resolvedPath}`);
12221
13031
  } catch (markError) {
12222
- console.error(`[ContextStream] Failed to mark project as indexed:`, markError);
13032
+ logDebug(`Failed to mark project as indexed: ${markError}`);
12223
13033
  }
12224
13034
  } catch (error) {
12225
- console.error(`[ContextStream] Ingestion failed:`, error);
13035
+ logTool("ingest", "error", `${error}`);
12226
13036
  }
12227
13037
  })();
12228
13038
  }
@@ -12467,9 +13277,7 @@ Use contextstream_help({}) to see available operations.`
12467
13277
  };
12468
13278
  }
12469
13279
  );
12470
- console.error(
12471
- `[ContextStream] Router mode: Registered 2 meta-tools, ${operationsRegistry.size} operations available via dispatcher.`
12472
- );
13280
+ logDebug(`Router mode: 2 meta-tools, ${operationsRegistry.size} operations`);
12473
13281
  }
12474
13282
  registerTool(
12475
13283
  "workspaces_list",
@@ -12629,7 +13437,7 @@ Access: Free`,
12629
13437
  rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
12630
13438
  }
12631
13439
  } catch (err) {
12632
- console.error("[ContextStream] Failed to write project config:", err);
13440
+ logDebug(`Failed to write project config: ${err}`);
12633
13441
  }
12634
13442
  }
12635
13443
  const response = {
@@ -13145,10 +13953,7 @@ Access: Free`,
13145
13953
  const stats = await client.projectStatistics(projectId);
13146
13954
  estimate = estimateGraphIngestMinutes(stats);
13147
13955
  } catch (error) {
13148
- console.error(
13149
- "[ContextStream] Failed to fetch project statistics for graph ingest estimate:",
13150
- error
13151
- );
13956
+ logDebug(`Failed to fetch project statistics for graph estimate: ${error}`);
13152
13957
  }
13153
13958
  const result = await client.graphIngest({ project_id: projectId, wait });
13154
13959
  const estimateText = estimate ? `Estimated time: ${estimate.min}-${estimate.max} min${estimate.basis ? ` (based on ${estimate.basis})` : ""}.` : "Estimated time varies with repo size.";
@@ -13824,7 +14629,7 @@ This does semantic search on the first message. You only need context_smart on s
13824
14629
  "If true, skip automatic project creation/matching. Use for parent folders containing multiple projects where you want workspace-level context but no project-specific context."
13825
14630
  ),
13826
14631
  is_post_compact: external_exports.boolean().optional().describe(
13827
- "Set to true when resuming after conversation compaction. This prioritizes session_snapshot restoration and recent decisions."
14632
+ "Controls context restoration from recent snapshots. Defaults to true (always restores). Set to false to skip restoration. Can also be controlled via CONTEXTSTREAM_RESTORE_CONTEXT environment variable."
13828
14633
  )
13829
14634
  })
13830
14635
  },
@@ -13845,19 +14650,22 @@ This does semantic search on the first message. You only need context_smart on s
13845
14650
  }
13846
14651
  const result = await client.initSession(input, ideRoots);
13847
14652
  result.tools_hint = getCoreToolsHint();
13848
- if (input.is_post_compact) {
14653
+ const shouldRestoreContext = input.is_post_compact ?? RESTORE_CONTEXT_DEFAULT;
14654
+ if (shouldRestoreContext) {
14655
+ result.is_post_compact = true;
13849
14656
  const workspaceIdForRestore = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
13850
14657
  const projectIdForRestore = typeof result.project_id === "string" ? result.project_id : void 0;
13851
14658
  if (workspaceIdForRestore) {
13852
14659
  try {
13853
- const snapshotSearch = await client.searchEvents({
14660
+ const listResult = await client.listMemoryEvents({
13854
14661
  workspace_id: workspaceIdForRestore,
13855
14662
  project_id: projectIdForRestore,
13856
- query: "session_snapshot",
13857
- event_types: ["session_snapshot"],
13858
- limit: 1
14663
+ limit: 50
13859
14664
  });
13860
- const snapshots = snapshotSearch?.data?.results || snapshotSearch?.results || snapshotSearch?.data || [];
14665
+ const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
14666
+ const snapshots = allEvents.filter(
14667
+ (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
14668
+ );
13861
14669
  if (snapshots && snapshots.length > 0) {
13862
14670
  const latestSnapshot = snapshots[0];
13863
14671
  let snapshotData;
@@ -13866,19 +14674,58 @@ This does semantic search on the first message. You only need context_smart on s
13866
14674
  } catch {
13867
14675
  snapshotData = { conversation_summary: latestSnapshot.content };
13868
14676
  }
14677
+ const prevSessionId = snapshotData.session_id || latestSnapshot.session_id;
14678
+ const sessionLinking = {};
14679
+ if (prevSessionId) {
14680
+ sessionLinking.previous_session_id = prevSessionId;
14681
+ const workingOn = [];
14682
+ const activeFiles = snapshotData.active_files;
14683
+ const lastTools = snapshotData.last_tools;
14684
+ if (activeFiles && activeFiles.length > 0) {
14685
+ workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
14686
+ }
14687
+ if (lastTools && lastTools.length > 0) {
14688
+ const toolCounts = lastTools.reduce((acc, tool) => {
14689
+ acc[tool] = (acc[tool] || 0) + 1;
14690
+ return acc;
14691
+ }, {});
14692
+ const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
14693
+ workingOn.push(`Recent tools: ${topTools.join(", ")}`);
14694
+ }
14695
+ if (workingOn.length > 0) {
14696
+ sessionLinking.previous_session_summary = workingOn.join("; ");
14697
+ }
14698
+ const relatedSessionIds = /* @__PURE__ */ new Set();
14699
+ snapshots.forEach((s) => {
14700
+ let sData;
14701
+ try {
14702
+ sData = JSON.parse(s.content || "{}");
14703
+ } catch {
14704
+ sData = {};
14705
+ }
14706
+ const sSessionId = sData.session_id || s.session_id;
14707
+ if (sSessionId && sSessionId !== prevSessionId) {
14708
+ relatedSessionIds.add(sSessionId);
14709
+ }
14710
+ });
14711
+ if (relatedSessionIds.size > 0) {
14712
+ sessionLinking.related_sessions = Array.from(relatedSessionIds);
14713
+ }
14714
+ }
13869
14715
  result.restored_context = {
13870
14716
  snapshot_id: latestSnapshot.id,
13871
14717
  captured_at: snapshotData.captured_at || latestSnapshot.created_at,
14718
+ session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
13872
14719
  ...snapshotData
13873
14720
  };
13874
14721
  result.is_post_compact = true;
13875
- result.post_compact_hint = "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
14722
+ result.post_compact_hint = prevSessionId ? `Session restored from session ${prevSessionId}. Review 'restored_context' to continue where you left off.` : "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
13876
14723
  } else {
13877
14724
  result.is_post_compact = true;
13878
14725
  result.post_compact_hint = "Post-compaction session started, but no snapshots found. Use context_smart to retrieve relevant context.";
13879
14726
  }
13880
14727
  } catch (err) {
13881
- console.error("[ContextStream] Failed to restore post-compact context:", err);
14728
+ logDebug(`Failed to restore post-compact context: ${err}`);
13882
14729
  result.is_post_compact = true;
13883
14730
  result.post_compact_hint = "Post-compaction session started. Snapshot restoration failed, use context_smart for context.";
13884
14731
  }
@@ -13919,10 +14766,7 @@ This does semantic search on the first message. You only need context_smart on s
13919
14766
  hint: intStatus.slack || intStatus.github ? "Integration tools are now available in the tool list." : "Connect integrations at https://contextstream.io/settings/integrations to enable Slack/GitHub tools."
13920
14767
  };
13921
14768
  } catch (error) {
13922
- console.error(
13923
- "[ContextStream] Failed to check integration status in session_init:",
13924
- error
13925
- );
14769
+ logDebug(`Failed to check integration status: ${error}`);
13926
14770
  }
13927
14771
  }
13928
14772
  result.modes = {
@@ -14017,7 +14861,7 @@ ${benefitsList}` : "",
14017
14861
  const projectId = typeof result.project_id === "string" ? result.project_id : void 0;
14018
14862
  const projectName = typeof result.project_name === "string" ? result.project_name : void 0;
14019
14863
  markProjectIndexed(folderPathForRules, { project_id: projectId, project_name: projectName }).catch(
14020
- (err) => console.error("[ContextStream] Failed to mark project as indexed:", err)
14864
+ (err) => logDebug(`Failed to mark project as indexed: ${err}`)
14021
14865
  );
14022
14866
  }
14023
14867
  if (noticeLines.length > 0) {
@@ -14133,7 +14977,7 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
14133
14977
  workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
14134
14978
  create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
14135
14979
  generate_editor_rules: external_exports.boolean().optional().describe(
14136
- "Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"
14980
+ "Generate AI editor rules for Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"
14137
14981
  ),
14138
14982
  overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
14139
14983
  })
@@ -14545,14 +15389,16 @@ Use this in combination with session_init(is_post_compact=true) for seamless con
14545
15389
  structuredContent: toStructured(response2)
14546
15390
  };
14547
15391
  }
14548
- const searchResult = await client.searchEvents({
15392
+ const listResult = await client.listMemoryEvents({
14549
15393
  workspace_id: workspaceId,
14550
15394
  project_id: projectId,
14551
- query: "session_snapshot",
14552
- event_types: ["session_snapshot"],
14553
- limit: input.max_snapshots || 1
15395
+ limit: 50
15396
+ // Fetch more to filter
14554
15397
  });
14555
- const events = searchResult?.data?.results || searchResult?.results || searchResult?.data || [];
15398
+ const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
15399
+ const events = allEvents.filter(
15400
+ (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
15401
+ ).slice(0, input.max_snapshots || 1);
14556
15402
  if (!events || events.length === 0) {
14557
15403
  return {
14558
15404
  content: [
@@ -14923,7 +15769,7 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
14923
15769
  "generate_rules",
14924
15770
  {
14925
15771
  title: "Generate ContextStream rules",
14926
- description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
15772
+ description: `Generate AI rule files for editors (Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
14927
15773
  Defaults to the current project folder; no folder_path required when run from a project.
14928
15774
  Supported editors: ${getAvailableEditors().join(", ")}`,
14929
15775
  inputSchema: external_exports.object({
@@ -14931,7 +15777,6 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
14931
15777
  editors: external_exports.array(
14932
15778
  external_exports.enum([
14933
15779
  "codex",
14934
- "windsurf",
14935
15780
  "cursor",
14936
15781
  "cline",
14937
15782
  "kilo",
@@ -15020,34 +15865,70 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
15020
15865
  const globalPrompt = input.apply_global ? "Global rule update complete." : globalTargets.length > 0 ? "Apply rules globally too? Re-run with apply_global: true." : "No global rule locations are known for these editors.";
15021
15866
  let hooksResults;
15022
15867
  let hooksPrompt;
15023
- const hasClaude = editors.includes("claude");
15024
- const shouldInstallHooks = hasClaude && input.install_hooks !== false;
15868
+ const editorHookMap = {
15869
+ claude: "claude",
15870
+ cline: "cline",
15871
+ roo: "roo",
15872
+ kilo: "kilo",
15873
+ cursor: "cursor"
15874
+ };
15875
+ const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
15876
+ const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
15025
15877
  if (shouldInstallHooks) {
15026
15878
  try {
15027
15879
  if (input.dry_run) {
15028
- hooksResults = [
15029
- { file: "~/.claude/hooks/contextstream-redirect.py", status: "dry run - would create" },
15030
- { file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
15031
- { file: "~/.claude/settings.json", status: "dry run - would update" }
15032
- ];
15033
- if (input.include_pre_compact) {
15034
- hooksResults.push({ file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
15880
+ hooksResults = [];
15881
+ for (const editor of hookSupportedEditors) {
15882
+ if (editor === "claude") {
15883
+ hooksResults.push(
15884
+ { editor, file: "~/.claude/hooks/contextstream-redirect.py", status: "dry run - would create" },
15885
+ { editor, file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
15886
+ { editor, file: "~/.claude/settings.json", status: "dry run - would update" }
15887
+ );
15888
+ if (input.include_pre_compact) {
15889
+ hooksResults.push({ editor, file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
15890
+ }
15891
+ } else if (editor === "cline") {
15892
+ hooksResults.push(
15893
+ { editor, file: "~/Documents/Cline/Rules/Hooks/PreToolUse", status: "dry run - would create" },
15894
+ { editor, file: "~/Documents/Cline/Rules/Hooks/UserPromptSubmit", status: "dry run - would create" }
15895
+ );
15896
+ } else if (editor === "roo") {
15897
+ hooksResults.push(
15898
+ { editor, file: "~/.roo/hooks/PreToolUse", status: "dry run - would create" },
15899
+ { editor, file: "~/.roo/hooks/UserPromptSubmit", status: "dry run - would create" }
15900
+ );
15901
+ } else if (editor === "kilo") {
15902
+ hooksResults.push(
15903
+ { editor, file: "~/.kilocode/hooks/PreToolUse", status: "dry run - would create" },
15904
+ { editor, file: "~/.kilocode/hooks/UserPromptSubmit", status: "dry run - would create" }
15905
+ );
15906
+ } else if (editor === "cursor") {
15907
+ hooksResults.push(
15908
+ { editor, file: "~/.cursor/hooks/contextstream-pretooluse.py", status: "dry run - would create" },
15909
+ { editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
15910
+ { editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
15911
+ );
15912
+ }
15035
15913
  }
15036
15914
  } else {
15037
- const hookResult = await installClaudeCodeHooks({
15038
- scope: "user",
15915
+ hooksResults = [];
15916
+ const allHookResults = await installAllEditorHooks({
15917
+ scope: "global",
15918
+ editors: hookSupportedEditors,
15039
15919
  includePreCompact: input.include_pre_compact
15040
15920
  });
15041
- hooksResults = [
15042
- ...hookResult.scripts.map((f) => ({ file: f, status: "created" })),
15043
- ...hookResult.settings.map((f) => ({ file: f, status: "updated" }))
15044
- ];
15921
+ for (const result of allHookResults) {
15922
+ for (const file of result.installed) {
15923
+ hooksResults.push({ editor: result.editor, file, status: "created" });
15924
+ }
15925
+ }
15045
15926
  }
15046
15927
  } catch (err) {
15047
15928
  hooksResults = [{ file: "hooks", status: `error: ${err.message}` }];
15048
15929
  }
15049
- } else if (hasClaude && input.install_hooks === false) {
15050
- hooksPrompt = "Hooks skipped. Claude may use default tools instead of ContextStream search.";
15930
+ } else if (hookSupportedEditors.length > 0 && input.install_hooks === false) {
15931
+ hooksPrompt = "Hooks skipped. AI may use default tools instead of ContextStream search.";
15051
15932
  }
15052
15933
  const summary = {
15053
15934
  folder: folderPath,
@@ -15069,7 +15950,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
15069
15950
  "generate_editor_rules",
15070
15951
  {
15071
15952
  title: "Generate editor AI rules",
15072
- description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
15953
+ description: `Generate AI rule files for editors (Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
15073
15954
  These rules instruct the AI to automatically use ContextStream for memory and context.
15074
15955
  Supported editors: ${getAvailableEditors().join(", ")}`,
15075
15956
  inputSchema: external_exports.object({
@@ -15077,7 +15958,6 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
15077
15958
  editors: external_exports.array(
15078
15959
  external_exports.enum([
15079
15960
  "codex",
15080
- "windsurf",
15081
15961
  "cursor",
15082
15962
  "cline",
15083
15963
  "kilo",
@@ -15426,6 +16306,52 @@ This saves ~80% tokens compared to including full chat history.`,
15426
16306
  }
15427
16307
  sessionManager.addTokens(input.user_message);
15428
16308
  }
16309
+ let postCompactContext = "";
16310
+ let postCompactRestored = false;
16311
+ if (sessionManager && sessionManager.shouldRestorePostCompact() && workspaceId) {
16312
+ try {
16313
+ const listResult = await client.listMemoryEvents({
16314
+ workspace_id: workspaceId,
16315
+ project_id: projectId,
16316
+ limit: 20
16317
+ });
16318
+ const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
16319
+ const snapshotEvent = allEvents.find(
16320
+ (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.tags?.includes("session_snapshot")
16321
+ );
16322
+ if (snapshotEvent && snapshotEvent.content) {
16323
+ let snapshotData;
16324
+ try {
16325
+ snapshotData = JSON.parse(snapshotEvent.content);
16326
+ } catch {
16327
+ snapshotData = { conversation_summary: snapshotEvent.content };
16328
+ }
16329
+ const summary = snapshotData.conversation_summary || snapshotData.summary || "";
16330
+ const decisions = snapshotData.key_decisions || [];
16331
+ const unfinished = snapshotData.unfinished_work || snapshotData.pending_tasks || [];
16332
+ const files = snapshotData.active_files || [];
16333
+ const parts = [];
16334
+ parts.push("\u{1F4CB} [POST-COMPACTION CONTEXT RESTORED]");
16335
+ if (summary) parts.push(`Summary: ${summary}`);
16336
+ if (Array.isArray(decisions) && decisions.length > 0) {
16337
+ parts.push(`Decisions: ${decisions.slice(0, 5).join("; ")}`);
16338
+ }
16339
+ if (Array.isArray(unfinished) && unfinished.length > 0) {
16340
+ parts.push(`Unfinished: ${unfinished.slice(0, 3).join("; ")}`);
16341
+ }
16342
+ if (Array.isArray(files) && files.length > 0) {
16343
+ parts.push(`Active files: ${files.slice(0, 5).join(", ")}`);
16344
+ }
16345
+ parts.push("---");
16346
+ postCompactContext = parts.join("\n") + "\n\n";
16347
+ postCompactRestored = true;
16348
+ sessionManager.markPostCompactRestoreCompleted();
16349
+ logSystem("context restored");
16350
+ }
16351
+ } catch (err) {
16352
+ logDebug(`Failed to restore post-compact context: ${err}`);
16353
+ }
16354
+ }
15429
16355
  const result = await client.getSmartContext({
15430
16356
  user_message: input.user_message,
15431
16357
  workspace_id: workspaceId,
@@ -15466,8 +16392,44 @@ This saves ~80% tokens compared to including full chat history.`,
15466
16392
  project_id: projectId,
15467
16393
  max_tokens: input.max_tokens
15468
16394
  });
15469
- const hasLessons = result.context.includes("|L:") || result.context.includes("L:") || result.context.toLowerCase().includes("lesson");
15470
- const lessonsWarningLine = hasLessons ? "\n\n\u26A0\uFE0F [LESSONS DETECTED] Review the L: items above - these are past mistakes. STOP and review before making similar changes." : "";
16395
+ let lessonsWarningLine = "";
16396
+ const riskyKeywords = detectRiskyActions(input.user_message);
16397
+ if (riskyKeywords.length > 0 && workspaceId) {
16398
+ try {
16399
+ const lessons = await client.getHighPriorityLessons({
16400
+ workspace_id: workspaceId,
16401
+ project_id: projectId,
16402
+ context_hint: riskyKeywords.join(" "),
16403
+ limit: 5
16404
+ });
16405
+ if (lessons.length > 0) {
16406
+ const lessonLines = lessons.slice(0, 5).map((l, i) => {
16407
+ const severity = l.severity === "critical" ? "\u{1F6A8}" : l.severity === "high" ? "\u26A0\uFE0F" : "\u{1F4DD}";
16408
+ const title = l.title || "Untitled lesson";
16409
+ const prevention = l.prevention || "";
16410
+ return `${i + 1}. ${severity} ${title}${prevention ? `: ${prevention.slice(0, 100)}` : ""}`;
16411
+ });
16412
+ lessonsWarningLine = `
16413
+
16414
+ \u{1F6A8} [LESSONS_WARNING] Relevant Lessons for "${riskyKeywords.slice(0, 3).join(", ")}"
16415
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
16416
+ \u26A0\uFE0F IMPORTANT: You MUST tell the user about these lessons before proceeding.
16417
+ These are past mistakes that may be relevant to the current task.
16418
+
16419
+ ${lessonLines.join("\n")}
16420
+
16421
+ Action: Review each lesson and explain to the user how you will avoid these mistakes.
16422
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
16423
+ }
16424
+ } catch {
16425
+ }
16426
+ }
16427
+ if (!lessonsWarningLine) {
16428
+ const hasLessonsInContext = result.context.includes("|L:") || result.context.includes("L:") || result.context.toLowerCase().includes("lesson");
16429
+ if (hasLessonsInContext) {
16430
+ lessonsWarningLine = "\n\n\u26A0\uFE0F [LESSONS_WARNING] Lessons found in context - review the L: items above before making changes.";
16431
+ }
16432
+ }
15471
16433
  const searchRulesLine = SEARCH_RULES_REMINDER_ENABLED ? `
15472
16434
 
15473
16435
  ${SEARCH_RULES_REMINDER}` : "";
@@ -15475,20 +16437,29 @@ ${SEARCH_RULES_REMINDER}` : "";
15475
16437
  if (result.context_pressure) {
15476
16438
  const cp = result.context_pressure;
15477
16439
  if (cp.level === "critical") {
16440
+ if (sessionManager) {
16441
+ sessionManager.markHighContextPressure();
16442
+ }
15478
16443
  contextPressureWarning = `
15479
16444
 
15480
16445
  \u{1F6A8} [CONTEXT PRESSURE: CRITICAL] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
15481
16446
  Action: ${cp.suggested_action === "save_now" ? 'SAVE STATE NOW - Call session(action="capture") to preserve conversation state before compaction.' : cp.suggested_action}
15482
16447
  The conversation may compact soon. Save important decisions, insights, and progress immediately.`;
15483
16448
  } else if (cp.level === "high") {
16449
+ if (sessionManager) {
16450
+ sessionManager.markHighContextPressure();
16451
+ }
15484
16452
  contextPressureWarning = `
15485
16453
 
15486
16454
  \u26A0\uFE0F [CONTEXT PRESSURE: HIGH] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
15487
16455
  Action: ${cp.suggested_action === "prepare_save" ? "Consider saving important decisions and conversation state soon." : cp.suggested_action}`;
15488
16456
  }
15489
16457
  }
16458
+ const serverWarnings = result.warnings || [];
16459
+ const serverWarningsLine = serverWarnings.length > 0 ? "\n\n" + serverWarnings.map((w) => `\u26A0\uFE0F ${w}`).join("\n") : "";
15490
16460
  const allWarnings = [
15491
- lessonsWarningLine,
16461
+ serverWarningsLine || lessonsWarningLine,
16462
+ // Server warnings OR client-side lesson detection
15492
16463
  rulesWarningLine ? `
15493
16464
 
15494
16465
  ${rulesWarningLine}` : "",
@@ -15498,14 +16469,16 @@ ${versionWarningLine}` : "",
15498
16469
  contextPressureWarning,
15499
16470
  searchRulesLine
15500
16471
  ].filter(Boolean).join("");
16472
+ const finalContext = postCompactContext + result.context;
16473
+ const enrichedResultWithRestore = postCompactRestored ? { ...enrichedResult, post_compact_restored: true } : enrichedResult;
15501
16474
  return {
15502
16475
  content: [
15503
16476
  {
15504
16477
  type: "text",
15505
- text: result.context + footer + allWarnings
16478
+ text: finalContext + footer + allWarnings
15506
16479
  }
15507
16480
  ],
15508
- structuredContent: toStructured(enrichedResult)
16481
+ structuredContent: toStructured(enrichedResultWithRestore)
15509
16482
  };
15510
16483
  }
15511
16484
  );
@@ -16097,6 +17070,8 @@ Returns: the created page ID, URL, title, and timestamps.
16097
17070
  Use this to save notes, documentation, or any content to Notion.
16098
17071
  Supports Markdown content which is automatically converted to Notion blocks.
16099
17072
 
17073
+ IMPORTANT: If using parent_database_id, you MUST call integration(provider="notion", action="list_databases") FIRST to get valid database IDs. Do NOT use database IDs from memory or previous conversations - they may be stale or inaccessible.
17074
+
16100
17075
  Example prompts:
16101
17076
  - "Create a Notion page with today's meeting notes"
16102
17077
  - "Save this documentation to Notion"
@@ -16106,7 +17081,7 @@ Example prompts:
16106
17081
  project_id: external_exports.string().uuid().optional().describe("Project ID (uses session default if not provided). If provided, the memory event will be scoped to this project."),
16107
17082
  title: external_exports.string().describe("Page title"),
16108
17083
  content: external_exports.string().optional().describe("Page content in Markdown format"),
16109
- parent_database_id: external_exports.string().optional().describe("Parent database ID to create page in"),
17084
+ parent_database_id: external_exports.string().optional().describe("Parent database ID. MUST call integration(provider='notion', action='list_databases') first to get valid IDs - do NOT use IDs from memory"),
16110
17085
  parent_page_id: external_exports.string().optional().describe("Parent page ID to create page under")
16111
17086
  })
16112
17087
  },
@@ -16574,7 +17549,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16574
17549
  "session",
16575
17550
  {
16576
17551
  title: "Session",
16577
- description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
17552
+ description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
16578
17553
  inputSchema: external_exports.object({
16579
17554
  action: external_exports.enum([
16580
17555
  "capture",
@@ -16592,7 +17567,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16592
17567
  "capture_plan",
16593
17568
  "get_plan",
16594
17569
  "update_plan",
16595
- "list_plans"
17570
+ "list_plans",
17571
+ // Context restore
17572
+ "restore_context"
16596
17573
  ]).describe("Action to perform"),
16597
17574
  workspace_id: external_exports.string().uuid().optional(),
16598
17575
  project_id: external_exports.string().uuid().optional(),
@@ -16614,7 +17591,8 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16614
17591
  "lesson",
16615
17592
  "warning",
16616
17593
  "frustration",
16617
- "conversation"
17594
+ "conversation",
17595
+ "session_snapshot"
16618
17596
  ]).optional().describe("Event type for capture"),
16619
17597
  importance: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
16620
17598
  tags: external_exports.array(external_exports.string()).optional(),
@@ -16664,7 +17642,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16664
17642
  status: external_exports.enum(["draft", "active", "completed", "archived", "abandoned"]).optional().describe("Plan status"),
16665
17643
  due_at: external_exports.string().optional().describe("Due date for plan (ISO timestamp)"),
16666
17644
  source_tool: external_exports.string().optional().describe("Tool that generated this plan"),
16667
- include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan")
17645
+ include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan"),
17646
+ // Restore context params
17647
+ snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
17648
+ max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
16668
17649
  })
16669
17650
  },
16670
17651
  async (input) => {
@@ -16970,6 +17951,143 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16970
17951
  structuredContent: toStructured(result)
16971
17952
  };
16972
17953
  }
17954
+ case "restore_context": {
17955
+ if (!workspaceId) {
17956
+ return errorResult(
17957
+ "restore_context requires workspace_id. Call session_init first."
17958
+ );
17959
+ }
17960
+ if (input.snapshot_id) {
17961
+ const eventResult = await client.getEvent(input.snapshot_id);
17962
+ const event = eventResult?.data || eventResult;
17963
+ if (!event || !event.content) {
17964
+ return errorResult(
17965
+ `Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
17966
+ );
17967
+ }
17968
+ let snapshotData;
17969
+ try {
17970
+ snapshotData = JSON.parse(event.content);
17971
+ } catch {
17972
+ snapshotData = { conversation_summary: event.content };
17973
+ }
17974
+ const sessionId = snapshotData.session_id || event.session_id;
17975
+ const sessionLinking2 = {};
17976
+ if (sessionId) {
17977
+ sessionLinking2.previous_session_id = sessionId;
17978
+ const workingOn = [];
17979
+ const activeFiles = snapshotData.active_files;
17980
+ const lastTools = snapshotData.last_tools;
17981
+ if (activeFiles && activeFiles.length > 0) {
17982
+ workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
17983
+ }
17984
+ if (lastTools && lastTools.length > 0) {
17985
+ const toolCounts = lastTools.reduce((acc, tool) => {
17986
+ acc[tool] = (acc[tool] || 0) + 1;
17987
+ return acc;
17988
+ }, {});
17989
+ const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
17990
+ workingOn.push(`Recent tools: ${topTools.join(", ")}`);
17991
+ }
17992
+ if (workingOn.length > 0) {
17993
+ sessionLinking2.previous_session_summary = workingOn.join("; ");
17994
+ }
17995
+ }
17996
+ const response2 = {
17997
+ restored: true,
17998
+ snapshot_id: event.id,
17999
+ captured_at: snapshotData.captured_at || event.created_at,
18000
+ session_linking: Object.keys(sessionLinking2).length > 0 ? sessionLinking2 : void 0,
18001
+ ...snapshotData,
18002
+ hint: sessionId ? `Context restored from session ${sessionId}. Continue the conversation with awareness of the above state.` : "Context restored. Continue the conversation with awareness of the above state."
18003
+ };
18004
+ return {
18005
+ content: [{ type: "text", text: formatContent(response2) }],
18006
+ structuredContent: toStructured(response2)
18007
+ };
18008
+ }
18009
+ const listResult = await client.listMemoryEvents({
18010
+ workspace_id: workspaceId,
18011
+ project_id: projectId,
18012
+ limit: 50
18013
+ // Fetch more to filter
18014
+ });
18015
+ const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
18016
+ const snapshotEvents = allEvents.filter(
18017
+ (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
18018
+ ).slice(0, input.max_snapshots || 1);
18019
+ if (!snapshotEvents || snapshotEvents.length === 0) {
18020
+ return {
18021
+ content: [
18022
+ {
18023
+ type: "text",
18024
+ text: formatContent({
18025
+ restored: false,
18026
+ message: "No session snapshots found. Use session_capture_smart to save state before compaction.",
18027
+ hint: "Start fresh or use session_init to get recent context."
18028
+ })
18029
+ }
18030
+ ]
18031
+ };
18032
+ }
18033
+ const snapshots = snapshotEvents.map((event) => {
18034
+ let snapshotData;
18035
+ try {
18036
+ snapshotData = JSON.parse(event.content || "{}");
18037
+ } catch {
18038
+ snapshotData = { conversation_summary: event.content };
18039
+ }
18040
+ return {
18041
+ snapshot_id: event.id,
18042
+ captured_at: snapshotData.captured_at || event.created_at,
18043
+ session_id: snapshotData.session_id || event.session_id,
18044
+ ...snapshotData
18045
+ };
18046
+ });
18047
+ const latestSnapshot = snapshots[0];
18048
+ const sessionLinking = {};
18049
+ if (latestSnapshot?.session_id) {
18050
+ sessionLinking.previous_session_id = latestSnapshot.session_id;
18051
+ const workingOn = [];
18052
+ const activeFiles = latestSnapshot.active_files;
18053
+ const lastTools = latestSnapshot.last_tools;
18054
+ if (activeFiles && activeFiles.length > 0) {
18055
+ workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
18056
+ }
18057
+ if (lastTools && lastTools.length > 0) {
18058
+ const toolCounts = lastTools.reduce((acc, tool) => {
18059
+ acc[tool] = (acc[tool] || 0) + 1;
18060
+ return acc;
18061
+ }, {});
18062
+ const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
18063
+ workingOn.push(`Recent tools: ${topTools.join(", ")}`);
18064
+ }
18065
+ if (workingOn.length > 0) {
18066
+ sessionLinking.previous_session_summary = workingOn.join("; ");
18067
+ }
18068
+ const relatedSessionIds = /* @__PURE__ */ new Set();
18069
+ snapshots.forEach((s) => {
18070
+ if (s.session_id && s.session_id !== latestSnapshot.session_id) {
18071
+ relatedSessionIds.add(s.session_id);
18072
+ }
18073
+ });
18074
+ if (relatedSessionIds.size > 0) {
18075
+ sessionLinking.related_sessions = Array.from(relatedSessionIds);
18076
+ }
18077
+ }
18078
+ const response = {
18079
+ restored: true,
18080
+ snapshots_found: snapshots.length,
18081
+ latest: snapshots[0],
18082
+ all_snapshots: snapshots.length > 1 ? snapshots : void 0,
18083
+ session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
18084
+ hint: sessionLinking.previous_session_id ? `Context restored from session ${sessionLinking.previous_session_id}. Continue the conversation with awareness of the above state.` : "Context restored. Continue the conversation with awareness of the above state."
18085
+ };
18086
+ return {
18087
+ content: [{ type: "text", text: formatContent(response) }],
18088
+ structuredContent: toStructured(response)
18089
+ };
18090
+ }
16973
18091
  default:
16974
18092
  return errorResult(`Unknown action: ${input.action}`);
16975
18093
  }
@@ -18079,7 +19197,7 @@ ${formatContent(result)}`
18079
19197
  title: external_exports.string().optional().describe("Page/database title (for Notion create_page/update_page/create_database)"),
18080
19198
  content: external_exports.string().optional().describe("Page content in Markdown (for Notion create_page/update_page)"),
18081
19199
  description: external_exports.string().optional().describe("Database description (for Notion create_database)"),
18082
- parent_database_id: external_exports.string().optional().describe("Parent database ID (for Notion create_page)"),
19200
+ parent_database_id: external_exports.string().optional().describe("Parent database ID (for Notion create_page). MUST call list_databases first - do NOT use IDs from memory"),
18083
19201
  parent_page_id: external_exports.string().optional().describe("Parent page ID (for Notion create_page/create_database)"),
18084
19202
  page_id: external_exports.string().optional().describe("Page ID (for Notion get_page/update_page)"),
18085
19203
  database_id: external_exports.string().optional().describe("Database ID (for Notion query_database/search_pages/activity)"),
@@ -18673,9 +19791,7 @@ Each domain tool has an 'action' parameter for specific operations.` : "";
18673
19791
  }
18674
19792
  }
18675
19793
  );
18676
- console.error(
18677
- `[ContextStream] Consolidated mode: Registered ${CONSOLIDATED_TOOLS.size} domain tools.`
18678
- );
19794
+ logDebug(`Consolidated mode: ${CONSOLIDATED_TOOLS.size} domain tools`);
18679
19795
  }
18680
19796
  }
18681
19797
  function registerLimitedTools(server) {
@@ -18710,7 +19826,7 @@ After setup, restart your editor to enable all ContextStream tools.`
18710
19826
  };
18711
19827
  }
18712
19828
  );
18713
- console.error("[ContextStream] Limited mode: Registered setup helper tool only.");
19829
+ logSystem("limited mode (run setup)");
18714
19830
  }
18715
19831
 
18716
19832
  // src/resources.ts
@@ -19492,7 +20608,7 @@ function registerPrompts(server) {
19492
20608
  "First:",
19493
20609
  "- If you can infer the project folder path from the environment/IDE roots, use it.",
19494
20610
  "- Otherwise ask me for an absolute folder path.",
19495
- "- Ask which editor(s) (codex,windsurf,cursor,cline,kilo,roo,claude,aider,antigravity) or default to all.",
20611
+ "- Ask which editor(s) (codex,cursor,cline,kilo,roo,claude,aider,antigravity) or default to all.",
19496
20612
  "",
19497
20613
  "Then call `generate_rules` and confirm which files were created/updated.",
19498
20614
  "Ask if the user also wants to apply rules globally (pass apply_global: true)."
@@ -19534,8 +20650,7 @@ function registerPrompts(server) {
19534
20650
  }
19535
20651
 
19536
20652
  // src/session-manager.ts
19537
- var SessionManager = class {
19538
- // Conservative default for 100k context window
20653
+ var SessionManager = class _SessionManager {
19539
20654
  constructor(server, client) {
19540
20655
  this.server = server;
19541
20656
  this.client = client;
@@ -19547,8 +20662,30 @@ var SessionManager = class {
19547
20662
  this.contextSmartCalled = false;
19548
20663
  this.warningShown = false;
19549
20664
  // Token tracking for context pressure calculation
20665
+ // Note: MCP servers cannot see actual token usage (AI responses, thinking, system prompts).
20666
+ // We use a heuristic: tracked tokens + (turns * estimated tokens per turn)
19550
20667
  this.sessionTokens = 0;
19551
20668
  this.contextThreshold = 7e4;
20669
+ // Conservative default for 100k context window
20670
+ this.conversationTurns = 0;
20671
+ // Continuous checkpointing
20672
+ this.toolCallCount = 0;
20673
+ this.checkpointInterval = 20;
20674
+ // Save checkpoint every N tool calls
20675
+ this.lastCheckpointAt = 0;
20676
+ this.activeFiles = /* @__PURE__ */ new Set();
20677
+ this.recentToolCalls = [];
20678
+ this.checkpointEnabled = process.env.CONTEXTSTREAM_CHECKPOINT_ENABLED?.toLowerCase() === "true";
20679
+ // Post-compaction restoration tracking
20680
+ // Tracks when context pressure was high/critical so we can detect post-compaction state
20681
+ this.lastHighPressureAt = null;
20682
+ this.lastHighPressureTokens = 0;
20683
+ this.postCompactRestoreCompleted = false;
20684
+ }
20685
+ static {
20686
+ // Each conversation turn typically includes: user message (~500), AI response (~1500),
20687
+ // system prompt overhead (~500), and reasoning (~1500). Conservative estimate: 3000/turn
20688
+ this.TOKENS_PER_TURN_ESTIMATE = 3e3;
19552
20689
  }
19553
20690
  /**
19554
20691
  * Check if session has been auto-initialized
@@ -19591,17 +20728,40 @@ var SessionManager = class {
19591
20728
  this.folderPath = path9;
19592
20729
  }
19593
20730
  /**
19594
- * Mark that context_smart has been called in this session
20731
+ * Mark that context_smart has been called in this session.
20732
+ * Also increments the conversation turn counter for token estimation.
19595
20733
  */
19596
20734
  markContextSmartCalled() {
19597
20735
  this.contextSmartCalled = true;
20736
+ this.conversationTurns++;
19598
20737
  }
19599
20738
  /**
19600
20739
  * Get current session token count for context pressure calculation.
20740
+ *
20741
+ * This returns an ESTIMATED count based on:
20742
+ * 1. Tokens tracked through ContextStream tools (actual)
20743
+ * 2. Estimated tokens per conversation turn (heuristic)
20744
+ *
20745
+ * Note: MCP servers cannot see actual AI token usage (responses, thinking,
20746
+ * system prompts). This estimate helps provide a more realistic context
20747
+ * pressure signal.
19601
20748
  */
19602
20749
  getSessionTokens() {
20750
+ const turnEstimate = this.conversationTurns * _SessionManager.TOKENS_PER_TURN_ESTIMATE;
20751
+ return this.sessionTokens + turnEstimate;
20752
+ }
20753
+ /**
20754
+ * Get the raw tracked tokens (without turn-based estimation).
20755
+ */
20756
+ getRawTrackedTokens() {
19603
20757
  return this.sessionTokens;
19604
20758
  }
20759
+ /**
20760
+ * Get the current conversation turn count.
20761
+ */
20762
+ getConversationTurns() {
20763
+ return this.conversationTurns;
20764
+ }
19605
20765
  /**
19606
20766
  * Get the context threshold (max tokens before compaction warning).
19607
20767
  */
@@ -19640,6 +20800,52 @@ var SessionManager = class {
19640
20800
  */
19641
20801
  resetTokenCount() {
19642
20802
  this.sessionTokens = 0;
20803
+ this.conversationTurns = 0;
20804
+ }
20805
+ /**
20806
+ * Record that context pressure is high/critical.
20807
+ * Called when context_smart returns high or critical pressure level.
20808
+ */
20809
+ markHighContextPressure() {
20810
+ this.lastHighPressureAt = Date.now();
20811
+ this.lastHighPressureTokens = this.getSessionTokens();
20812
+ }
20813
+ /**
20814
+ * Check if we should attempt post-compaction restoration.
20815
+ *
20816
+ * Detection heuristic:
20817
+ * 1. We recorded high/critical context pressure recently (within 10 minutes)
20818
+ * 2. Current token count is very low (< 5000) compared to when pressure was high
20819
+ * 3. We haven't already restored in this session
20820
+ *
20821
+ * This indicates compaction likely happened and we should restore context.
20822
+ */
20823
+ shouldRestorePostCompact() {
20824
+ if (this.postCompactRestoreCompleted) {
20825
+ return false;
20826
+ }
20827
+ if (!this.lastHighPressureAt) {
20828
+ return false;
20829
+ }
20830
+ const elapsed = Date.now() - this.lastHighPressureAt;
20831
+ if (elapsed > 10 * 60 * 1e3) {
20832
+ return false;
20833
+ }
20834
+ const currentTokens = this.getSessionTokens();
20835
+ const tokenDrop = this.lastHighPressureTokens - currentTokens;
20836
+ if (currentTokens > 1e4 || tokenDrop < this.lastHighPressureTokens * 0.5) {
20837
+ return false;
20838
+ }
20839
+ return true;
20840
+ }
20841
+ /**
20842
+ * Mark post-compaction restoration as completed.
20843
+ * Prevents multiple restoration attempts in the same session.
20844
+ */
20845
+ markPostCompactRestoreCompleted() {
20846
+ this.postCompactRestoreCompleted = true;
20847
+ this.lastHighPressureAt = null;
20848
+ this.lastHighPressureTokens = 0;
19643
20849
  }
19644
20850
  /**
19645
20851
  * Check if context_smart has been called and warn if not.
@@ -19905,6 +21111,112 @@ var SessionManager = class {
19905
21111
  parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
19906
21112
  return parts.join("\n");
19907
21113
  }
21114
+ // =========================================================================
21115
+ // Continuous Checkpointing
21116
+ // =========================================================================
21117
+ /**
21118
+ * Track a tool call for checkpointing purposes.
21119
+ * Call this after each tool execution to track files and trigger periodic checkpoints.
21120
+ */
21121
+ trackToolCall(toolName, input) {
21122
+ this.toolCallCount++;
21123
+ this.recentToolCalls.push({ name: toolName, timestamp: Date.now() });
21124
+ if (this.recentToolCalls.length > 50) {
21125
+ this.recentToolCalls = this.recentToolCalls.slice(-50);
21126
+ }
21127
+ if (input) {
21128
+ const filePath = input.file_path || input.notebook_path || input.path;
21129
+ if (filePath && typeof filePath === "string") {
21130
+ this.activeFiles.add(filePath);
21131
+ if (this.activeFiles.size > 30) {
21132
+ const arr = Array.from(this.activeFiles);
21133
+ this.activeFiles = new Set(arr.slice(-30));
21134
+ }
21135
+ }
21136
+ }
21137
+ this.maybeCheckpoint();
21138
+ }
21139
+ /**
21140
+ * Save a checkpoint if the interval has been reached.
21141
+ */
21142
+ async maybeCheckpoint() {
21143
+ if (!this.checkpointEnabled || !this.initialized || !this.context) {
21144
+ return;
21145
+ }
21146
+ const callsSinceLastCheckpoint = this.toolCallCount - this.lastCheckpointAt;
21147
+ if (callsSinceLastCheckpoint < this.checkpointInterval) {
21148
+ return;
21149
+ }
21150
+ this.lastCheckpointAt = this.toolCallCount;
21151
+ await this.saveCheckpoint("periodic");
21152
+ }
21153
+ /**
21154
+ * Get the list of active files being worked on.
21155
+ */
21156
+ getActiveFiles() {
21157
+ return Array.from(this.activeFiles);
21158
+ }
21159
+ /**
21160
+ * Get recent tool call names.
21161
+ */
21162
+ getRecentToolNames() {
21163
+ return this.recentToolCalls.map((t) => t.name);
21164
+ }
21165
+ /**
21166
+ * Get the current tool call count.
21167
+ */
21168
+ getToolCallCount() {
21169
+ return this.toolCallCount;
21170
+ }
21171
+ /**
21172
+ * Save a checkpoint snapshot to ContextStream.
21173
+ */
21174
+ async saveCheckpoint(trigger) {
21175
+ if (!this.initialized || !this.context) {
21176
+ return false;
21177
+ }
21178
+ const workspaceId = this.context.workspace_id;
21179
+ if (!workspaceId) {
21180
+ return false;
21181
+ }
21182
+ const checkpointData = {
21183
+ trigger,
21184
+ checkpoint_number: Math.floor(this.toolCallCount / this.checkpointInterval),
21185
+ tool_call_count: this.toolCallCount,
21186
+ session_tokens: this.sessionTokens,
21187
+ active_files: this.getActiveFiles(),
21188
+ recent_tools: this.getRecentToolNames().slice(-10),
21189
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
21190
+ auto_captured: true
21191
+ };
21192
+ try {
21193
+ await this.client.captureContext({
21194
+ workspace_id: workspaceId,
21195
+ project_id: this.context.project_id,
21196
+ event_type: "session_snapshot",
21197
+ title: `Checkpoint #${checkpointData.checkpoint_number} (${trigger})`,
21198
+ content: JSON.stringify(checkpointData),
21199
+ importance: trigger === "periodic" ? "low" : "medium",
21200
+ tags: ["session_snapshot", "checkpoint", trigger]
21201
+ });
21202
+ return true;
21203
+ } catch (err) {
21204
+ console.error("[ContextStream] Failed to save checkpoint:", err);
21205
+ return false;
21206
+ }
21207
+ }
21208
+ /**
21209
+ * Enable or disable continuous checkpointing.
21210
+ */
21211
+ setCheckpointEnabled(enabled) {
21212
+ this.checkpointEnabled = enabled;
21213
+ }
21214
+ /**
21215
+ * Set the checkpoint interval (tool calls between checkpoints).
21216
+ */
21217
+ setCheckpointInterval(interval) {
21218
+ this.checkpointInterval = Math.max(5, interval);
21219
+ }
19908
21220
  };
19909
21221
 
19910
21222
  // src/http-gateway.ts
@@ -20268,7 +21580,6 @@ var EDITOR_LABELS = {
20268
21580
  codex: "Codex CLI",
20269
21581
  claude: "Claude Code",
20270
21582
  cursor: "Cursor / VS Code",
20271
- windsurf: "Windsurf",
20272
21583
  cline: "Cline",
20273
21584
  kilo: "Kilo Code",
20274
21585
  roo: "Roo Code",
@@ -20347,7 +21658,6 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
20347
21658
  /^#\s+codex cli instructions$/i,
20348
21659
  /^#\s+claude code instructions$/i,
20349
21660
  /^#\s+cursor rules$/i,
20350
- /^#\s+windsurf rules$/i,
20351
21661
  /^#\s+cline rules$/i,
20352
21662
  /^#\s+kilo code rules$/i,
20353
21663
  /^#\s+roo code rules$/i,
@@ -20489,8 +21799,6 @@ function globalRulesPathForEditor(editor) {
20489
21799
  return path8.join(home, ".codex", "AGENTS.md");
20490
21800
  case "claude":
20491
21801
  return path8.join(home, ".claude", "CLAUDE.md");
20492
- case "windsurf":
20493
- return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
20494
21802
  case "cline":
20495
21803
  return path8.join(home, "Documents", "Cline", "Rules", "contextstream.md");
20496
21804
  case "kilo":
@@ -20537,25 +21845,6 @@ async function isClaudeInstalled() {
20537
21845
  }
20538
21846
  return anyPathExists(candidates);
20539
21847
  }
20540
- async function isWindsurfInstalled() {
20541
- const home = homedir5();
20542
- const candidates = [
20543
- path8.join(home, ".codeium"),
20544
- path8.join(home, ".codeium", "windsurf"),
20545
- path8.join(home, ".config", "codeium")
20546
- ];
20547
- if (process.platform === "darwin") {
20548
- candidates.push(path8.join(home, "Library", "Application Support", "Windsurf"));
20549
- candidates.push(path8.join(home, "Library", "Application Support", "Codeium"));
20550
- } else if (process.platform === "win32") {
20551
- const appData = process.env.APPDATA;
20552
- if (appData) {
20553
- candidates.push(path8.join(appData, "Windsurf"));
20554
- candidates.push(path8.join(appData, "Codeium"));
20555
- }
20556
- }
20557
- return anyPathExists(candidates);
20558
- }
20559
21848
  async function isClineInstalled() {
20560
21849
  const home = homedir5();
20561
21850
  const candidates = [
@@ -20634,8 +21923,6 @@ async function isEditorInstalled(editor) {
20634
21923
  return isClaudeInstalled();
20635
21924
  case "cursor":
20636
21925
  return isCursorInstalled();
20637
- case "windsurf":
20638
- return isWindsurfInstalled();
20639
21926
  case "cline":
20640
21927
  return isClineInstalled();
20641
21928
  case "kilo":
@@ -20660,6 +21947,9 @@ function buildContextStreamMcpServer(params) {
20660
21947
  env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
20661
21948
  }
20662
21949
  env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
21950
+ if (params.restoreContextEnabled === false) {
21951
+ env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
21952
+ }
20663
21953
  if (params.showTiming) {
20664
21954
  env.CONTEXTSTREAM_SHOW_TIMING = "true";
20665
21955
  }
@@ -20685,6 +21975,9 @@ function buildContextStreamVsCodeServer(params) {
20685
21975
  env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
20686
21976
  }
20687
21977
  env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
21978
+ if (params.restoreContextEnabled === false) {
21979
+ env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
21980
+ }
20688
21981
  if (params.showTiming) {
20689
21982
  env.CONTEXTSTREAM_SHOW_TIMING = "true";
20690
21983
  }
@@ -20788,6 +22081,8 @@ async function upsertCodexTomlConfig(filePath, params) {
20788
22081
  ` : "";
20789
22082
  const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
20790
22083
  `;
22084
+ const restoreContextLine = params.restoreContextEnabled === false ? `CONTEXTSTREAM_RESTORE_CONTEXT = "false"
22085
+ ` : "";
20791
22086
  const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
20792
22087
  ` : "";
20793
22088
  const commandLine = IS_WINDOWS ? `command = "cmd"
@@ -20803,7 +22098,7 @@ args = ["-y", "@contextstream/mcp-server"]
20803
22098
  [mcp_servers.contextstream.env]
20804
22099
  CONTEXTSTREAM_API_URL = "${params.apiUrl}"
20805
22100
  CONTEXTSTREAM_API_KEY = "${params.apiKey}"
20806
- ` + toolsetLine + contextPackLine + showTimingLine;
22101
+ ` + toolsetLine + contextPackLine + restoreContextLine + showTimingLine;
20807
22102
  if (!exists) {
20808
22103
  await fs7.writeFile(filePath, block.trimStart(), "utf8");
20809
22104
  return "created";
@@ -20933,6 +22228,26 @@ async function runSetupWizard(args) {
20933
22228
  console.log("This configures ContextStream MCP + rules for your AI editor(s).");
20934
22229
  if (dryRun) console.log("DRY RUN: no files will be written.\n");
20935
22230
  else console.log("");
22231
+ const versionNotice = await getUpdateNotice();
22232
+ if (versionNotice?.behind) {
22233
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
22234
+ console.log(`\u26A0\uFE0F You're running an outdated version (v${versionNotice.current})`);
22235
+ console.log(` Latest version is v${versionNotice.latest}`);
22236
+ console.log("");
22237
+ console.log(" To use the latest version, exit and run:");
22238
+ console.log(" npx -y @contextstream/mcp-server@latest setup");
22239
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
22240
+ console.log("");
22241
+ const continueAnyway = normalizeInput(
22242
+ await rl.question("Continue with current version anyway? [y/N]: ")
22243
+ ).toLowerCase();
22244
+ if (continueAnyway !== "y" && continueAnyway !== "yes") {
22245
+ console.log("\nExiting. Run the command above to use the latest version.");
22246
+ rl.close();
22247
+ return;
22248
+ }
22249
+ console.log("");
22250
+ }
20936
22251
  const savedCreds = await readSavedCredentials();
20937
22252
  const apiUrlDefault = normalizeApiUrl(
20938
22253
  process.env.CONTEXTSTREAM_API_URL || savedCreds?.api_url || "https://api.contextstream.io"
@@ -21125,11 +22440,7 @@ Code: ${device.user_code}`);
21125
22440
  }
21126
22441
  }
21127
22442
  }
21128
- console.log("Rules detail level (in the generated rules file):\n");
21129
- console.log(" 1) Standard (recommended) \u2014 concise, high-signal (lower token overhead)");
21130
- console.log(" 2) Enhanced \u2014 more guidance + examples (higher token overhead)");
21131
- const modeChoice = normalizeInput(await rl.question("Choose [1/2] (default 1): ")) || "1";
21132
- const mode = modeChoice === "2" ? "full" : "minimal";
22443
+ const mode = "full";
21133
22444
  const detectedPlanName = await client.getPlanName();
21134
22445
  const detectedGraphTier = await client.getGraphTier();
21135
22446
  const graphTierLabel = detectedGraphTier === "full" ? "full graph" : detectedGraphTier === "lite" ? "graph-lite" : "none";
@@ -21165,11 +22476,16 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
21165
22476
  console.log(" Useful for debugging performance; disabled by default.");
21166
22477
  const showTimingChoice = normalizeInput(await rl.question("Show response timing? [y/N]: "));
21167
22478
  const showTiming = showTimingChoice.toLowerCase() === "y" || showTimingChoice.toLowerCase() === "yes";
22479
+ console.log("\nAutomatic Context Restoration:");
22480
+ console.log(" Automatically restore context from recent snapshots on every session_init.");
22481
+ console.log(" This enables seamless continuation across conversations and after compaction.");
22482
+ console.log(" Enabled by default; disable if you prefer explicit control.");
22483
+ const restoreContextChoice = normalizeInput(await rl.question("Enable automatic context restoration? [Y/n]: "));
22484
+ const restoreContextEnabled = !(restoreContextChoice.toLowerCase() === "n" || restoreContextChoice.toLowerCase() === "no");
21168
22485
  const editors = [
21169
22486
  "codex",
21170
22487
  "claude",
21171
22488
  "cursor",
21172
- "windsurf",
21173
22489
  "cline",
21174
22490
  "kilo",
21175
22491
  "roo",
@@ -21215,7 +22531,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
21215
22531
  console.log(" 1) Global");
21216
22532
  console.log(" 2) Project");
21217
22533
  console.log(" 3) Both");
21218
- const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 3): ")) || "3";
22534
+ const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 2): ")) || "2";
21219
22535
  const scope = scopeChoice === "1" ? "global" : scopeChoice === "2" ? "project" : "both";
21220
22536
  console.log("\nInstall MCP server config as:");
21221
22537
  if (hasCodex && !hasProjectMcpEditors) {
@@ -21232,27 +22548,29 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
21232
22548
  );
21233
22549
  }
21234
22550
  }
21235
- const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "3";
22551
+ const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "2";
21236
22552
  const mcpChoice = normalizeInput(
21237
22553
  await rl.question(
21238
22554
  `Choose [${hasCodex && !hasProjectMcpEditors ? "1/2" : "1/2/3/4"}] (default ${mcpChoiceDefault}): `
21239
22555
  )
21240
22556
  ) || mcpChoiceDefault;
21241
22557
  const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
21242
- const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming });
22558
+ const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming, restoreContextEnabled });
21243
22559
  const mcpServerClaude = buildContextStreamMcpServer({
21244
22560
  apiUrl,
21245
22561
  apiKey,
21246
22562
  toolset,
21247
22563
  contextPackEnabled,
21248
- showTiming
22564
+ showTiming,
22565
+ restoreContextEnabled
21249
22566
  });
21250
22567
  const vsCodeServer = buildContextStreamVsCodeServer({
21251
22568
  apiUrl,
21252
22569
  apiKey,
21253
22570
  toolset,
21254
22571
  contextPackEnabled,
21255
- showTiming
22572
+ showTiming,
22573
+ restoreContextEnabled
21256
22574
  });
21257
22575
  const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
21258
22576
  if (needsGlobalMcpConfig) {
@@ -21272,24 +22590,13 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
21272
22590
  apiKey,
21273
22591
  toolset,
21274
22592
  contextPackEnabled,
21275
- showTiming
22593
+ showTiming,
22594
+ restoreContextEnabled
21276
22595
  });
21277
22596
  writeActions.push({ kind: "mcp-config", target: filePath, status });
21278
22597
  console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
21279
22598
  continue;
21280
22599
  }
21281
- if (editor === "windsurf") {
21282
- const filePath = path8.join(homedir5(), ".codeium", "windsurf", "mcp_config.json");
21283
- if (dryRun) {
21284
- writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
21285
- console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
21286
- continue;
21287
- }
21288
- const status = await upsertJsonMcpConfig(filePath, mcpServer);
21289
- writeActions.push({ kind: "mcp-config", target: filePath, status });
21290
- console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
21291
- continue;
21292
- }
21293
22600
  if (editor === "claude") {
21294
22601
  const desktopPath = claudeDesktopConfigPath();
21295
22602
  if (desktopPath) {
@@ -21354,53 +22661,68 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
21354
22661
  }
21355
22662
  }
21356
22663
  }
21357
- if (configuredEditors.includes("claude")) {
22664
+ const HOOKS_SUPPORTED_EDITORS = {
22665
+ claude: "claude",
22666
+ cursor: "cursor",
22667
+ cline: "cline",
22668
+ roo: "roo",
22669
+ kilo: "kilo",
22670
+ codex: null,
22671
+ // No hooks API
22672
+ aider: null,
22673
+ // No hooks API
22674
+ antigravity: null
22675
+ // No hooks API
22676
+ };
22677
+ const hookEligibleEditors = configuredEditors.filter(
22678
+ (e) => HOOKS_SUPPORTED_EDITORS[e] !== null
22679
+ );
22680
+ if (hookEligibleEditors.length > 0) {
21358
22681
  console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
21359
- console.log("\u2502 Claude Code Hooks (Recommended) \u2502");
22682
+ console.log("\u2502 AI Editor Hooks (Recommended) \u2502");
21360
22683
  console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
21361
22684
  console.log("");
21362
- console.log(" Problem: Claude Code often ignores CLAUDE.md instructions and uses");
21363
- console.log(" its default tools (Grep/Glob/Search) instead of ContextStream search.");
21364
- console.log(" This happens because instructions decay over long conversations.");
22685
+ console.log(" Problem: AI editors often use their default tools (Grep/Glob/Search)");
22686
+ console.log(" instead of ContextStream smart search. Instructions decay over long chats.");
21365
22687
  console.log("");
21366
22688
  console.log(" Solution: Install hooks that:");
21367
- console.log(" \u2713 Block default search tools (Grep/Glob/Search) \u2192 redirect to ContextStream");
21368
- console.log(" \u2713 Block built-in plan mode \u2192 redirect to ContextStream plans (persistent)");
21369
- console.log(" \u2713 Inject reminders on every message to keep rules in context");
21370
- console.log(" \u2713 Result: Faster searches, persistent plans across sessions");
22689
+ console.log(" \u2713 Use ContextStream (indexed, faster) with default tool use");
22690
+ console.log(" \u2713 Use ContextStream plans (persistent) with default tool use");
22691
+ console.log(" \u2713 Inject reminders to keep rules in context");
22692
+ console.log("");
22693
+ console.log(` Hooks available for: ${hookEligibleEditors.map((e) => EDITOR_LABELS[e]).join(", ")}`);
21371
22694
  console.log("");
21372
22695
  console.log(" You can disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
21373
22696
  console.log("");
21374
22697
  const installHooks = normalizeInput(
21375
- await rl.question("Install Claude Code hooks? [Y/n] (recommended): ")
22698
+ await rl.question("Install editor hooks? [Y/n] (recommended): ")
21376
22699
  ).toLowerCase();
21377
22700
  if (installHooks !== "n" && installHooks !== "no") {
21378
- try {
21379
- if (dryRun) {
21380
- console.log("- Would install hooks to ~/.claude/hooks/");
21381
- console.log("- Would update ~/.claude/settings.json");
21382
- writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "hooks", "contextstream-redirect.py"), status: "dry-run" });
21383
- writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "hooks", "contextstream-reminder.py"), status: "dry-run" });
21384
- writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "settings.json"), status: "dry-run" });
21385
- } else {
21386
- const result = await installClaudeCodeHooks({ scope: "user" });
21387
- result.scripts.forEach((script) => {
21388
- writeActions.push({ kind: "mcp-config", target: script, status: "created" });
21389
- console.log(`- Created hook: ${script}`);
21390
- });
21391
- result.settings.forEach((settings) => {
21392
- writeActions.push({ kind: "mcp-config", target: settings, status: "updated" });
21393
- console.log(`- Updated settings: ${settings}`);
22701
+ for (const editor of hookEligibleEditors) {
22702
+ const hookEditor = HOOKS_SUPPORTED_EDITORS[editor];
22703
+ if (!hookEditor) continue;
22704
+ try {
22705
+ if (dryRun) {
22706
+ console.log(`- ${EDITOR_LABELS[editor]}: would install hooks`);
22707
+ continue;
22708
+ }
22709
+ const result = await installEditorHooks({
22710
+ editor: hookEditor,
22711
+ scope: "global"
21394
22712
  });
22713
+ for (const script of result.installed) {
22714
+ writeActions.push({ kind: "hooks", target: script, status: "created" });
22715
+ console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
22716
+ }
22717
+ } catch (err) {
22718
+ const message = err instanceof Error ? err.message : String(err);
22719
+ console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
21395
22720
  }
21396
- console.log(" Hooks installed. Disable with CONTEXTSTREAM_HOOK_ENABLED=false");
21397
- } catch (err) {
21398
- const message = err instanceof Error ? err.message : String(err);
21399
- console.log(`- Failed to install hooks: ${message}`);
21400
22721
  }
22722
+ console.log(" Hooks installed. Disable with CONTEXTSTREAM_HOOK_ENABLED=false");
21401
22723
  } else {
21402
22724
  console.log("- Skipped hooks installation.");
21403
- console.log(" Note: Without hooks, Claude may still use default tools instead of ContextStream.");
22725
+ console.log(" Note: Without hooks, AI may still use default tools instead of ContextStream.");
21404
22726
  }
21405
22727
  }
21406
22728
  console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
@@ -21729,11 +23051,7 @@ Applying to ${projects.length} project(s)...`);
21729
23051
  "- Toggle Response Timing with CONTEXTSTREAM_SHOW_TIMING=true|false."
21730
23052
  );
21731
23053
  console.log("");
21732
- console.log("You're set up! Now try these prompts in your AI tool:");
21733
- console.log(' 1) "session summary"');
21734
- console.log(` 2) "remember we're using PostgreSQL"`);
21735
- console.log(' 3) "what did we decide about auth?"');
21736
- console.log("");
23054
+ console.log("You're all set! ContextStream gives your AI persistent memory, semantic code search, and cross-session context.");
21737
23055
  console.log("More at: https://contextstream.io/docs/mcp");
21738
23056
  } finally {
21739
23057
  rl.close();
@@ -21883,13 +23201,21 @@ async function main() {
21883
23201
  if (ENABLE_PROMPTS2) {
21884
23202
  registerPrompts(server);
21885
23203
  }
21886
- console.error(`ContextStream MCP server v${VERSION} starting...`);
21887
- console.error(`API URL: ${config.apiUrl}`);
21888
- console.error(`Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "None"}`);
21889
- console.error(`Auto-Context: ENABLED (context loads on first tool call)`);
23204
+ const logLevel = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
23205
+ const logQuiet = logLevel === "quiet";
23206
+ const logVerbose = logLevel === "verbose";
23207
+ if (!logQuiet) {
23208
+ console.error(`\u2501\u2501\u2501 ContextStream v${VERSION} \u2501\u2501\u2501`);
23209
+ }
23210
+ if (logVerbose) {
23211
+ console.error(` API: ${config.apiUrl}`);
23212
+ console.error(` Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "None"}`);
23213
+ }
21890
23214
  const transport = new StdioServerTransport();
21891
23215
  await server.connect(transport);
21892
- console.error("ContextStream MCP server connected and ready");
23216
+ if (!logQuiet) {
23217
+ console.error("\u2713 ready");
23218
+ }
21893
23219
  showFirstRunMessage();
21894
23220
  checkForUpdates().catch(() => {
21895
23221
  });