@contextstream/mcp-server 0.4.39 → 0.4.40

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 +207 -1746
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8872,15 +8872,16 @@ function applyMcpToolPrefix(markdown, toolPrefix) {
8872
8872
  return markdown.replace(toolRegex, `${toolPrefix}$1`);
8873
8873
  }
8874
8874
  var CONTEXTSTREAM_RULES_FULL = `
8875
- ## ContextStream Rules
8875
+ ## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
8876
8876
 
8877
8877
  <contextstream_rules>
8878
- 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
8879
- 2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
8880
- 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
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
8881
8881
  </contextstream_rules>
8882
8882
 
8883
- **Display this block at the start of responses to keep rules in context.**
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
8885
 
8885
8886
  ---
8886
8887
 
@@ -8939,10 +8940,8 @@ This applies to **EVERY search** throughout the **ENTIRE conversation**, not jus
8939
8940
  - **Apply the prevention steps** from each lesson to avoid repeating mistakes
8940
8941
 
8941
8942
  ### On \`context_smart\`:
8942
- - Check for \`[LESSONS_WARNING]\` tag in the response
8943
- - If present, you **MUST** tell the user about the lessons before proceeding
8944
- - Lessons are proactively fetched when risky actions are detected (refactor, migrate, deploy, etc.)
8945
- - **Do not skip or bury this warning** - lessons represent real past mistakes
8943
+ - Check for any lessons in the returned context
8944
+ - Lessons may be included based on semantic relevance to the user's message
8946
8945
 
8947
8946
  ### Before ANY Non-Trivial Work:
8948
8947
  **ALWAYS call \`session(action="get_lessons", query="<topic>")\`** where \`<topic>\` matches what you're about to do:
@@ -8966,42 +8965,22 @@ You have access to ContextStream MCP tools for persistent memory and context.
8966
8965
  v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
8967
8966
  Rules Version: ${RULES_VERSION}
8968
8967
 
8969
- ## TL;DR - WHEN TO USE CONTEXT
8968
+ ## TL;DR - REQUIRED EVERY MESSAGE
8970
8969
 
8971
- | Request Type | What to Do |
8972
- |--------------|------------|
8973
- | **\u{1F680} Simple utility** (list workspaces, show version) | **Just execute directly** - skip session_init, context_smart, capture |
8974
- | **\u{1F4BB} Coding task** (edit, create, refactor) | Full context: session_init \u2192 context_smart \u2192 work \u2192 capture |
8975
- | **\u{1F50D} Code search/discovery** | session_init \u2192 context_smart \u2192 search() |
8976
- | **\u26A0\uFE0F Risky work** (deploy, migrate, refactor) | Check lessons first: \`session(action="get_lessons")\` |
8977
- | **User frustration/correction** | Capture lesson: \`session(action="capture_lesson", ...)\` |
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 |
8978
8980
 
8979
- ### Simple Utility Operations - FAST PATH
8981
+ **NO EXCEPTIONS.** Do not skip even if you think you have enough context.
8980
8982
 
8981
- **For simple queries, just execute and respond:**
8982
- - "list workspaces" \u2192 \`workspace(action="list")\` \u2192 done
8983
- - "list projects" \u2192 \`project(action="list")\` \u2192 done
8984
- - "show version" \u2192 \`help(action="version")\` \u2192 done
8985
- - "what reminders do I have" \u2192 \`reminder(action="list")\` \u2192 done
8986
-
8987
- **No session_init. No context_smart. No capture.** These add noise, not value.
8988
-
8989
- ### Coding Tasks - FULL CONTEXT
8990
-
8991
- | Step | What to Call |
8992
- |------|--------------|
8993
- | **1st message** | \`session_init(folder_path="...", context_hint="<msg>")\`, then \`context_smart(...)\` |
8994
- | **2nd+ messages** | \`context_smart(user_message="<msg>", format="minified", max_tokens=400)\` |
8995
- | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
8996
- | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
8997
- | **User correction** | \`session(action="capture_lesson", ...)\` |
8998
-
8999
- **How to detect simple utility operations:**
9000
- - Single-word commands: "list", "show", "version", "help"
9001
- - Data retrieval with no context dependency: "list my workspaces", "what projects do I have"
9002
- - Status checks: "am I authenticated?", "what's the server version?"
9003
-
9004
- **First message rule (for coding tasks):** After \`session_init\`:
8983
+ **First message rule:** After \`session_init\`:
9005
8984
  1. Check for \`lessons\` in response - if present, READ and SUMMARIZE them to user
9006
8985
  2. Then call \`context_smart\` before any other tool or response
9007
8986
 
@@ -9015,9 +8994,9 @@ Rules Version: ${RULES_VERSION}
9015
8994
 
9016
8995
  v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode dispatch:
9017
8996
 
9018
- ### Standalone Tools
9019
- - **\`session_init\`** - Initialize session with workspace detection + context (skip for simple utility operations)
9020
- - **\`context_smart\`** - Semantic search for relevant context (skip for simple utility operations)
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\`)
9021
9000
 
9022
9001
  ### Domain Tools (Use action/mode parameter)
9023
9002
 
@@ -9064,21 +9043,10 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
9064
9043
 
9065
9044
  ---
9066
9045
 
9067
- ### Rules, Version & Lessons Notices
9068
-
9069
- **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
9046
+ ### Rules Update Notices
9070
9047
 
9071
- **[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
9072
- 1. A new MCP server version is available
9073
- 2. The exact update command to run
9074
- 3. That they need to restart their AI tool after updating
9075
- **Do not skip this** - users often miss stderr warnings.
9076
-
9077
- **[LESSONS_WARNING]** - You **MUST** before proceeding:
9078
- 1. Read all lessons listed
9079
- 2. Tell the user about relevant lessons
9080
- 3. Explain how you will avoid each past mistake
9081
- **This is critical** - ignoring lessons leads to repeated failures.
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.
9082
9050
 
9083
9051
  ---
9084
9052
 
@@ -9209,24 +9177,16 @@ If ContextStream returns results, stop and use them. NEVER use local Search/Expl
9209
9177
 
9210
9178
  ---
9211
9179
 
9212
- ### When to Capture
9180
+ ### When to Capture (MANDATORY)
9213
9181
 
9214
9182
  | When | Call | Example |
9215
9183
  |------|------|---------|
9216
9184
  | User makes decision | \`session(action="capture", event_type="decision", ...)\` | "Let's use PostgreSQL" |
9217
9185
  | User states preference | \`session(action="capture", event_type="preference", ...)\` | "I prefer TypeScript" |
9218
- | Complete significant task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
9186
+ | You complete a task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
9219
9187
  | Need past context | \`session(action="recall", query="...")\` | "What did we decide about X?" |
9220
9188
 
9221
- **DO NOT capture utility operations:**
9222
- - \u274C "Listed workspaces" - not meaningful context
9223
- - \u274C "Showed version" - not a decision
9224
- - \u274C "Listed projects" - just data retrieval
9225
-
9226
- **DO capture meaningful work:**
9227
- - \u2705 Decisions, preferences, completed features
9228
- - \u2705 Lessons from mistakes
9229
- - \u2705 Insights about architecture or patterns
9189
+ **You MUST capture after completing any significant task.** This ensures future sessions have context.
9230
9190
 
9231
9191
  ---
9232
9192
 
@@ -9306,15 +9266,16 @@ memory(action="create_task", title="<task title>", plan_id="<plan_id from step 1
9306
9266
  See full documentation: https://contextstream.io/docs/mcp/tools
9307
9267
  `.trim();
9308
9268
  var CONTEXTSTREAM_RULES_MINIMAL = `
9309
- ## ContextStream Rules
9269
+ ## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
9310
9270
 
9311
9271
  <contextstream_rules>
9312
- 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
9313
- 2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
9314
- 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
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
9315
9275
  </contextstream_rules>
9316
9276
 
9317
- **Display this block at the start of responses to keep rules in context.**
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.
9318
9279
 
9319
9280
  ---
9320
9281
 
@@ -9323,7 +9284,7 @@ var CONTEXTSTREAM_RULES_MINIMAL = `
9323
9284
  Rules Version: ${RULES_VERSION}
9324
9285
  **Note:** PreToolUse hooks block Glob/Grep/Search when ContextStream is available.
9325
9286
 
9326
- ### For Coding Tasks
9287
+ ### Required Every Message
9327
9288
 
9328
9289
  | Action | Tool Call |
9329
9290
  |--------|-----------|
