@contextstream/mcp-server 0.4.39 → 0.4.41
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.
- package/dist/index.js +207 -1746
- 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
|
-
##
|
|
8875
|
+
## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
|
|
8876
8876
|
|
|
8877
8877
|
<contextstream_rules>
|
|
8878
|
-
1. **
|
|
8879
|
-
2. **
|
|
8880
|
-
3. **
|
|
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
|
-
**
|
|
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
|
|
8943
|
-
-
|
|
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 -
|
|
8968
|
+
## TL;DR - REQUIRED EVERY MESSAGE
|
|
8970
8969
|
|
|
8971
|
-
|
|
|
8972
|
-
|
|
8973
|
-
|
|
|
8974
|
-
| **\
|
|
8975
|
-
|
|
|
8976
|
-
| **\
|
|
8977
|
-
|
|
|
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
|
-
|
|
8981
|
+
**NO EXCEPTIONS.** Do not skip even if you think you have enough context.
|
|
8980
8982
|
|
|
8981
|
-
**
|
|
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
|
|
9020
|
-
- **\`context_smart\`** - Semantic search for relevant context (
|
|
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
|
|
9068
|
-
|
|
9069
|
-
**[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
|
|
9046
|
+
### Rules Update Notices
|
|
9070
9047
|
|
|
9071
|
-
**[
|
|
9072
|
-
|
|
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
|
-
|
|
|
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
|
-
**
|
|
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
|
-
##
|
|
9269
|
+
## \u{1F6A8} MANDATORY RULE: DISPLAY AT START OF EVERY RESPONSE \u{1F6A8}
|
|
9310
9270
|
|
|
9311
9271
|
<contextstream_rules>
|
|
9312
|
-
1. **
|
|
9313
|
-
2. **
|
|
9314
|
-
3. **
|
|
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
|
-
**
|
|
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
|
-
###
|
|
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 \`
|
|
9396
|
-
-
|
|
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
|
-
|
|
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": "...
|
|
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
|
-
#
|
|
10080
|
-
|
|
10081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10101
|
-
|
|
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
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
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
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
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
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
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
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
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
|
-
|
|
10949
|
-
throw new Error(`Unsupported editor: ${editor}`);
|
|
10020
|
+
result.settings.push(settingsPath);
|
|
10950
10021
|
}
|
|
10022
|
+
return result;
|
|
10951
10023
|
}
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
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
|
-
|
|
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
|
-
\
|
|
11046
|
-
|
|
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} [
|
|
11153
|
-
\
|
|
11154
|
-
|
|
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
|
-
|
|
11160
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
|
13853
|
+
const snapshotSearch = await client.searchEvents({
|
|
14846
13854
|
workspace_id: workspaceIdForRestore,
|
|
14847
13855
|
project_id: projectIdForRestore,
|
|
14848
|
-
|
|
13856
|
+
query: "session_snapshot",
|
|
13857
|
+
event_types: ["session_snapshot"],
|
|
13858
|
+
limit: 1
|
|
14849
13859
|
});
|
|
14850
|
-
const
|
|
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 =
|
|
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
|
|
14548
|
+
const searchResult = await client.searchEvents({
|
|
15581
14549
|
workspace_id: workspaceId,
|
|
15582
14550
|
project_id: projectId,
|
|
15583
|
-
|
|
15584
|
-
|
|
14551
|
+
query: "session_snapshot",
|
|
14552
|
+
event_types: ["session_snapshot"],
|
|
14553
|
+
limit: input.max_snapshots || 1
|
|
15585
14554
|
});
|
|
15586
|
-
const
|
|
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
|
|
16058
|
-
|
|
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
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
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
|
-
|
|
16112
|
-
|
|
16113
|
-
scope: "global",
|
|
16114
|
-
editors: hookSupportedEditors,
|
|
15037
|
+
const hookResult = await installClaudeCodeHooks({
|
|
15038
|
+
scope: "user",
|
|
16115
15039
|
includePreCompact: input.include_pre_compact
|
|
16116
15040
|
});
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
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 (
|
|
16127
|
-
hooksPrompt = "Hooks skipped.
|
|
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
|
-
|
|
16593
|
-
const
|
|
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:
|
|
15505
|
+
text: result.context + footer + allWarnings
|
|
16673
15506
|
}
|
|
16674
15507
|
],
|
|
16675
|
-
structuredContent: toStructured(
|
|
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)
|
|
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
|
|
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 +
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
22903
|
-
console.log(" instead of ContextStream
|
|
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
|
|
22907
|
-
console.log(" \u2713
|
|
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
|
|
21375
|
+
await rl.question("Install Claude Code hooks? [Y/n] (recommended): ")
|
|
22916
21376
|
).toLowerCase();
|
|
22917
21377
|
if (installHooks !== "n" && installHooks !== "no") {
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
const result = await
|
|
22927
|
-
|
|
22928
|
-
|
|
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,
|
|
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");
|