@@ -9357,33 +9318,6 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
9357
9318
  | \`memory\` | \`memory(action="list_events", limit=10)\` |
9358
9319
  | \`graph\` | \`graph(action="dependencies", file_path="...")\` |
9359
9320
 
9360
- ### \u{1F680} FAST PATH: Simple Utility Operations
9361
-
9362
- **For simple utility commands, SKIP the ceremony and just execute directly:**
9363
-
9364
- | Command Type | Just Call | Skip |
9365
- |--------------|-----------|------|
9366
- | List workspaces | \`workspace(action="list")\` | session_init, context_smart, capture |
9367
- | List projects | \`project(action="list")\` | session_init, context_smart, capture |
9368
- | Show version | \`help(action="version")\` | session_init, context_smart, capture |
9369
- | List reminders | \`reminder(action="list")\` | session_init, context_smart, capture |
9370
- | Check auth | \`help(action="auth")\` | session_init, context_smart, capture |
9371
-
9372
- **Detect simple operations by these patterns:**
9373
- - "list ...", "show ...", "what are my ...", "get ..."
9374
- - Single-action queries with no context dependency
9375
- - User just wants data, not analysis or coding help
9376
-
9377
- **DO NOT add overhead for utility operations:**
9378
- - \u274C Don't call session_init just to list workspaces
9379
- - \u274C Don't call context_smart for simple queries
9380
- - \u274C Don't capture "listed workspaces" as an event (that's noise)
9381
-
9382
- **Use full context ceremony ONLY for:**
9383
- - Coding tasks (edit, create, refactor, debug)
9384
- - Search/discovery (finding code, understanding architecture)
9385
- - Tasks where past decisions or lessons matter
9386
-
9387
9321
  ### Lessons (Past Mistakes)
9388
9322
 
9389
9323
  - After \`session_init\`: Check for \`lessons\` field and apply before work
@@ -9392,27 +9326,8 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
9392
9326
 
9393
9327
  ### Context Pressure & Compaction
9394
9328
 
9395
- - If \`context_smart\` returns high/critical \`context_pressure\`: call \`session(action="capture", ...)\` to save state
9396
- - PreCompact hooks automatically save snapshots before compaction (if installed)
9397
-
9398
- ### Automatic Context Restoration
9399
-
9400
- **Context restoration is now enabled by default.** Every \`session_init\` call automatically:
9401
- - Restores context from recent snapshots (if available)
9402
- - Returns \`restored_context\` field with snapshot data
9403
- - Sets \`is_post_compact=true\` in response when restoration occurs
9404
-
9405
- **No special handling needed after compaction** - just call \`session_init\` normally.
9406
-
9407
- To disable automatic restoration:
9408
- - Pass \`is_post_compact=false\` in the API call
9409
- - Or set \`CONTEXTSTREAM_RESTORE_CONTEXT=false\` environment variable
9410
-
9411
- ### Notices - MUST HANDLE IMMEDIATELY
9412
-
9413
- - **[VERSION_NOTICE]**: Tell the user about the update and command to run
9414
- - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
9415
- - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
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()\`
9416
9331
 
9417
9332
  ### Plans & Tasks
9418
9333
 
@@ -9890,7 +9805,7 @@ var PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
9890
9805
  ContextStream PreCompact Hook for Claude Code
9891
9806
 
9892
9807
  Runs BEFORE conversation context is compacted (manual via /compact or automatic).
9893
- Automatically saves conversation state to ContextStream by parsing the transcript.
9808
+ Injects a reminder for the AI to save conversation state using session_capture_smart.
9894
9809
 
9895
9810
  Input (via stdin):
9896
9811
  {
@@ -9906,7 +9821,7 @@ Output (to stdout):
9906
9821
  {
9907
9822
  "hookSpecificOutput": {
9908
9823
  "hookEventName": "PreCompact",
9909
- "additionalContext": "... status message ..."
9824
+ "additionalContext": "... instructions for AI ..."
9910
9825
  }
9911
9826
  }
9912
9827
  """
@@ -9914,149 +9829,8 @@ Output (to stdout):
9914
9829
  import json
9915
9830
  import sys
9916
9831
  import os
9917
- import re
9918
- import urllib.request
9919
- import urllib.error
9920
9832
 
9921
9833
  ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
9922
- AUTO_SAVE = os.environ.get("CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE", "true").lower() == "true"
9923
- API_URL = os.environ.get("CONTEXTSTREAM_API_URL", "https://api.contextstream.io")
9924
- API_KEY = os.environ.get("CONTEXTSTREAM_API_KEY", "")
9925
-
9926
- WORKSPACE_ID = None
9927
-
9928
- def load_config_from_mcp_json(cwd):
9929
- """Load API config from .mcp.json if env vars not set."""
9930
- global API_URL, API_KEY, WORKSPACE_ID
9931
-
9932
- # Try to find .mcp.json and .contextstream/config.json in cwd or parent directories
9933
- search_dir = cwd
9934
- for _ in range(5): # Search up to 5 levels
9935
- # Load API config from .mcp.json
9936
- if not API_KEY:
9937
- mcp_path = os.path.join(search_dir, ".mcp.json")
9938
- if os.path.exists(mcp_path):
9939
- try:
9940
- with open(mcp_path, 'r') as f:
9941
- config = json.load(f)
9942
- servers = config.get("mcpServers", {})
9943
- cs_config = servers.get("contextstream", {})
9944
- env = cs_config.get("env", {})
9945
- if env.get("CONTEXTSTREAM_API_KEY"):
9946
- API_KEY = env["CONTEXTSTREAM_API_KEY"]
9947
- if env.get("CONTEXTSTREAM_API_URL"):
9948
- API_URL = env["CONTEXTSTREAM_API_URL"]
9949
- except:
9950
- pass
9951
-
9952
- # Load workspace_id from .contextstream/config.json
9953
- if not WORKSPACE_ID:
9954
- cs_config_path = os.path.join(search_dir, ".contextstream", "config.json")
9955
- if os.path.exists(cs_config_path):
9956
- try:
9957
- with open(cs_config_path, 'r') as f:
9958
- cs_config = json.load(f)
9959
- if cs_config.get("workspace_id"):
9960
- WORKSPACE_ID = cs_config["workspace_id"]
9961
- except:
9962
- pass
9963
-
9964
- parent = os.path.dirname(search_dir)
9965
- if parent == search_dir:
9966
- break
9967
- search_dir = parent
9968
-
9969
- def parse_transcript(transcript_path):
9970
- """Parse transcript to extract active files, decisions, and context."""
9971
- active_files = set()
9972
- recent_messages = []
9973
- tool_calls = []
9974
-
9975
- try:
9976
- with open(transcript_path, 'r') as f:
9977
- for line in f:
9978
- try:
9979
- entry = json.loads(line.strip())
9980
- msg_type = entry.get("type", "")
9981
-
9982
- # Extract files from tool calls
9983
- if msg_type == "tool_use":
9984
- tool_name = entry.get("name", "")
9985
- tool_input = entry.get("input", {})
9986
- tool_calls.append({"name": tool_name, "input": tool_input})
9987
-
9988
- # Extract file paths from common tools
9989
- if tool_name in ["Read", "Write", "Edit", "NotebookEdit"]:
9990
- file_path = tool_input.get("file_path") or tool_input.get("notebook_path")
9991
- if file_path:
9992
- active_files.add(file_path)
9993
- elif tool_name == "Glob":
9994
- pattern = tool_input.get("pattern", "")
9995
- if pattern:
9996
- active_files.add(f"[glob:{pattern}]")
9997
-
9998
- # Collect recent assistant messages for summary
9999
- if msg_type == "assistant" and entry.get("content"):
10000
- content = entry.get("content", "")
10001
- if isinstance(content, str) and len(content) > 50:
10002
- recent_messages.append(content[:500])
10003
-
10004
- except json.JSONDecodeError:
10005
- continue
10006
- except Exception as e:
10007
- pass
10008
-
10009
- return {
10010
- "active_files": list(active_files)[-20:], # Last 20 files
10011
- "tool_call_count": len(tool_calls),
10012
- "message_count": len(recent_messages),
10013
- "last_tools": [t["name"] for t in tool_calls[-10:]], # Last 10 tool names
10014
- }
10015
-
10016
- def save_snapshot(session_id, transcript_data, trigger):
10017
- """Save snapshot to ContextStream API."""
10018
- if not API_KEY:
10019
- return False, "No API key configured"
10020
-
10021
- snapshot_content = {
10022
- "session_id": session_id,
10023
- "trigger": trigger,
10024
- "captured_at": None, # API will set timestamp
10025
- "active_files": transcript_data.get("active_files", []),
10026
- "tool_call_count": transcript_data.get("tool_call_count", 0),
10027
- "last_tools": transcript_data.get("last_tools", []),
10028
- "auto_captured": True,
10029
- }
10030
-
10031
- payload = {
10032
- "event_type": "session_snapshot",
10033
- "title": f"Auto Pre-compaction Snapshot ({trigger})",
10034
- "content": json.dumps(snapshot_content),
10035
- "importance": "high",
10036
- "tags": ["session_snapshot", "pre_compaction", "auto_captured"],
10037
- "source_type": "hook",
10038
- }
10039
-
10040
- # Add workspace_id if available
10041
- if WORKSPACE_ID:
10042
- payload["workspace_id"] = WORKSPACE_ID
10043
-
10044
- try:
10045
- req = urllib.request.Request(
10046
- f"{API_URL}/api/v1/memory/events",
10047
- data=json.dumps(payload).encode('utf-8'),
10048
- headers={
10049
- "Content-Type": "application/json",
10050
- "X-API-Key": API_KEY,
10051
- },
10052
- method="POST"
10053
- )
10054
- with urllib.request.urlopen(req, timeout=5) as resp:
10055
- return True, "Snapshot saved"
10056
- except urllib.error.URLError as e:
10057
- return False, str(e)
10058
- except Exception as e:
10059
- return False, str(e)
10060
9834
 
10061
9835
  def main():
10062
9836
  if not ENABLED:
@@ -10067,38 +9841,26 @@ def main():
10067
9841
  except:
10068
9842
  sys.exit(0)
10069
9843
 
10070
- # Load config from .mcp.json if env vars not set
10071
- cwd = data.get("cwd", os.getcwd())
10072
- load_config_from_mcp_json(cwd)
10073
-
10074
- session_id = data.get("session_id", "unknown")
10075
- transcript_path = data.get("transcript_path", "")
10076
9844
  trigger = data.get("trigger", "unknown")
10077
9845
  custom_instructions = data.get("custom_instructions", "")
10078
9846
 
10079
- # Parse transcript for context
10080
- transcript_data = {}
10081
- if transcript_path and os.path.exists(transcript_path):
10082
- transcript_data = parse_transcript(transcript_path)
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:
10083
9850
 
10084
- # Auto-save snapshot if enabled
10085
- auto_save_status = ""
10086
- if AUTO_SAVE and API_KEY:
10087
- success, msg = save_snapshot(session_id, transcript_data, trigger)
10088
- if success:
10089
- auto_save_status = f"\\n[ContextStream: Auto-saved snapshot with {len(transcript_data.get('active_files', []))} active files]"
10090
- else:
10091
- auto_save_status = f"\\n[ContextStream: Auto-save failed - {msg}]"
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>")
10092
9852
 
10093
- # Build context injection for the AI (backup in case auto-save fails)
10094
- files_list = ", ".join(transcript_data.get("active_files", [])[:5]) or "none detected"
10095
- context = f"""[CONTEXT COMPACTION - {trigger.upper()}]{auto_save_status}
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
10096
9859
 
10097
- Active files detected: {files_list}
10098
- Tool calls in session: {transcript_data.get('tool_call_count', 0)}
9860
+ 3. After compaction, call session_init(is_post_compact=true) to restore context.
10099
9861
 
10100
- After compaction, call session_init(is_post_compact=true) to restore context.
10101
- {f"User instructions: {custom_instructions}" if custom_instructions else ""}"""
9862
+ {f"User instructions: {custom_instructions}" if custom_instructions else ""}
9863
+ [END COMPACTION WARNING]"""
10102
9864
 
10103
9865
  output = {
10104
9866
  "hookSpecificOutput": {
@@ -10212,760 +9974,80 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
10212
9974
  for (const [hookType, matchers] of Object.entries(newHooks || {})) {
10213
9975
  if (!matchers) continue;
10214
9976
  const existing = existingHooks?.[hookType] || [];
10215
- const filtered = existing.filter((m) => {
10216
- return !m.hooks?.some((h) => h.command?.includes("contextstream"));
10217
- });
10218
- existingHooks[hookType] = [...filtered, ...matchers];
10219
- }
10220
- settings.hooks = existingHooks;
10221
- return settings;
10222
- }
10223
- function getIndexStatusPath() {
10224
- return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
10225
- }
10226
- async function readIndexStatus() {
10227
- const statusPath = getIndexStatusPath();
10228
- try {
10229
- const content = await fs4.readFile(statusPath, "utf-8");
10230
- return JSON.parse(content);
10231
- } catch {
10232
- return { version: 1, projects: {} };
10233
- }
10234
- }
10235
- async function writeIndexStatus(status) {
10236
- const statusPath = getIndexStatusPath();
10237
- const dir = path5.dirname(statusPath);
10238
- await fs4.mkdir(dir, { recursive: true });
10239
- await fs4.writeFile(statusPath, JSON.stringify(status, null, 2));
10240
- }
10241
- async function markProjectIndexed(projectPath, options) {
10242
- const status = await readIndexStatus();
10243
- const resolvedPath = path5.resolve(projectPath);
10244
- status.projects[resolvedPath] = {
10245
- indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
10246
- project_id: options?.project_id,
10247
- project_name: options?.project_name
10248
- };
10249
- await writeIndexStatus(status);
10250
- }
10251
- var CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
10252
- """
10253
- ContextStream PreToolUse Hook for Cline
10254
- Blocks discovery tools and redirects to ContextStream search.
10255
-
10256
- Cline hooks use JSON output format:
10257
- {
10258
- "cancel": true/false,
10259
- "errorMessage": "optional error description",
10260
- "contextModification": "optional text to inject"
10261
- }
10262
- """
10263
-
10264
- import json
10265
- import sys
10266
- import os
10267
- from pathlib import Path
10268
- from datetime import datetime, timedelta
10269
-
10270
- ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
10271
- INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
10272
- STALE_THRESHOLD_DAYS = 7
10273
-
10274
- DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
10275
-
10276
- def is_discovery_glob(pattern):
10277
- pattern_lower = pattern.lower()
10278
- for p in DISCOVERY_PATTERNS:
10279
- if p in pattern_lower:
10280
- return True
10281
- if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
10282
- return True
10283
- if "**" in pattern or "*/" in pattern:
10284
- return True
10285
- return False
10286
-
10287
- def is_discovery_grep(file_path):
10288
- if not file_path or file_path in [".", "./", "*", "**"]:
10289
- return True
10290
- if "*" in file_path or "**" in file_path:
10291
- return True
10292
- return False
10293
-
10294
- def is_project_indexed(workspace_roots):
10295
- """Check if any workspace root is in an indexed project."""
10296
- if not INDEX_STATUS_FILE.exists():
10297
- return False, False
10298
-
10299
- try:
10300
- with open(INDEX_STATUS_FILE, "r") as f:
10301
- data = json.load(f)
10302
- except:
10303
- return False, False
10304
-
10305
- projects = data.get("projects", {})
10306
-
10307
- for workspace in workspace_roots:
10308
- cwd_path = Path(workspace).resolve()
10309
- for project_path, info in projects.items():
10310
- try:
10311
- indexed_path = Path(project_path).resolve()
10312
- if cwd_path == indexed_path or indexed_path in cwd_path.parents:
10313
- indexed_at = info.get("indexed_at")
10314
- if indexed_at:
10315
- try:
10316
- indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
10317
- if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
10318
- return True, True
10319
- except:
10320
- pass
10321
- return True, False
10322
- except:
10323
- continue
10324
- return False, False
10325
-
10326
- def output_allow(context_mod=None):
10327
- result = {"cancel": False}
10328
- if context_mod:
10329
- result["contextModification"] = context_mod
10330
- print(json.dumps(result))
10331
- sys.exit(0)
10332
-
10333
- def output_block(error_msg, context_mod=None):
10334
- result = {"cancel": True, "errorMessage": error_msg}
10335
- if context_mod:
10336
- result["contextModification"] = context_mod
10337
- print(json.dumps(result))
10338
- sys.exit(0)
10339
-
10340
- def main():
10341
- if not ENABLED:
10342
- output_allow()
10343
-
10344
- try:
10345
- data = json.load(sys.stdin)
10346
- except:
10347
- output_allow()
10348
-
10349
- hook_name = data.get("hookName", "")
10350
- if hook_name != "PreToolUse":
10351
- output_allow()
10352
-
10353
- tool = data.get("toolName", "")
10354
- params = data.get("toolParameters", {})
10355
- workspace_roots = data.get("workspaceRoots", [])
10356
-
10357
- # Check if project is indexed
10358
- is_indexed, is_stale = is_project_indexed(workspace_roots)
10359
- if not is_indexed:
10360
- output_allow()
10361
-
10362
- # Check for discovery patterns
10363
- if tool == "list_files" or tool == "search_files":
10364
- pattern = params.get("path", "") or params.get("regex", "")
10365
- if is_discovery_glob(pattern) or is_discovery_grep(pattern):
10366
- output_block(
10367
- f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
10368
- "ContextStream search is indexed and faster. Only use local tools if ContextStream returns 0 results.",
10369
- "[CONTEXTSTREAM] Use ContextStream search for code discovery."
10370
- )
10371
-
10372
- elif tool == "read_file":
10373
- # Allow read_file by default - blocking discovery at search level is enough
10374
- pass
10375
-
10376
- output_allow()
10377
-
10378
- if __name__ == "__main__":
10379
- main()
10380
- `;
10381
- var CLINE_USER_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env python3
10382
- """
10383
- ContextStream UserPromptSubmit Hook for Cline
10384
- Injects reminder about ContextStream rules on every message.
10385
- """
10386
-
10387
- import json
10388
- import sys
10389
- import os
10390
-
10391
- ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
10392
-
10393
- REMINDER = """[CONTEXTSTREAM RULES]
10394
- 1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="hybrid") FIRST
10395
- 2. Call context_smart at start of EVERY response
10396
- 3. Local tools ONLY if ContextStream returns 0 results
10397
- [END RULES]"""
10398
-
10399
- def main():
10400
- if not ENABLED:
10401
- print(json.dumps({"cancel": False}))
10402
- sys.exit(0)
10403
-
10404
- try:
10405
- json.load(sys.stdin)
10406
- except:
10407
- print(json.dumps({"cancel": False}))
10408
- sys.exit(0)
10409
-
10410
- print(json.dumps({
10411
- "cancel": False,
10412
- "contextModification": REMINDER
10413
- }))
10414
- sys.exit(0)
10415
-
10416
- if __name__ == "__main__":
10417
- main()
10418
- `;
10419
- function getClineHooksDir(scope, projectPath) {
10420
- if (scope === "global") {
10421
- return path5.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
10422
- }
10423
- if (!projectPath) {
10424
- throw new Error("projectPath required for project scope");
10425
- }
10426
- return path5.join(projectPath, ".clinerules", "hooks");
10427
- }
10428
- async function installClineHookScripts(options) {
10429
- const hooksDir = getClineHooksDir(options.scope, options.projectPath);
10430
- await fs4.mkdir(hooksDir, { recursive: true });
10431
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10432
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10433
- await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10434
- await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10435
- return {
10436
- preToolUse: preToolUsePath,
10437
- userPromptSubmit: userPromptPath
10438
- };
10439
- }
10440
- function getRooCodeHooksDir(scope, projectPath) {
10441
- if (scope === "global") {
10442
- return path5.join(homedir2(), ".roo", "hooks");
10443
- }
10444
- if (!projectPath) {
10445
- throw new Error("projectPath required for project scope");
10446
- }
10447
- return path5.join(projectPath, ".roo", "hooks");
10448
- }
10449
- async function installRooCodeHookScripts(options) {
10450
- const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
10451
- await fs4.mkdir(hooksDir, { recursive: true });
10452
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10453
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10454
- await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10455
- await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10456
- return {
10457
- preToolUse: preToolUsePath,
10458
- userPromptSubmit: userPromptPath
10459
- };
10460
- }
10461
- function getKiloCodeHooksDir(scope, projectPath) {
10462
- if (scope === "global") {
10463
- return path5.join(homedir2(), ".kilocode", "hooks");
10464
- }
10465
- if (!projectPath) {
10466
- throw new Error("projectPath required for project scope");
10467
- }
10468
- return path5.join(projectPath, ".kilocode", "hooks");
10469
- }
10470
- async function installKiloCodeHookScripts(options) {
10471
- const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
10472
- await fs4.mkdir(hooksDir, { recursive: true });
10473
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
10474
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
10475
- await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10476
- await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
10477
- return {
10478
- preToolUse: preToolUsePath,
10479
- userPromptSubmit: userPromptPath
10480
- };
10481
- }
10482
- var CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
10483
- """
10484
- ContextStream PreToolUse Hook for Cursor
10485
- Blocks discovery tools and redirects to ContextStream search.
10486
-
10487
- Cursor hooks use JSON output format:
10488
- {
10489
- "decision": "allow" | "deny",
10490
- "reason": "optional error description"
10491
- }
10492
- """
10493
-
10494
- import json
10495
- import sys
10496
- import os
10497
- from pathlib import Path
10498
- from datetime import datetime, timedelta
10499
-
10500
- ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
10501
- INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
10502
- STALE_THRESHOLD_DAYS = 7
10503
-
10504
- DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
10505
-
10506
- def is_discovery_glob(pattern):
10507
- pattern_lower = pattern.lower()
10508
- for p in DISCOVERY_PATTERNS:
10509
- if p in pattern_lower:
10510
- return True
10511
- if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
10512
- return True
10513
- if "**" in pattern or "*/" in pattern:
10514
- return True
10515
- return False
10516
-
10517
- def is_discovery_grep(file_path):
10518
- if not file_path or file_path in [".", "./", "*", "**"]:
10519
- return True
10520
- if "*" in file_path or "**" in file_path:
10521
- return True
10522
- return False
10523
-
10524
- def is_project_indexed(workspace_roots):
10525
- """Check if any workspace root is in an indexed project."""
10526
- if not INDEX_STATUS_FILE.exists():
10527
- return False, False
10528
-
10529
- try:
10530
- with open(INDEX_STATUS_FILE, "r") as f:
10531
- data = json.load(f)
10532
- except:
10533
- return False, False
10534
-
10535
- projects = data.get("projects", {})
10536
-
10537
- for workspace in workspace_roots:
10538
- cwd_path = Path(workspace).resolve()
10539
- for project_path, info in projects.items():
10540
- try:
10541
- indexed_path = Path(project_path).resolve()
10542
- if cwd_path == indexed_path or indexed_path in cwd_path.parents:
10543
- indexed_at = info.get("indexed_at")
10544
- if indexed_at:
10545
- try:
10546
- indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
10547
- if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
10548
- return True, True
10549
- except:
10550
- pass
10551
- return True, False
10552
- except:
10553
- continue
10554
- return False, False
10555
-
10556
- def output_allow():
10557
- print(json.dumps({"decision": "allow"}))
10558
- sys.exit(0)
10559
-
10560
- def output_deny(reason):
10561
- print(json.dumps({"decision": "deny", "reason": reason}))
10562
- sys.exit(0)
10563
-
10564
- def main():
10565
- if not ENABLED:
10566
- output_allow()
10567
-
10568
- try:
10569
- data = json.load(sys.stdin)
10570
- except:
10571
- output_allow()
10572
-
10573
- hook_name = data.get("hook_event_name", "")
10574
- if hook_name != "preToolUse":
10575
- output_allow()
10576
-
10577
- tool = data.get("tool_name", "")
10578
- params = data.get("tool_input", {}) or data.get("parameters", {})
10579
- workspace_roots = data.get("workspace_roots", [])
10580
-
10581
- # Check if project is indexed
10582
- is_indexed, _ = is_project_indexed(workspace_roots)
10583
- if not is_indexed:
10584
- output_allow()
10585
-
10586
- # Check for Cursor tools
10587
- if tool in ["Glob", "glob", "list_files"]:
10588
- pattern = params.get("pattern", "") or params.get("path", "")
10589
- if is_discovery_glob(pattern):
10590
- output_deny(
10591
- f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
10592
- "ContextStream search is indexed and faster."
10593
- )
10594
-
10595
- elif tool in ["Grep", "grep", "search_files", "ripgrep"]:
10596
- pattern = params.get("pattern", "") or params.get("regex", "")
10597
- file_path = params.get("path", "")
10598
- if is_discovery_grep(file_path):
10599
- output_deny(
10600
- f"Use mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") instead of {tool}. "
10601
- "ContextStream search is indexed and faster."
10602
- )
10603
-
10604
- output_allow()
10605
-
10606
- if __name__ == "__main__":
10607
- main()
10608
- `;
10609
- var CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT = `#!/usr/bin/env python3
10610
- """
10611
- ContextStream BeforeSubmitPrompt Hook for Cursor
10612
- Injects reminder about ContextStream rules.
10613
- """
10614
-
10615
- import json
10616
- import sys
10617
- import os
10618
-
10619
- ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
10620
-
10621
- def main():
10622
- if not ENABLED:
10623
- print(json.dumps({"continue": True}))
10624
- sys.exit(0)
10625
-
10626
- try:
10627
- json.load(sys.stdin)
10628
- except:
10629
- print(json.dumps({"continue": True}))
10630
- sys.exit(0)
10631
-
10632
- print(json.dumps({
10633
- "continue": True,
10634
- "user_message": "[CONTEXTSTREAM] Search with mcp__contextstream__search before using Glob/Grep/Read"
10635
- }))
10636
- sys.exit(0)
10637
-
10638
- if __name__ == "__main__":
10639
- main()
10640
- `;
10641
- function getCursorHooksConfigPath(scope, projectPath) {
10642
- if (scope === "global") {
10643
- return path5.join(homedir2(), ".cursor", "hooks.json");
10644
- }
10645
- if (!projectPath) {
10646
- throw new Error("projectPath required for project scope");
10647
- }
10648
- return path5.join(projectPath, ".cursor", "hooks.json");
10649
- }
10650
- function getCursorHooksDir(scope, projectPath) {
10651
- if (scope === "global") {
10652
- return path5.join(homedir2(), ".cursor", "hooks");
10653
- }
10654
- if (!projectPath) {
10655
- throw new Error("projectPath required for project scope");
10656
- }
10657
- return path5.join(projectPath, ".cursor", "hooks");
10658
- }
10659
- async function readCursorHooksConfig(scope, projectPath) {
10660
- const configPath = getCursorHooksConfigPath(scope, projectPath);
10661
- try {
10662
- const content = await fs4.readFile(configPath, "utf-8");
10663
- return JSON.parse(content);
10664
- } catch {
10665
- return { version: 1, hooks: {} };
10666
- }
10667
- }
10668
- async function writeCursorHooksConfig(config, scope, projectPath) {
10669
- const configPath = getCursorHooksConfigPath(scope, projectPath);
10670
- const dir = path5.dirname(configPath);
10671
- await fs4.mkdir(dir, { recursive: true });
10672
- await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
10673
- }
10674
- async function installCursorHookScripts(options) {
10675
- const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
10676
- await fs4.mkdir(hooksDir, { recursive: true });
10677
- const preToolUsePath = path5.join(hooksDir, "contextstream-pretooluse.py");
10678
- const beforeSubmitPath = path5.join(hooksDir, "contextstream-beforesubmit.py");
10679
- await fs4.writeFile(preToolUsePath, CURSOR_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
10680
- await fs4.writeFile(beforeSubmitPath, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT, { mode: 493 });
10681
- const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
10682
- const filterContextStreamHooks = (hooks) => {
10683
- if (!hooks) return [];
10684
- return hooks.filter((h) => !h.command?.includes("contextstream"));
10685
- };
10686
- const config = {
10687
- version: 1,
10688
- hooks: {
10689
- ...existingConfig.hooks,
10690
- preToolUse: [
10691
- ...filterContextStreamHooks(existingConfig.hooks.preToolUse),
10692
- {
10693
- command: `python3 "${preToolUsePath}"`,
10694
- type: "command",
10695
- timeout: 5,
10696
- matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
10697
- }
10698
- ],
10699
- beforeSubmitPrompt: [
10700
- ...filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt),
10701
- {
10702
- command: `python3 "${beforeSubmitPath}"`,
10703
- type: "command",
10704
- timeout: 5
10705
- }
10706
- ]
10707
- }
10708
- };
10709
- await writeCursorHooksConfig(config, options.scope, options.projectPath);
10710
- const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
10711
- return {
10712
- preToolUse: preToolUsePath,
10713
- beforeSubmitPrompt: beforeSubmitPath,
10714
- config: configPath
10715
- };
10716
- }
10717
- var WINDSURF_PRE_MCP_TOOL_USE_SCRIPT = `#!/usr/bin/env python3
10718
- """
10719
- ContextStream pre_mcp_tool_use Hook for Windsurf
10720
- Blocks discovery tools and redirects to ContextStream search.
10721
-
10722
- Exit codes:
10723
- - 0: Allow action to proceed
10724
- - 2: Block action (message to stderr)
10725
- """
10726
-
10727
- import json
10728
- import sys
10729
- import os
10730
-
10731
- ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
10732
-
10733
- # Tools to redirect
10734
- DISCOVERY_TOOLS = {
10735
- "read_file": "Use mcp__contextstream__search(mode=\\"hybrid\\") for discovery",
10736
- "search_files": "Use mcp__contextstream__search(mode=\\"hybrid\\")",
10737
- "list_files": "Use mcp__contextstream__search(mode=\\"pattern\\")",
10738
- "codebase_search": "Use mcp__contextstream__search(mode=\\"hybrid\\")",
10739
- "grep_search": "Use mcp__contextstream__search(mode=\\"keyword\\")",
10740
- }
10741
-
10742
- def is_project_indexed(workspace_roots):
10743
- """Check if any workspace has a .contextstream/index marker."""
10744
- for root in workspace_roots:
10745
- marker = os.path.join(root, ".contextstream", "index.json")
10746
- if os.path.exists(marker):
10747
- return True, root
10748
- return False, None
10749
-
10750
- def main():
10751
- if not ENABLED:
10752
- sys.exit(0)
10753
-
10754
- try:
10755
- data = json.load(sys.stdin)
10756
- except:
10757
- sys.exit(0)
10758
-
10759
- tool_info = data.get("tool_info", {})
10760
- tool_name = tool_info.get("tool_name", "")
10761
-
10762
- # For MCP tools, check the server and tool
10763
- mcp_server = tool_info.get("mcp_server", "")
10764
-
10765
- # Get workspace roots from the data
10766
- workspace_roots = []
10767
- if "working_directory" in data:
10768
- workspace_roots.append(data["working_directory"])
10769
-
10770
- # Check if project is indexed
10771
- is_indexed, _ = is_project_indexed(workspace_roots)
10772
- if not is_indexed:
10773
- sys.exit(0)
10774
-
10775
- # Check if this is a discovery tool we should redirect
10776
- if tool_name in DISCOVERY_TOOLS:
10777
- message = DISCOVERY_TOOLS[tool_name]
10778
- print(message, file=sys.stderr)
10779
- sys.exit(2)
10780
-
10781
- sys.exit(0)
10782
-
10783
- if __name__ == "__main__":
10784
- main()
10785
- `;
10786
- var WINDSURF_PRE_USER_PROMPT_SCRIPT = `#!/usr/bin/env python3
10787
- """
10788
- ContextStream pre_user_prompt Hook for Windsurf
10789
- Injects reminder about ContextStream rules.
10790
-
10791
- Note: This hook runs before prompt processing but cannot modify the prompt.
10792
- It primarily serves for logging and validation purposes.
10793
- """
10794
-
10795
- import json
10796
- import sys
10797
- import os
10798
-
10799
- ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
10800
-
10801
- def main():
10802
- if not ENABLED:
10803
- sys.exit(0)
10804
-
10805
- try:
10806
- json.load(sys.stdin)
10807
- except:
10808
- sys.exit(0)
10809
-
10810
- # Allow the prompt to proceed
10811
- sys.exit(0)
10812
-
10813
- if __name__ == "__main__":
10814
- main()
10815
- `;
10816
- function getWindsurfHooksConfigPath(scope, projectPath) {
10817
- if (scope === "project" && projectPath) {
10818
- return path5.join(projectPath, ".windsurf", "hooks.json");
10819
- }
10820
- return path5.join(homedir2(), ".codeium", "windsurf", "hooks.json");
10821
- }
10822
- function getWindsurfHooksDir(scope, projectPath) {
10823
- if (scope === "project" && projectPath) {
10824
- return path5.join(projectPath, ".windsurf", "hooks");
10825
- }
10826
- return path5.join(homedir2(), ".codeium", "windsurf", "hooks");
10827
- }
10828
- async function readWindsurfHooksConfig(scope, projectPath) {
10829
- const configPath = getWindsurfHooksConfigPath(scope, projectPath);
10830
- try {
10831
- const content = await fs4.promises.readFile(configPath, "utf-8");
10832
- return JSON.parse(content);
10833
- } catch {
10834
- return { hooks: {} };
10835
- }
10836
- }
10837
- async function writeWindsurfHooksConfig(config, scope, projectPath) {
10838
- const configPath = getWindsurfHooksConfigPath(scope, projectPath);
10839
- const configDir = path5.dirname(configPath);
10840
- await fs4.promises.mkdir(configDir, { recursive: true });
10841
- await fs4.promises.writeFile(configPath, JSON.stringify(config, null, 2));
10842
- }
10843
- function filterWindsurfContextStreamHooks(hooks) {
10844
- if (!hooks) return [];
10845
- return hooks.filter((h) => !h.command?.includes("contextstream"));
10846
- }
10847
- async function installWindsurfHookScripts(options) {
10848
- const scope = options.scope || "global";
10849
- const hooksDir = getWindsurfHooksDir(scope, options.projectPath);
10850
- await fs4.promises.mkdir(hooksDir, { recursive: true });
10851
- const preMcpToolUsePath = path5.join(hooksDir, "contextstream-pretooluse.py");
10852
- const preUserPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
10853
- await fs4.promises.writeFile(preMcpToolUsePath, WINDSURF_PRE_MCP_TOOL_USE_SCRIPT);
10854
- await fs4.promises.writeFile(preUserPromptPath, WINDSURF_PRE_USER_PROMPT_SCRIPT);
10855
- if (process.platform !== "win32") {
10856
- await fs4.promises.chmod(preMcpToolUsePath, 493);
10857
- await fs4.promises.chmod(preUserPromptPath, 493);
10858
- }
10859
- const existingConfig = await readWindsurfHooksConfig(scope, options.projectPath);
10860
- const config = {
10861
- hooks: {
10862
- ...existingConfig.hooks,
10863
- pre_mcp_tool_use: [
10864
- ...filterWindsurfContextStreamHooks(existingConfig.hooks.pre_mcp_tool_use),
10865
- {
10866
- command: `python3 "${preMcpToolUsePath}"`,
10867
- show_output: true
10868
- }
10869
- ],
10870
- pre_user_prompt: [
10871
- ...filterWindsurfContextStreamHooks(existingConfig.hooks.pre_user_prompt),
10872
- {
10873
- command: `python3 "${preUserPromptPath}"`,
10874
- show_output: false
10875
- }
10876
- ]
10877
- }
10878
- };
10879
- await writeWindsurfHooksConfig(config, scope, options.projectPath);
10880
- const configPath = getWindsurfHooksConfigPath(scope, options.projectPath);
10881
- return {
10882
- preMcpToolUse: preMcpToolUsePath,
10883
- preUserPrompt: preUserPromptPath,
10884
- config: configPath
10885
- };
10886
- }
10887
- async function installEditorHooks(options) {
10888
- const { editor, scope, projectPath, includePreCompact } = options;
10889
- switch (editor) {
10890
- case "claude": {
10891
- if (scope === "project" && !projectPath) {
10892
- throw new Error("projectPath required for project scope");
10893
- }
10894
- const scripts = await installHookScripts({ includePreCompact });
10895
- const hooksConfig = buildHooksConfig({ includePreCompact });
10896
- const settingsScope = scope === "global" ? "user" : "project";
10897
- const existing = await readClaudeSettings(settingsScope, projectPath);
10898
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
10899
- await writeClaudeSettings(merged, settingsScope, projectPath);
10900
- const installed = [scripts.preToolUse, scripts.userPrompt];
10901
- if (scripts.preCompact) installed.push(scripts.preCompact);
10902
- return {
10903
- editor: "claude",
10904
- installed,
10905
- hooksDir: getHooksDir()
10906
- };
10907
- }
10908
- case "cline": {
10909
- const scripts = await installClineHookScripts({ scope, projectPath });
10910
- return {
10911
- editor: "cline",
10912
- installed: [scripts.preToolUse, scripts.userPromptSubmit],
10913
- hooksDir: getClineHooksDir(scope, projectPath)
10914
- };
10915
- }
10916
- case "roo": {
10917
- const scripts = await installRooCodeHookScripts({ scope, projectPath });
10918
- return {
10919
- editor: "roo",
10920
- installed: [scripts.preToolUse, scripts.userPromptSubmit],
10921
- hooksDir: getRooCodeHooksDir(scope, projectPath)
10922
- };
9977
+ const filtered = existing.filter((m) => {
9978
+ return !m.hooks?.some((h) => h.command?.includes("contextstream"));
9979
+ });
9980
+ existingHooks[hookType] = [...filtered, ...matchers];
9981
+ }
9982
+ settings.hooks = existingHooks;
9983
+ return settings;
9984
+ }
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);
10923
9992
  }
10924
- case "kilo": {
10925
- const scripts = await installKiloCodeHookScripts({ scope, projectPath });
10926
- return {
10927
- editor: "kilo",
10928
- installed: [scripts.preToolUse, scripts.userPromptSubmit],
10929
- hooksDir: getKiloCodeHooksDir(scope, projectPath)
10930
- };
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"));
10931
10001
  }
10932
- case "cursor": {
10933
- const scripts = await installCursorHookScripts();
10934
- return {
10935
- editor: "cursor",
10936
- installed: [scripts.preToolUse, scripts.beforeSubmit],
10937
- hooksDir: getCursorHooksDir()
10938
- };
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");
10939
10010
  }
10940
- case "windsurf": {
10941
- const scripts = await installWindsurfHookScripts({ scope, projectPath });
10942
- return {
10943
- editor: "windsurf",
10944
- installed: [scripts.preMcpToolUse, scripts.preUserPrompt],
10945
- hooksDir: getWindsurfHooksDir(scope, projectPath)
10946
- };
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);
10947
10019
  }
10948
- default:
10949
- throw new Error(`Unsupported editor: ${editor}`);
10020
+ result.settings.push(settingsPath);
10950
10021
  }
10022
+ return result;
10951
10023
  }
10952
- async function installAllEditorHooks(options) {
10953
- const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor", "windsurf"];
10954
- const results = [];
10955
- for (const editor of editors) {
10956
- try {
10957
- const result = await installEditorHooks({
10958
- editor,
10959
- scope: options.scope,
10960
- projectPath: options.projectPath,
10961
- includePreCompact: options.includePreCompact
10962
- });
10963
- results.push(result);
10964
- } catch (error) {
10965
- console.error(`Failed to install hooks for ${editor}:`, error);
10966
- }
10024
+ function getIndexStatusPath() {
10025
+ return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
10026
+ }
10027
+ async function readIndexStatus() {
10028
+ const statusPath = getIndexStatusPath();
10029
+ try {
10030
+ const content = await fs4.readFile(statusPath, "utf-8");
10031
+ return JSON.parse(content);
10032
+ } catch {
10033
+ return { version: 1, projects: {} };
10967
10034
  }
10968
- return results;
10035
+ }
10036
+ async function writeIndexStatus(status) {
10037
+ const statusPath = getIndexStatusPath();
10038
+ const dir = path5.dirname(statusPath);
10039
+ await fs4.mkdir(dir, { recursive: true });
10040
+ await fs4.writeFile(statusPath, JSON.stringify(status, null, 2));
10041
+ }
10042
+ async function markProjectIndexed(projectPath, options) {
10043
+ const status = await readIndexStatus();
10044
+ const resolvedPath = path5.resolve(projectPath);
10045
+ status.projects[resolvedPath] = {
10046
+ indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
10047
+ project_id: options?.project_id,
10048
+ project_name: options?.project_name
10049
+ };
10050
+ await writeIndexStatus(status);
10969
10051
  }
10970
10052
 
10971
10053
  // src/token-savings.ts
@@ -11042,75 +10124,9 @@ BEFORE using EnterPlanMode/Task(Plan) \u2192 call mcp__contextstream__session(ac
11042
10124
  Local tools ONLY if ContextStream returns 0 results after retry.
11043
10125
  `.trim();
11044
10126
  var LESSONS_REMINDER_PREFIX = `
11045
- \u{1F6A8} [LESSONS_WARNING] Past Mistakes Found - READ BEFORE PROCEEDING!
11046
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
11047
- \u26A0\uFE0F IMPORTANT: You MUST review these lessons and tell the user about relevant ones.
11048
- These are mistakes from past sessions that you should NOT repeat.
10127
+ \u26A0\uFE0F [LESSONS - REVIEW BEFORE CHANGES]
10128
+ Past mistakes found that may be relevant. STOP and review before proceeding:
11049
10129
  `.trim();
11050
- var RISKY_ACTION_KEYWORDS = [
11051
- // Code changes
11052
- "refactor",
11053
- "rewrite",
11054
- "restructure",
11055
- "reorganize",
11056
- "migrate",
11057
- "delete",
11058
- "remove",
11059
- "drop",
11060
- "deprecate",
11061
- // Database
11062
- "database",
11063
- "migration",
11064
- "schema",
11065
- "sql",
11066
- // Deployment
11067
- "deploy",
11068
- "release",
11069
- "production",
11070
- "prod",
11071
- // API changes
11072
- "api",
11073
- "endpoint",
11074
- "breaking change",
11075
- // Architecture
11076
- "architecture",
11077
- "design",
11078
- "pattern",
11079
- // Testing
11080
- "test",
11081
- "testing",
11082
- // Security
11083
- "auth",
11084
- "security",
11085
- "permission",
11086
- "credential",
11087
- "access",
11088
- "token",
11089
- "secret",
11090
- // Version control
11091
- "git",
11092
- "commit",
11093
- "merge",
11094
- "rebase",
11095
- "push",
11096
- "force",
11097
- // Infrastructure
11098
- "config",
11099
- "environment",
11100
- "env",
11101
- "docker",
11102
- "kubernetes",
11103
- "k8s",
11104
- // Performance
11105
- "performance",
11106
- "optimize",
11107
- "cache",
11108
- "memory"
11109
- ];
11110
- function detectRiskyActions(userMessage) {
11111
- const messageLower = userMessage.toLowerCase();
11112
- return RISKY_ACTION_KEYWORDS.filter((keyword) => messageLower.includes(keyword));
11113
- }
11114
10130
  function generateLessonsReminder(result) {
11115
10131
  const lessons = result.lessons;
11116
10132
  if (!lessons || lessons.length === 0) {
@@ -11125,8 +10141,7 @@ function generateLessonsReminder(result) {
11125
10141
  return `
11126
10142
 
11127
10143
  ${LESSONS_REMINDER_PREFIX}
11128
- ${lessonLines.join("\n")}
11129
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
10144
+ ${lessonLines.join("\n")}`;
11130
10145
  }
11131
10146
  function generateRulesUpdateWarning(rulesNotice) {
11132
10147
  if (!rulesNotice || rulesNotice.status !== "behind" && rulesNotice.status !== "missing") {
@@ -11149,16 +10164,12 @@ function generateVersionUpdateWarning(versionNotice) {
11149
10164
  return "";
11150
10165
  }
11151
10166
  return `
11152
- \u{1F6A8} [VERSION_NOTICE] MCP Server Update Available!
11153
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
11154
- Version: ${versionNotice.current} \u2192 ${versionNotice.latest}
11155
-
11156
- \u26A0\uFE0F IMPORTANT: You MUST tell the user about this update IMMEDIATELY.
11157
- Do not proceed with other work until you have informed the user.
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.
11158
10170
 
11159
- Update command: \`${versionNotice.upgrade_command || "npm update -g @contextstream/mcp-server"}\`
11160
- After updating, restart the AI tool to use the new version.
11161
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
10171
+ **UPDATE NOW:** Run \`${versionNotice.upgrade_command || "npm update @contextstream/mcp-server"}\`
10172
+ Then restart Claude Code to use the new version.
11162
10173
  `.trim();
11163
10174
  }
11164
10175
  var DEFAULT_PARAM_DESCRIPTIONS = {
@@ -12019,7 +11030,6 @@ var ALL_INTEGRATION_TOOLS = /* @__PURE__ */ new Set([
12019
11030
  ...CROSS_INTEGRATION_TOOLS
12020
11031
  ]);
12021
11032
  var AUTO_HIDE_INTEGRATIONS = process.env.CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS !== "false";
12022
- var RESTORE_CONTEXT_DEFAULT = process.env.CONTEXTSTREAM_RESTORE_CONTEXT !== "false";
12023
11033
  var TOKEN_SENSITIVE_CLIENTS = /* @__PURE__ */ new Set([
12024
11034
  "claude",
12025
11035
  "claude-code",
@@ -14814,7 +13824,7 @@ This does semantic search on the first message. You only need context_smart on s
14814
13824
  "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."
14815
13825
  ),
14816
13826
  is_post_compact: external_exports.boolean().optional().describe(
14817
- "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."
13827
+ "Set to true when resuming after conversation compaction. This prioritizes session_snapshot restoration and recent decisions."
14818
13828
  )
14819
13829
  })
14820
13830
  },
@@ -14835,22 +13845,19 @@ This does semantic search on the first message. You only need context_smart on s
14835
13845
  }
14836
13846
  const result = await client.initSession(input, ideRoots);
14837
13847
  result.tools_hint = getCoreToolsHint();
14838
- const shouldRestoreContext = input.is_post_compact ?? RESTORE_CONTEXT_DEFAULT;
14839
- if (shouldRestoreContext) {
14840
- result.is_post_compact = true;
13848
+ if (input.is_post_compact) {
14841
13849
  const workspaceIdForRestore = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
14842
13850
  const projectIdForRestore = typeof result.project_id === "string" ? result.project_id : void 0;
14843
13851
  if (workspaceIdForRestore) {
14844
13852
  try {
14845
- const listResult = await client.listMemoryEvents({
13853
+ const snapshotSearch = await client.searchEvents({
14846
13854
  workspace_id: workspaceIdForRestore,
14847
13855
  project_id: projectIdForRestore,
14848
- limit: 50
13856
+ query: "session_snapshot",
13857
+ event_types: ["session_snapshot"],
13858
+ limit: 1
14849
13859
  });
14850
- const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
14851
- const snapshots = allEvents.filter(
14852
- (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
14853
- );
13860
+ const snapshots = snapshotSearch?.data?.results || snapshotSearch?.results || snapshotSearch?.data || [];
14854
13861
  if (snapshots && snapshots.length > 0) {
14855
13862
  const latestSnapshot = snapshots[0];
14856
13863
  let snapshotData;
@@ -14859,52 +13866,13 @@ This does semantic search on the first message. You only need context_smart on s
14859
13866
  } catch {
14860
13867
  snapshotData = { conversation_summary: latestSnapshot.content };
14861
13868
  }
14862
- const prevSessionId = snapshotData.session_id || latestSnapshot.session_id;
14863
- const sessionLinking = {};
14864
- if (prevSessionId) {
14865
- sessionLinking.previous_session_id = prevSessionId;
14866
- const workingOn = [];
14867
- const activeFiles = snapshotData.active_files;
14868
- const lastTools = snapshotData.last_tools;
14869
- if (activeFiles && activeFiles.length > 0) {
14870
- workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
14871
- }
14872
- if (lastTools && lastTools.length > 0) {
14873
- const toolCounts = lastTools.reduce((acc, tool) => {
14874
- acc[tool] = (acc[tool] || 0) + 1;
14875
- return acc;
14876
- }, {});
14877
- const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
14878
- workingOn.push(`Recent tools: ${topTools.join(", ")}`);
14879
- }
14880
- if (workingOn.length > 0) {
14881
- sessionLinking.previous_session_summary = workingOn.join("; ");
14882
- }
14883
- const relatedSessionIds = /* @__PURE__ */ new Set();
14884
- snapshots.forEach((s) => {
14885
- let sData;
14886
- try {
14887
- sData = JSON.parse(s.content || "{}");
14888
- } catch {
14889
- sData = {};
14890
- }
14891
- const sSessionId = sData.session_id || s.session_id;
14892
- if (sSessionId && sSessionId !== prevSessionId) {
14893
- relatedSessionIds.add(sSessionId);
14894
- }
14895
- });
14896
- if (relatedSessionIds.size > 0) {
14897
- sessionLinking.related_sessions = Array.from(relatedSessionIds);
14898
- }
14899
- }
14900
13869
  result.restored_context = {
14901
13870
  snapshot_id: latestSnapshot.id,
14902
13871
  captured_at: snapshotData.captured_at || latestSnapshot.created_at,
14903
- session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
14904
13872
  ...snapshotData
14905
13873
  };
14906
13874
  result.is_post_compact = true;
14907
- 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.";
13875
+ result.post_compact_hint = "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
14908
13876
  } else {
14909
13877
  result.is_post_compact = true;
14910
13878
  result.post_compact_hint = "Post-compaction session started, but no snapshots found. Use context_smart to retrieve relevant context.";
@@ -15577,16 +14545,14 @@ Use this in combination with session_init(is_post_compact=true) for seamless con
15577
14545
  structuredContent: toStructured(response2)
15578
14546
  };
15579
14547
  }
15580
- const listResult = await client.listMemoryEvents({
14548
+ const searchResult = await client.searchEvents({
15581
14549
  workspace_id: workspaceId,
15582
14550
  project_id: projectId,
15583
- limit: 50
15584
- // Fetch more to filter
14551
+ query: "session_snapshot",
14552
+ event_types: ["session_snapshot"],
14553
+ limit: input.max_snapshots || 1
15585
14554
  });
15586
- const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
15587
- const events = allEvents.filter(
15588
- (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
15589
- ).slice(0, input.max_snapshots || 1);
14555
+ const events = searchResult?.data?.results || searchResult?.results || searchResult?.data || [];
15590
14556
  if (!events || events.length === 0) {
15591
14557
  return {
15592
14558
  content: [
@@ -16054,77 +15020,34 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
16054
15020
  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.";
16055
15021
  let hooksResults;
16056
15022
  let hooksPrompt;
16057
- const editorHookMap = {
16058
- claude: "claude",
16059
- cline: "cline",
16060
- roo: "roo",
16061
- kilo: "kilo",
16062
- cursor: "cursor",
16063
- windsurf: "windsurf"
16064
- };
16065
- const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
16066
- const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
15023
+ const hasClaude = editors.includes("claude");
15024
+ const shouldInstallHooks = hasClaude && input.install_hooks !== false;
16067
15025
  if (shouldInstallHooks) {
16068
15026
  try {
16069
15027
  if (input.dry_run) {
16070
- hooksResults = [];
16071
- for (const editor of hookSupportedEditors) {
16072
- if (editor === "claude") {
16073
- hooksResults.push(
16074
- { editor, file: "~/.claude/hooks/contextstream-redirect.py", status: "dry run - would create" },
16075
- { editor, file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
16076
- { editor, file: "~/.claude/settings.json", status: "dry run - would update" }
16077
- );
16078
- if (input.include_pre_compact) {
16079
- hooksResults.push({ editor, file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
16080
- }
16081
- } else if (editor === "cline") {
16082
- hooksResults.push(
16083
- { editor, file: "~/Documents/Cline/Rules/Hooks/PreToolUse", status: "dry run - would create" },
16084
- { editor, file: "~/Documents/Cline/Rules/Hooks/UserPromptSubmit", status: "dry run - would create" }
16085
- );
16086
- } else if (editor === "roo") {
16087
- hooksResults.push(
16088
- { editor, file: "~/.roo/hooks/PreToolUse", status: "dry run - would create" },
16089
- { editor, file: "~/.roo/hooks/UserPromptSubmit", status: "dry run - would create" }
16090
- );
16091
- } else if (editor === "kilo") {
16092
- hooksResults.push(
16093
- { editor, file: "~/.kilocode/hooks/PreToolUse", status: "dry run - would create" },
16094
- { editor, file: "~/.kilocode/hooks/UserPromptSubmit", status: "dry run - would create" }
16095
- );
16096
- } else if (editor === "cursor") {
16097
- hooksResults.push(
16098
- { editor, file: "~/.cursor/hooks/contextstream-pretooluse.py", status: "dry run - would create" },
16099
- { editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
16100
- { editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
16101
- );
16102
- } else if (editor === "windsurf") {
16103
- hooksResults.push(
16104
- { editor, file: "~/.codeium/windsurf/hooks/contextstream-pretooluse.py", status: "dry run - would create" },
16105
- { editor, file: "~/.codeium/windsurf/hooks/contextstream-reminder.py", status: "dry run - would create" },
16106
- { editor, file: "~/.codeium/windsurf/hooks.json", status: "dry run - would update" }
16107
- );
16108
- }
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" });
16109
15035
  }
16110
15036
  } else {
16111
- hooksResults = [];
16112
- const allHookResults = await installAllEditorHooks({
16113
- scope: "global",
16114
- editors: hookSupportedEditors,
15037
+ const hookResult = await installClaudeCodeHooks({
15038
+ scope: "user",
16115
15039
  includePreCompact: input.include_pre_compact
16116
15040
  });
16117
- for (const result of allHookResults) {
16118
- for (const file of result.installed) {
16119
- hooksResults.push({ editor: result.editor, file, status: "created" });
16120
- }
16121
- }
15041
+ hooksResults = [
15042
+ ...hookResult.scripts.map((f) => ({ file: f, status: "created" })),
15043
+ ...hookResult.settings.map((f) => ({ file: f, status: "updated" }))
15044
+ ];
16122
15045
  }
16123
15046
  } catch (err) {
16124
15047
  hooksResults = [{ file: "hooks", status: `error: ${err.message}` }];
16125
15048
  }
16126
- } else if (hookSupportedEditors.length > 0 && input.install_hooks === false) {
16127
- hooksPrompt = "Hooks skipped. AI may use default tools instead of ContextStream search.";
15049
+ } else if (hasClaude && input.install_hooks === false) {
15050
+ hooksPrompt = "Hooks skipped. Claude may use default tools instead of ContextStream search.";
16128
15051
  }
16129
15052
  const summary = {
16130
15053
  folder: folderPath,
@@ -16503,52 +15426,6 @@ This saves ~80% tokens compared to including full chat history.`,
16503
15426
  }
16504
15427
  sessionManager.addTokens(input.user_message);
16505
15428
  }
16506
- let postCompactContext = "";
16507
- let postCompactRestored = false;
16508
- if (sessionManager && sessionManager.shouldRestorePostCompact() && workspaceId) {
16509
- try {
16510
- const listResult = await client.listMemoryEvents({
16511
- workspace_id: workspaceId,
16512
- project_id: projectId,
16513
- limit: 20
16514
- });
16515
- const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
16516
- const snapshotEvent = allEvents.find(
16517
- (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.tags?.includes("session_snapshot")
16518
- );
16519
- if (snapshotEvent && snapshotEvent.content) {
16520
- let snapshotData;
16521
- try {
16522
- snapshotData = JSON.parse(snapshotEvent.content);
16523
- } catch {
16524
- snapshotData = { conversation_summary: snapshotEvent.content };
16525
- }
16526
- const summary = snapshotData.conversation_summary || snapshotData.summary || "";
16527
- const decisions = snapshotData.key_decisions || [];
16528
- const unfinished = snapshotData.unfinished_work || snapshotData.pending_tasks || [];
16529
- const files = snapshotData.active_files || [];
16530
- const parts = [];
16531
- parts.push("\u{1F4CB} [POST-COMPACTION CONTEXT RESTORED]");
16532
- if (summary) parts.push(`Summary: ${summary}`);
16533
- if (Array.isArray(decisions) && decisions.length > 0) {
16534
- parts.push(`Decisions: ${decisions.slice(0, 5).join("; ")}`);
16535
- }
16536
- if (Array.isArray(unfinished) && unfinished.length > 0) {
16537
- parts.push(`Unfinished: ${unfinished.slice(0, 3).join("; ")}`);
16538
- }
16539
- if (Array.isArray(files) && files.length > 0) {
16540
- parts.push(`Active files: ${files.slice(0, 5).join(", ")}`);
16541
- }
16542
- parts.push("---");
16543
- postCompactContext = parts.join("\n") + "\n\n";
16544
- postCompactRestored = true;
16545
- sessionManager.markPostCompactRestoreCompleted();
16546
- console.error("[ContextStream] Post-compaction context restored automatically");
16547
- }
16548
- } catch (err) {
16549
- console.error("[ContextStream] Failed to restore post-compact context:", err);
16550
- }
16551
- }
16552
15429
  const result = await client.getSmartContext({
16553
15430
  user_message: input.user_message,
16554
15431
  workspace_id: workspaceId,
@@ -16589,44 +15466,8 @@ This saves ~80% tokens compared to including full chat history.`,
16589
15466
  project_id: projectId,
16590
15467
  max_tokens: input.max_tokens
16591
15468
  });
16592
- let lessonsWarningLine = "";
16593
- const riskyKeywords = detectRiskyActions(input.user_message);
16594
- if (riskyKeywords.length > 0 && workspaceId) {
16595
- try {
16596
- const lessons = await client.getHighPriorityLessons({
16597
- workspace_id: workspaceId,
16598
- project_id: projectId,
16599
- context_hint: riskyKeywords.join(" "),
16600
- limit: 5
16601
- });
16602
- if (lessons.length > 0) {
16603
- const lessonLines = lessons.slice(0, 5).map((l, i) => {
16604
- const severity = l.severity === "critical" ? "\u{1F6A8}" : l.severity === "high" ? "\u26A0\uFE0F" : "\u{1F4DD}";
16605
- const title = l.title || "Untitled lesson";
16606
- const prevention = l.prevention || "";
16607
- return `${i + 1}. ${severity} ${title}${prevention ? `: ${prevention.slice(0, 100)}` : ""}`;
16608
- });
16609
- lessonsWarningLine = `
16610
-
16611
- \u{1F6A8} [LESSONS_WARNING] Relevant Lessons for "${riskyKeywords.slice(0, 3).join(", ")}"
16612
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
16613
- \u26A0\uFE0F IMPORTANT: You MUST tell the user about these lessons before proceeding.
16614
- These are past mistakes that may be relevant to the current task.
16615
-
16616
- ${lessonLines.join("\n")}
16617
-
16618
- Action: Review each lesson and explain to the user how you will avoid these mistakes.
16619
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
16620
- }
16621
- } catch {
16622
- }
16623
- }
16624
- if (!lessonsWarningLine) {
16625
- const hasLessonsInContext = result.context.includes("|L:") || result.context.includes("L:") || result.context.toLowerCase().includes("lesson");
16626
- if (hasLessonsInContext) {
16627
- lessonsWarningLine = "\n\n\u26A0\uFE0F [LESSONS_WARNING] Lessons found in context - review the L: items above before making changes.";
16628
- }
16629
- }
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." : "";
16630
15471
  const searchRulesLine = SEARCH_RULES_REMINDER_ENABLED ? `
16631
15472
 
16632
15473
  ${SEARCH_RULES_REMINDER}` : "";
@@ -16634,18 +15475,12 @@ ${SEARCH_RULES_REMINDER}` : "";
16634
15475
  if (result.context_pressure) {
16635
15476
  const cp = result.context_pressure;
16636
15477
  if (cp.level === "critical") {
16637
- if (sessionManager) {
16638
- sessionManager.markHighContextPressure();
16639
- }
16640
15478
  contextPressureWarning = `
16641
15479
 
16642
15480
  \u{1F6A8} [CONTEXT PRESSURE: CRITICAL] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
16643
15481
  Action: ${cp.suggested_action === "save_now" ? 'SAVE STATE NOW - Call session(action="capture") to preserve conversation state before compaction.' : cp.suggested_action}
16644
15482
  The conversation may compact soon. Save important decisions, insights, and progress immediately.`;
16645
15483
  } else if (cp.level === "high") {
16646
- if (sessionManager) {
16647
- sessionManager.markHighContextPressure();
16648
- }
16649
15484
  contextPressureWarning = `
16650
15485
 
16651
15486
  \u26A0\uFE0F [CONTEXT PRESSURE: HIGH] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
@@ -16663,16 +15498,14 @@ ${versionWarningLine}` : "",
16663
15498
  contextPressureWarning,
16664
15499
  searchRulesLine
16665
15500
  ].filter(Boolean).join("");
16666
- const finalContext = postCompactContext + result.context;
16667
- const enrichedResultWithRestore = postCompactRestored ? { ...enrichedResult, post_compact_restored: true } : enrichedResult;
16668
15501
  return {
16669
15502
  content: [
16670
15503
  {
16671
15504
  type: "text",
16672
- text: finalContext + footer + allWarnings
15505
+ text: result.context + footer + allWarnings
16673
15506
  }
16674
15507
  ],
16675
- structuredContent: toStructured(enrichedResultWithRestore)
15508
+ structuredContent: toStructured(enrichedResult)
16676
15509
  };
16677
15510
  }
16678
15511
  );
@@ -17741,7 +16574,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
17741
16574
  "session",
17742
16575
  {
17743
16576
  title: "Session",
17744
- 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).`,
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).`,
17745
16578
  inputSchema: external_exports.object({
17746
16579
  action: external_exports.enum([
17747
16580
  "capture",
@@ -17759,9 +16592,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
17759
16592
  "capture_plan",
17760
16593
  "get_plan",
17761
16594
  "update_plan",
17762
- "list_plans",
17763
- // Context restore
17764
- "restore_context"
16595
+ "list_plans"
17765
16596
  ]).describe("Action to perform"),
17766
16597
  workspace_id: external_exports.string().uuid().optional(),
17767
16598
  project_id: external_exports.string().uuid().optional(),
@@ -17783,8 +16614,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
17783
16614
  "lesson",
17784
16615
  "warning",
17785
16616
  "frustration",
17786
- "conversation",
17787
- "session_snapshot"
16617
+ "conversation"
17788
16618
  ]).optional().describe("Event type for capture"),
17789
16619
  importance: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
17790
16620
  tags: external_exports.array(external_exports.string()).optional(),
@@ -17834,10 +16664,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
17834
16664
  status: external_exports.enum(["draft", "active", "completed", "archived", "abandoned"]).optional().describe("Plan status"),
17835
16665
  due_at: external_exports.string().optional().describe("Due date for plan (ISO timestamp)"),
17836
16666
  source_tool: external_exports.string().optional().describe("Tool that generated this plan"),
17837
- include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan"),
17838
- // Restore context params
17839
- snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
17840
- max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
16667
+ include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan")
17841
16668
  })
17842
16669
  },
17843
16670
  async (input) => {
@@ -18143,143 +16970,6 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
18143
16970
  structuredContent: toStructured(result)
18144
16971
  };
18145
16972
  }
18146
- case "restore_context": {
18147
- if (!workspaceId) {
18148
- return errorResult(
18149
- "restore_context requires workspace_id. Call session_init first."
18150
- );
18151
- }
18152
- if (input.snapshot_id) {
18153
- const eventResult = await client.getEvent(input.snapshot_id);
18154
- const event = eventResult?.data || eventResult;
18155
- if (!event || !event.content) {
18156
- return errorResult(
18157
- `Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
18158
- );
18159
- }
18160
- let snapshotData;
18161
- try {
18162
- snapshotData = JSON.parse(event.content);
18163
- } catch {
18164
- snapshotData = { conversation_summary: event.content };
18165
- }
18166
- const sessionId = snapshotData.session_id || event.session_id;
18167
- const sessionLinking2 = {};
18168
- if (sessionId) {
18169
- sessionLinking2.previous_session_id = sessionId;
18170
- const workingOn = [];
18171
- const activeFiles = snapshotData.active_files;
18172
- const lastTools = snapshotData.last_tools;
18173
- if (activeFiles && activeFiles.length > 0) {
18174
- workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
18175
- }
18176
- if (lastTools && lastTools.length > 0) {
18177
- const toolCounts = lastTools.reduce((acc, tool) => {
18178
- acc[tool] = (acc[tool] || 0) + 1;
18179
- return acc;
18180
- }, {});
18181
- const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
18182
- workingOn.push(`Recent tools: ${topTools.join(", ")}`);
18183
- }
18184
- if (workingOn.length > 0) {
18185
- sessionLinking2.previous_session_summary = workingOn.join("; ");
18186
- }
18187
- }
18188
- const response2 = {
18189
- restored: true,
18190
- snapshot_id: event.id,
18191
- captured_at: snapshotData.captured_at || event.created_at,
18192
- session_linking: Object.keys(sessionLinking2).length > 0 ? sessionLinking2 : void 0,
18193
- ...snapshotData,
18194
- 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."
18195
- };
18196
- return {
18197
- content: [{ type: "text", text: formatContent(response2) }],
18198
- structuredContent: toStructured(response2)
18199
- };
18200
- }
18201
- const listResult = await client.listMemoryEvents({
18202
- workspace_id: workspaceId,
18203
- project_id: projectId,
18204
- limit: 50
18205
- // Fetch more to filter
18206
- });
18207
- const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
18208
- const snapshotEvents = allEvents.filter(
18209
- (e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
18210
- ).slice(0, input.max_snapshots || 1);
18211
- if (!snapshotEvents || snapshotEvents.length === 0) {
18212
- return {
18213
- content: [
18214
- {
18215
- type: "text",
18216
- text: formatContent({
18217
- restored: false,
18218
- message: "No session snapshots found. Use session_capture_smart to save state before compaction.",
18219
- hint: "Start fresh or use session_init to get recent context."
18220
- })
18221
- }
18222
- ]
18223
- };
18224
- }
18225
- const snapshots = snapshotEvents.map((event) => {
18226
- let snapshotData;
18227
- try {
18228
- snapshotData = JSON.parse(event.content || "{}");
18229
- } catch {
18230
- snapshotData = { conversation_summary: event.content };
18231
- }
18232
- return {
18233
- snapshot_id: event.id,
18234
- captured_at: snapshotData.captured_at || event.created_at,
18235
- session_id: snapshotData.session_id || event.session_id,
18236
- ...snapshotData
18237
- };
18238
- });
18239
- const latestSnapshot = snapshots[0];
18240
- const sessionLinking = {};
18241
- if (latestSnapshot?.session_id) {
18242
- sessionLinking.previous_session_id = latestSnapshot.session_id;
18243
- const workingOn = [];
18244
- const activeFiles = latestSnapshot.active_files;
18245
- const lastTools = latestSnapshot.last_tools;
18246
- if (activeFiles && activeFiles.length > 0) {
18247
- workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
18248
- }
18249
- if (lastTools && lastTools.length > 0) {
18250
- const toolCounts = lastTools.reduce((acc, tool) => {
18251
- acc[tool] = (acc[tool] || 0) + 1;
18252
- return acc;
18253
- }, {});
18254
- const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
18255
- workingOn.push(`Recent tools: ${topTools.join(", ")}`);
18256
- }
18257
- if (workingOn.length > 0) {
18258
- sessionLinking.previous_session_summary = workingOn.join("; ");
18259
- }
18260
- const relatedSessionIds = /* @__PURE__ */ new Set();
18261
- snapshots.forEach((s) => {
18262
- if (s.session_id && s.session_id !== latestSnapshot.session_id) {
18263
- relatedSessionIds.add(s.session_id);
18264
- }
18265
- });
18266
- if (relatedSessionIds.size > 0) {
18267
- sessionLinking.related_sessions = Array.from(relatedSessionIds);
18268
- }
18269
- }
18270
- const response = {
18271
- restored: true,
18272
- snapshots_found: snapshots.length,
18273
- latest: snapshots[0],
18274
- all_snapshots: snapshots.length > 1 ? snapshots : void 0,
18275
- session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
18276
- 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."
18277
- };
18278
- return {
18279
- content: [{ type: "text", text: formatContent(response) }],
18280
- structuredContent: toStructured(response)
18281
- };
18282
- }
18283
16973
  default:
18284
16974
  return errorResult(`Unknown action: ${input.action}`);
18285
16975
  }
@@ -20844,7 +19534,8 @@ function registerPrompts(server) {
20844
19534
  }
20845
19535
 
20846
19536
  // src/session-manager.ts
20847
- var SessionManager = class _SessionManager {
19537
+ var SessionManager = class {
19538
+ // Conservative default for 100k context window
20848
19539
  constructor(server, client) {
20849
19540
  this.server = server;
20850
19541
  this.client = client;
@@ -20856,30 +19547,8 @@ var SessionManager = class _SessionManager {
20856
19547
  this.contextSmartCalled = false;
20857
19548
  this.warningShown = false;
20858
19549
  // Token tracking for context pressure calculation
20859
- // Note: MCP servers cannot see actual token usage (AI responses, thinking, system prompts).
20860
- // We use a heuristic: tracked tokens + (turns * estimated tokens per turn)
20861
19550
  this.sessionTokens = 0;
20862
19551
  this.contextThreshold = 7e4;
20863
- // Conservative default for 100k context window
20864
- this.conversationTurns = 0;
20865
- // Continuous checkpointing
20866
- this.toolCallCount = 0;
20867
- this.checkpointInterval = 20;
20868
- // Save checkpoint every N tool calls
20869
- this.lastCheckpointAt = 0;
20870
- this.activeFiles = /* @__PURE__ */ new Set();
20871
- this.recentToolCalls = [];
20872
- this.checkpointEnabled = process.env.CONTEXTSTREAM_CHECKPOINT_ENABLED?.toLowerCase() === "true";
20873
- // Post-compaction restoration tracking
20874
- // Tracks when context pressure was high/critical so we can detect post-compaction state
20875
- this.lastHighPressureAt = null;
20876
- this.lastHighPressureTokens = 0;
20877
- this.postCompactRestoreCompleted = false;
20878
- }
20879
- static {
20880
- // Each conversation turn typically includes: user message (~500), AI response (~1500),
20881
- // system prompt overhead (~500), and reasoning (~1500). Conservative estimate: 3000/turn
20882
- this.TOKENS_PER_TURN_ESTIMATE = 3e3;
20883
19552
  }
20884
19553
  /**
20885
19554
  * Check if session has been auto-initialized
@@ -20922,40 +19591,17 @@ var SessionManager = class _SessionManager {
20922
19591
  this.folderPath = path9;
20923
19592
  }
20924
19593
  /**
20925
- * Mark that context_smart has been called in this session.
20926
- * Also increments the conversation turn counter for token estimation.
19594
+ * Mark that context_smart has been called in this session
20927
19595
  */
20928
19596
  markContextSmartCalled() {
20929
19597
  this.contextSmartCalled = true;
20930
- this.conversationTurns++;
20931
19598
  }
20932
19599
  /**
20933
19600
  * Get current session token count for context pressure calculation.
20934
- *
20935
- * This returns an ESTIMATED count based on:
20936
- * 1. Tokens tracked through ContextStream tools (actual)
20937
- * 2. Estimated tokens per conversation turn (heuristic)
20938
- *
20939
- * Note: MCP servers cannot see actual AI token usage (responses, thinking,
20940
- * system prompts). This estimate helps provide a more realistic context
20941
- * pressure signal.
20942
19601
  */
20943
19602
  getSessionTokens() {
20944
- const turnEstimate = this.conversationTurns * _SessionManager.TOKENS_PER_TURN_ESTIMATE;
20945
- return this.sessionTokens + turnEstimate;
20946
- }
20947
- /**
20948
- * Get the raw tracked tokens (without turn-based estimation).
20949
- */
20950
- getRawTrackedTokens() {
20951
19603
  return this.sessionTokens;
20952
19604
  }
20953
- /**
20954
- * Get the current conversation turn count.
20955
- */
20956
- getConversationTurns() {
20957
- return this.conversationTurns;
20958
- }
20959
19605
  /**
20960
19606
  * Get the context threshold (max tokens before compaction warning).
20961
19607
  */
@@ -20994,52 +19640,6 @@ var SessionManager = class _SessionManager {
20994
19640
  */
20995
19641
  resetTokenCount() {
20996
19642
  this.sessionTokens = 0;
20997
- this.conversationTurns = 0;
20998
- }
20999
- /**
21000
- * Record that context pressure is high/critical.
21001
- * Called when context_smart returns high or critical pressure level.
21002
- */
21003
- markHighContextPressure() {
21004
- this.lastHighPressureAt = Date.now();
21005
- this.lastHighPressureTokens = this.getSessionTokens();
21006
- }
21007
- /**
21008
- * Check if we should attempt post-compaction restoration.
21009
- *
21010
- * Detection heuristic:
21011
- * 1. We recorded high/critical context pressure recently (within 10 minutes)
21012
- * 2. Current token count is very low (< 5000) compared to when pressure was high
21013
- * 3. We haven't already restored in this session
21014
- *
21015
- * This indicates compaction likely happened and we should restore context.
21016
- */
21017
- shouldRestorePostCompact() {
21018
- if (this.postCompactRestoreCompleted) {
21019
- return false;
21020
- }
21021
- if (!this.lastHighPressureAt) {
21022
- return false;
21023
- }
21024
- const elapsed = Date.now() - this.lastHighPressureAt;
21025
- if (elapsed > 10 * 60 * 1e3) {
21026
- return false;
21027
- }
21028
- const currentTokens = this.getSessionTokens();
21029
- const tokenDrop = this.lastHighPressureTokens - currentTokens;
21030
- if (currentTokens > 1e4 || tokenDrop < this.lastHighPressureTokens * 0.5) {
21031
- return false;
21032
- }
21033
- return true;
21034
- }
21035
- /**
21036
- * Mark post-compaction restoration as completed.
21037
- * Prevents multiple restoration attempts in the same session.
21038
- */
21039
- markPostCompactRestoreCompleted() {
21040
- this.postCompactRestoreCompleted = true;
21041
- this.lastHighPressureAt = null;
21042
- this.lastHighPressureTokens = 0;
21043
19643
  }
21044
19644
  /**
21045
19645
  * Check if context_smart has been called and warn if not.
@@ -21305,112 +19905,6 @@ var SessionManager = class _SessionManager {
21305
19905
  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");
21306
19906
  return parts.join("\n");
21307
19907
  }
21308
- // =========================================================================
21309
- // Continuous Checkpointing
21310
- // =========================================================================
21311
- /**
21312
- * Track a tool call for checkpointing purposes.
21313
- * Call this after each tool execution to track files and trigger periodic checkpoints.
21314
- */
21315
- trackToolCall(toolName, input) {
21316
- this.toolCallCount++;
21317
- this.recentToolCalls.push({ name: toolName, timestamp: Date.now() });
21318
- if (this.recentToolCalls.length > 50) {
21319
- this.recentToolCalls = this.recentToolCalls.slice(-50);
21320
- }
21321
- if (input) {
21322
- const filePath = input.file_path || input.notebook_path || input.path;
21323
- if (filePath && typeof filePath === "string") {
21324
- this.activeFiles.add(filePath);
21325
- if (this.activeFiles.size > 30) {
21326
- const arr = Array.from(this.activeFiles);
21327
- this.activeFiles = new Set(arr.slice(-30));
21328
- }
21329
- }
21330
- }
21331
- this.maybeCheckpoint();
21332
- }
21333
- /**
21334
- * Save a checkpoint if the interval has been reached.
21335
- */
21336
- async maybeCheckpoint() {
21337
- if (!this.checkpointEnabled || !this.initialized || !this.context) {
21338
- return;
21339
- }
21340
- const callsSinceLastCheckpoint = this.toolCallCount - this.lastCheckpointAt;
21341
- if (callsSinceLastCheckpoint < this.checkpointInterval) {
21342
- return;
21343
- }
21344
- this.lastCheckpointAt = this.toolCallCount;
21345
- await this.saveCheckpoint("periodic");
21346
- }
21347
- /**
21348
- * Get the list of active files being worked on.
21349
- */
21350
- getActiveFiles() {
21351
- return Array.from(this.activeFiles);
21352
- }
21353
- /**
21354
- * Get recent tool call names.
21355
- */
21356
- getRecentToolNames() {
21357
- return this.recentToolCalls.map((t) => t.name);
21358
- }
21359
- /**
21360
- * Get the current tool call count.
21361
- */
21362
- getToolCallCount() {
21363
- return this.toolCallCount;
21364
- }
21365
- /**
21366
- * Save a checkpoint snapshot to ContextStream.
21367
- */
21368
- async saveCheckpoint(trigger) {
21369
- if (!this.initialized || !this.context) {
21370
- return false;
21371
- }
21372
- const workspaceId = this.context.workspace_id;
21373
- if (!workspaceId) {
21374
- return false;
21375
- }
21376
- const checkpointData = {
21377
- trigger,
21378
- checkpoint_number: Math.floor(this.toolCallCount / this.checkpointInterval),
21379
- tool_call_count: this.toolCallCount,
21380
- session_tokens: this.sessionTokens,
21381
- active_files: this.getActiveFiles(),
21382
- recent_tools: this.getRecentToolNames().slice(-10),
21383
- captured_at: (/* @__PURE__ */ new Date()).toISOString(),
21384
- auto_captured: true
21385
- };
21386
- try {
21387
- await this.client.captureContext({
21388
- workspace_id: workspaceId,
21389
- project_id: this.context.project_id,
21390
- event_type: "session_snapshot",
21391
- title: `Checkpoint #${checkpointData.checkpoint_number} (${trigger})`,
21392
- content: JSON.stringify(checkpointData),
21393
- importance: trigger === "periodic" ? "low" : "medium",
21394
- tags: ["session_snapshot", "checkpoint", trigger]
21395
- });
21396
- return true;
21397
- } catch (err) {
21398
- console.error("[ContextStream] Failed to save checkpoint:", err);
21399
- return false;
21400
- }
21401
- }
21402
- /**
21403
- * Enable or disable continuous checkpointing.
21404
- */
21405
- setCheckpointEnabled(enabled) {
21406
- this.checkpointEnabled = enabled;
21407
- }
21408
- /**
21409
- * Set the checkpoint interval (tool calls between checkpoints).
21410
- */
21411
- setCheckpointInterval(interval) {
21412
- this.checkpointInterval = Math.max(5, interval);
21413
- }
21414
19908
  };
21415
19909
 
21416
19910
  // src/http-gateway.ts
@@ -22166,9 +20660,6 @@ function buildContextStreamMcpServer(params) {
22166
20660
  env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
22167
20661
  }
22168
20662
  env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
22169
- if (params.restoreContextEnabled === false) {
22170
- env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
22171
- }
22172
20663
  if (params.showTiming) {
22173
20664
  env.CONTEXTSTREAM_SHOW_TIMING = "true";
22174
20665
  }
@@ -22194,9 +20685,6 @@ function buildContextStreamVsCodeServer(params) {
22194
20685
  env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
22195
20686
  }
22196
20687
  env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
22197
- if (params.restoreContextEnabled === false) {
22198
- env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
22199
- }
22200
20688
  if (params.showTiming) {
22201
20689
  env.CONTEXTSTREAM_SHOW_TIMING = "true";
22202
20690
  }
@@ -22300,8 +20788,6 @@ async function upsertCodexTomlConfig(filePath, params) {
22300
20788
  ` : "";
22301
20789
  const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
22302
20790
  `;
22303
- const restoreContextLine = params.restoreContextEnabled === false ? `CONTEXTSTREAM_RESTORE_CONTEXT = "false"
22304
- ` : "";
22305
20791
  const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
22306
20792
  ` : "";
22307
20793
  const commandLine = IS_WINDOWS ? `command = "cmd"
@@ -22317,7 +20803,7 @@ args = ["-y", "@contextstream/mcp-server"]
22317
20803
  [mcp_servers.contextstream.env]
22318
20804
  CONTEXTSTREAM_API_URL = "${params.apiUrl}"
22319
20805
  CONTEXTSTREAM_API_KEY = "${params.apiKey}"
22320
- ` + toolsetLine + contextPackLine + restoreContextLine + showTimingLine;
20806
+ ` + toolsetLine + contextPackLine + showTimingLine;
22321
20807
  if (!exists) {
22322
20808
  await fs7.writeFile(filePath, block.trimStart(), "utf8");
22323
20809
  return "created";
@@ -22679,12 +21165,6 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
22679
21165
  console.log(" Useful for debugging performance; disabled by default.");
22680
21166
  const showTimingChoice = normalizeInput(await rl.question("Show response timing? [y/N]: "));
22681
21167
  const showTiming = showTimingChoice.toLowerCase() === "y" || showTimingChoice.toLowerCase() === "yes";
22682
- console.log("\nAutomatic Context Restoration:");
22683
- console.log(" Automatically restore context from recent snapshots on every session_init.");
22684
- console.log(" This enables seamless continuation across conversations and after compaction.");
22685
- console.log(" Enabled by default; disable if you prefer explicit control.");
22686
- const restoreContextChoice = normalizeInput(await rl.question("Enable automatic context restoration? [Y/n]: "));
22687
- const restoreContextEnabled = !(restoreContextChoice.toLowerCase() === "n" || restoreContextChoice.toLowerCase() === "no");
22688
21168
  const editors = [
22689
21169
  "codex",
22690
21170
  "claude",
@@ -22735,7 +21215,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
22735
21215
  console.log(" 1) Global");
22736
21216
  console.log(" 2) Project");
22737
21217
  console.log(" 3) Both");
22738
- const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 2): ")) || "2";
21218
+ const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 3): ")) || "3";
22739
21219
  const scope = scopeChoice === "1" ? "global" : scopeChoice === "2" ? "project" : "both";
22740
21220
  console.log("\nInstall MCP server config as:");
22741
21221
  if (hasCodex && !hasProjectMcpEditors) {
@@ -22759,22 +21239,20 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
22759
21239
  )
22760
21240
  ) || mcpChoiceDefault;
22761
21241
  const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
22762
- const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming, restoreContextEnabled });
21242
+ const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming });
22763
21243
  const mcpServerClaude = buildContextStreamMcpServer({
22764
21244
  apiUrl,
22765
21245
  apiKey,
22766
21246
  toolset,
22767
21247
  contextPackEnabled,
22768
- showTiming,
22769
- restoreContextEnabled
21248
+ showTiming
22770
21249
  });
22771
21250
  const vsCodeServer = buildContextStreamVsCodeServer({
22772
21251
  apiUrl,
22773
21252
  apiKey,
22774
21253
  toolset,
22775
21254
  contextPackEnabled,
22776
- showTiming,
22777
- restoreContextEnabled
21255
+ showTiming
22778
21256
  });
22779
21257
  const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
22780
21258
  if (needsGlobalMcpConfig) {
@@ -22794,8 +21272,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
22794
21272
  apiKey,
22795
21273
  toolset,
22796
21274
  contextPackEnabled,
22797
- showTiming,
22798
- restoreContextEnabled
21275
+ showTiming
22799
21276
  });
22800
21277
  writeActions.push({ kind: "mcp-config", target: filePath, status });
22801
21278
  console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
@@ -22877,69 +21354,53 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
22877
21354
  }
22878
21355
  }
22879
21356
  }
22880
- const HOOKS_SUPPORTED_EDITORS = {
22881
- claude: "claude",
22882
- cursor: "cursor",
22883
- windsurf: "windsurf",
22884
- cline: "cline",
22885
- roo: "roo",
22886
- kilo: "kilo",
22887
- codex: null,
22888
- // No hooks API
22889
- aider: null,
22890
- // No hooks API
22891
- antigravity: null
22892
- // No hooks API
22893
- };
22894
- const hookEligibleEditors = configuredEditors.filter(
22895
- (e) => HOOKS_SUPPORTED_EDITORS[e] !== null
22896
- );
22897
- if (hookEligibleEditors.length > 0) {
21357
+ if (configuredEditors.includes("claude")) {
22898
21358
  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");
22899
- console.log("\u2502 AI Editor Hooks (Recommended) \u2502");
21359
+ console.log("\u2502 Claude Code Hooks (Recommended) \u2502");
22900
21360
  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");
22901
21361
  console.log("");
22902
- console.log(" Problem: AI editors often use their default tools (Grep/Glob/Search)");
22903
- console.log(" instead of ContextStream smart search. Instructions decay over long chats.");
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.");
22904
21365
  console.log("");
22905
21366
  console.log(" Solution: Install hooks that:");
22906
- console.log(" \u2713 Use ContextStream (indexed, faster) with default tool use");
22907
- console.log(" \u2713 Use ContextStream plans (persistent) with default tool use");
22908
- console.log(" \u2713 Inject reminders to keep rules in context");
22909
- console.log("");
22910
- console.log(` Hooks available for: ${hookEligibleEditors.map((e) => EDITOR_LABELS[e]).join(", ")}`);
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");
22911
21371
  console.log("");
22912
21372
  console.log(" You can disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
22913
21373
  console.log("");
22914
21374
  const installHooks = normalizeInput(
22915
- await rl.question("Install editor hooks? [Y/n] (recommended): ")
21375
+ await rl.question("Install Claude Code hooks? [Y/n] (recommended): ")
22916
21376
  ).toLowerCase();
22917
21377
  if (installHooks !== "n" && installHooks !== "no") {
22918
- for (const editor of hookEligibleEditors) {
22919
- const hookEditor = HOOKS_SUPPORTED_EDITORS[editor];
22920
- if (!hookEditor) continue;
22921
- try {
22922
- if (dryRun) {
22923
- console.log(`- ${EDITOR_LABELS[editor]}: would install hooks`);
22924
- continue;
22925
- }
22926
- const result = await installEditorHooks({
22927
- editor: hookEditor,
22928
- scope: "global"
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}`);
22929
21394
  });
22930
- for (const script of result.installed) {
22931
- writeActions.push({ kind: "hooks", target: script, status: "created" });
22932
- console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
22933
- }
22934
- } catch (err) {
22935
- const message = err instanceof Error ? err.message : String(err);
22936
- console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
22937
21395
  }
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}`);
22938
21400
  }
22939
- console.log(" Hooks installed. Disable with CONTEXTSTREAM_HOOK_ENABLED=false");
22940
21401
  } else {
22941
21402
  console.log("- Skipped hooks installation.");
22942
- console.log(" Note: Without hooks, AI may still use default tools instead of ContextStream.");
21403
+ console.log(" Note: Without hooks, Claude may still use default tools instead of ContextStream.");
22943
21404
  }
22944
21405
  }
22945
21406
  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");