@contextstream/mcp-server 0.4.41 → 0.4.42
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 +1672 -376
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -4542,7 +4542,7 @@ import { createRequire } from "module";
|
|
|
4542
4542
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4543
4543
|
import { homedir } from "os";
|
|
4544
4544
|
import { join } from "path";
|
|
4545
|
-
var UPGRADE_COMMAND = "npm
|
|
4545
|
+
var UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
|
|
4546
4546
|
var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
|
|
4547
4547
|
function getVersion() {
|
|
4548
4548
|
try {
|
|
@@ -8872,16 +8872,15 @@ function applyMcpToolPrefix(markdown, toolPrefix) {
|
|
|
8872
8872
|
return markdown.replace(toolRegex, `${toolPrefix}$1`);
|
|
8873
8873
|
}
|
|
8874
8874
|
var CONTEXTSTREAM_RULES_FULL = `
|
|
8875
|
-
##
|
|
8875
|
+
## ContextStream Rules
|
|
8876
8876
|
|
|
8877
8877
|
<contextstream_rules>
|
|
8878
|
-
1. **
|
|
8879
|
-
2. **
|
|
8880
|
-
3. **
|
|
8878
|
+
1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
|
|
8879
|
+
2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
|
|
8880
|
+
3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
|
|
8881
8881
|
</contextstream_rules>
|
|
8882
8882
|
|
|
8883
|
-
**
|
|
8884
|
-
This is a self-referential rule that ensures these instructions stay in recent context.
|
|
8883
|
+
**Display this block at the start of responses to keep rules in context.**
|
|
8885
8884
|
|
|
8886
8885
|
---
|
|
8887
8886
|
|
|
@@ -8940,8 +8939,10 @@ This applies to **EVERY search** throughout the **ENTIRE conversation**, not jus
|
|
|
8940
8939
|
- **Apply the prevention steps** from each lesson to avoid repeating mistakes
|
|
8941
8940
|
|
|
8942
8941
|
### On \`context_smart\`:
|
|
8943
|
-
- Check for
|
|
8944
|
-
-
|
|
8942
|
+
- Check for \`[LESSONS_WARNING]\` tag in the response
|
|
8943
|
+
- If present, you **MUST** tell the user about the lessons before proceeding
|
|
8944
|
+
- Lessons are proactively fetched when risky actions are detected (refactor, migrate, deploy, etc.)
|
|
8945
|
+
- **Do not skip or bury this warning** - lessons represent real past mistakes
|
|
8945
8946
|
|
|
8946
8947
|
### Before ANY Non-Trivial Work:
|
|
8947
8948
|
**ALWAYS call \`session(action="get_lessons", query="<topic>")\`** where \`<topic>\` matches what you're about to do:
|
|
@@ -8965,22 +8966,42 @@ You have access to ContextStream MCP tools for persistent memory and context.
|
|
|
8965
8966
|
v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
|
|
8966
8967
|
Rules Version: ${RULES_VERSION}
|
|
8967
8968
|
|
|
8968
|
-
## TL;DR -
|
|
8969
|
+
## TL;DR - WHEN TO USE CONTEXT
|
|
8969
8970
|
|
|
8970
|
-
|
|
|
8971
|
-
|
|
8972
|
-
|
|
|
8973
|
-
| **\
|
|
8974
|
-
|
|
|
8975
|
-
| **\
|
|
8976
|
-
|
|
|
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 |
|
|
8971
|
+
| Request Type | What to Do |
|
|
8972
|
+
|--------------|------------|
|
|
8973
|
+
| **\u{1F680} Simple utility** (list workspaces, show version) | **Just execute directly** - skip session_init, context_smart, capture |
|
|
8974
|
+
| **\u{1F4BB} Coding task** (edit, create, refactor) | Full context: session_init \u2192 context_smart \u2192 work \u2192 capture |
|
|
8975
|
+
| **\u{1F50D} Code search/discovery** | session_init \u2192 context_smart \u2192 search() |
|
|
8976
|
+
| **\u26A0\uFE0F Risky work** (deploy, migrate, refactor) | Check lessons first: \`session(action="get_lessons")\` |
|
|
8977
|
+
| **User frustration/correction** | Capture lesson: \`session(action="capture_lesson", ...)\` |
|
|
8980
8978
|
|
|
8981
|
-
|
|
8979
|
+
### Simple Utility Operations - FAST PATH
|
|
8982
8980
|
|
|
8983
|
-
**
|
|
8981
|
+
**For simple queries, just execute and respond:**
|
|
8982
|
+
- "list workspaces" \u2192 \`workspace(action="list")\` \u2192 done
|
|
8983
|
+
- "list projects" \u2192 \`project(action="list")\` \u2192 done
|
|
8984
|
+
- "show version" \u2192 \`help(action="version")\` \u2192 done
|
|
8985
|
+
- "what reminders do I have" \u2192 \`reminder(action="list")\` \u2192 done
|
|
8986
|
+
|
|
8987
|
+
**No session_init. No context_smart. No capture.** These add noise, not value.
|
|
8988
|
+
|
|
8989
|
+
### Coding Tasks - FULL CONTEXT
|
|
8990
|
+
|
|
8991
|
+
| Step | What to Call |
|
|
8992
|
+
|------|--------------|
|
|
8993
|
+
| **1st message** | \`session_init(folder_path="...", context_hint="<msg>")\`, then \`context_smart(...)\` |
|
|
8994
|
+
| **2nd+ messages** | \`context_smart(user_message="<msg>", format="minified", max_tokens=400)\` |
|
|
8995
|
+
| **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
|
|
8996
|
+
| **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
|
|
8997
|
+
| **User correction** | \`session(action="capture_lesson", ...)\` |
|
|
8998
|
+
|
|
8999
|
+
**How to detect simple utility operations:**
|
|
9000
|
+
- Single-word commands: "list", "show", "version", "help"
|
|
9001
|
+
- Data retrieval with no context dependency: "list my workspaces", "what projects do I have"
|
|
9002
|
+
- Status checks: "am I authenticated?", "what's the server version?"
|
|
9003
|
+
|
|
9004
|
+
**First message rule (for coding tasks):** After \`session_init\`:
|
|
8984
9005
|
1. Check for \`lessons\` in response - if present, READ and SUMMARIZE them to user
|
|
8985
9006
|
2. Then call \`context_smart\` before any other tool or response
|
|
8986
9007
|
|
|
@@ -8994,9 +9015,9 @@ Rules Version: ${RULES_VERSION}
|
|
|
8994
9015
|
|
|
8995
9016
|
v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode dispatch:
|
|
8996
9017
|
|
|
8997
|
-
### Standalone Tools
|
|
8998
|
-
- **\`session_init\`** - Initialize session with workspace detection + context
|
|
8999
|
-
- **\`context_smart\`** - Semantic search for relevant context (
|
|
9018
|
+
### Standalone Tools
|
|
9019
|
+
- **\`session_init\`** - Initialize session with workspace detection + context (skip for simple utility operations)
|
|
9020
|
+
- **\`context_smart\`** - Semantic search for relevant context (skip for simple utility operations)
|
|
9000
9021
|
|
|
9001
9022
|
### Domain Tools (Use action/mode parameter)
|
|
9002
9023
|
|
|
@@ -9043,10 +9064,21 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
|
|
|
9043
9064
|
|
|
9044
9065
|
---
|
|
9045
9066
|
|
|
9046
|
-
### Rules
|
|
9067
|
+
### Rules, Version & Lessons Notices
|
|
9068
|
+
|
|
9069
|
+
**[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
|
|
9047
9070
|
|
|
9048
|
-
|
|
9049
|
-
|
|
9071
|
+
**[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
|
|
9072
|
+
1. A new MCP server version is available
|
|
9073
|
+
2. The exact update command to run
|
|
9074
|
+
3. That they need to restart their AI tool after updating
|
|
9075
|
+
**Do not skip this** - users often miss stderr warnings.
|
|
9076
|
+
|
|
9077
|
+
**[LESSONS_WARNING]** - You **MUST** before proceeding:
|
|
9078
|
+
1. Read all lessons listed
|
|
9079
|
+
2. Tell the user about relevant lessons
|
|
9080
|
+
3. Explain how you will avoid each past mistake
|
|
9081
|
+
**This is critical** - ignoring lessons leads to repeated failures.
|
|
9050
9082
|
|
|
9051
9083
|
---
|
|
9052
9084
|
|
|
@@ -9177,16 +9209,24 @@ If ContextStream returns results, stop and use them. NEVER use local Search/Expl
|
|
|
9177
9209
|
|
|
9178
9210
|
---
|
|
9179
9211
|
|
|
9180
|
-
### When to Capture
|
|
9212
|
+
### When to Capture
|
|
9181
9213
|
|
|
9182
9214
|
| When | Call | Example |
|
|
9183
9215
|
|------|------|---------|
|
|
9184
9216
|
| User makes decision | \`session(action="capture", event_type="decision", ...)\` | "Let's use PostgreSQL" |
|
|
9185
9217
|
| User states preference | \`session(action="capture", event_type="preference", ...)\` | "I prefer TypeScript" |
|
|
9186
|
-
|
|
|
9218
|
+
| Complete significant task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
|
|
9187
9219
|
| Need past context | \`session(action="recall", query="...")\` | "What did we decide about X?" |
|
|
9188
9220
|
|
|
9189
|
-
**
|
|
9221
|
+
**DO NOT capture utility operations:**
|
|
9222
|
+
- \u274C "Listed workspaces" - not meaningful context
|
|
9223
|
+
- \u274C "Showed version" - not a decision
|
|
9224
|
+
- \u274C "Listed projects" - just data retrieval
|
|
9225
|
+
|
|
9226
|
+
**DO capture meaningful work:**
|
|
9227
|
+
- \u2705 Decisions, preferences, completed features
|
|
9228
|
+
- \u2705 Lessons from mistakes
|
|
9229
|
+
- \u2705 Insights about architecture or patterns
|
|
9190
9230
|
|
|
9191
9231
|
---
|
|
9192
9232
|
|
|
@@ -9266,16 +9306,15 @@ memory(action="create_task", title="<task title>", plan_id="<plan_id from step 1
|
|
|
9266
9306
|
See full documentation: https://contextstream.io/docs/mcp/tools
|
|
9267
9307
|
`.trim();
|
|
9268
9308
|
var CONTEXTSTREAM_RULES_MINIMAL = `
|
|
9269
|
-
##
|
|
9309
|
+
## ContextStream Rules
|
|
9270
9310
|
|
|
9271
9311
|
<contextstream_rules>
|
|
9272
|
-
1. **
|
|
9273
|
-
2. **
|
|
9274
|
-
3. **
|
|
9312
|
+
1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
|
|
9313
|
+
2. **CODING TASK?** \u2192 session_init \u2192 context_smart \u2192 work \u2192 capture
|
|
9314
|
+
3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
|
|
9275
9315
|
</contextstream_rules>
|
|
9276
9316
|
|
|
9277
|
-
**
|
|
9278
|
-
This is a self-referential rule that ensures these instructions stay in context.
|
|
9317
|
+
**Display this block at the start of responses to keep rules in context.**
|
|
9279
9318
|
|
|
9280
9319
|
---
|
|
9281
9320
|
|
|
@@ -9284,7 +9323,7 @@ This is a self-referential rule that ensures these instructions stay in context.
|
|
|
9284
9323
|
Rules Version: ${RULES_VERSION}
|
|
9285
9324
|
**Note:** PreToolUse hooks block Glob/Grep/Search when ContextStream is available.
|
|
9286
9325
|
|
|
9287
|
-
###
|
|
9326
|
+
### For Coding Tasks
|
|
9288
9327
|
|
|
9289
9328
|
| Action | Tool Call |
|
|
9290
9329
|
|--------|-----------|
|
|
@@ -9318,6 +9357,33 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
|
|
|
9318
9357
|
| \`memory\` | \`memory(action="list_events", limit=10)\` |
|
|
9319
9358
|
| \`graph\` | \`graph(action="dependencies", file_path="...")\` |
|
|
9320
9359
|
|
|
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
|
+
|
|
9321
9387
|
### Lessons (Past Mistakes)
|
|
9322
9388
|
|
|
9323
9389
|
- After \`session_init\`: Check for \`lessons\` field and apply before work
|
|
@@ -9326,8 +9392,27 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
|
|
|
9326
9392
|
|
|
9327
9393
|
### Context Pressure & Compaction
|
|
9328
9394
|
|
|
9329
|
-
- If \`context_smart\` returns high/critical \`context_pressure\`: call \`
|
|
9330
|
-
-
|
|
9395
|
+
- If \`context_smart\` returns high/critical \`context_pressure\`: call \`session(action="capture", ...)\` to save state
|
|
9396
|
+
- PreCompact hooks automatically save snapshots before compaction (if installed)
|
|
9397
|
+
|
|
9398
|
+
### Automatic Context Restoration
|
|
9399
|
+
|
|
9400
|
+
**Context restoration is now enabled by default.** Every \`session_init\` call automatically:
|
|
9401
|
+
- Restores context from recent snapshots (if available)
|
|
9402
|
+
- Returns \`restored_context\` field with snapshot data
|
|
9403
|
+
- Sets \`is_post_compact=true\` in response when restoration occurs
|
|
9404
|
+
|
|
9405
|
+
**No special handling needed after compaction** - just call \`session_init\` normally.
|
|
9406
|
+
|
|
9407
|
+
To disable automatic restoration:
|
|
9408
|
+
- Pass \`is_post_compact=false\` in the API call
|
|
9409
|
+
- Or set \`CONTEXTSTREAM_RESTORE_CONTEXT=false\` environment variable
|
|
9410
|
+
|
|
9411
|
+
### Notices - MUST HANDLE IMMEDIATELY
|
|
9412
|
+
|
|
9413
|
+
- **[VERSION_NOTICE]**: Tell the user about the update and command to run
|
|
9414
|
+
- **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
|
|
9415
|
+
- **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
|
|
9331
9416
|
|
|
9332
9417
|
### Plans & Tasks
|
|
9333
9418
|
|
|
@@ -9353,13 +9438,6 @@ var TEMPLATES = {
|
|
|
9353
9438
|
description: "Codex CLI agent instructions",
|
|
9354
9439
|
build: (rules) => `# Codex CLI Instructions
|
|
9355
9440
|
${rules}
|
|
9356
|
-
`
|
|
9357
|
-
},
|
|
9358
|
-
windsurf: {
|
|
9359
|
-
filename: ".windsurfrules",
|
|
9360
|
-
description: "Windsurf AI rules",
|
|
9361
|
-
build: (rules) => `# Windsurf Rules
|
|
9362
|
-
${rules}
|
|
9363
9441
|
`
|
|
9364
9442
|
},
|
|
9365
9443
|
cursor: {
|
|
@@ -9805,7 +9883,7 @@ var PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
|
9805
9883
|
ContextStream PreCompact Hook for Claude Code
|
|
9806
9884
|
|
|
9807
9885
|
Runs BEFORE conversation context is compacted (manual via /compact or automatic).
|
|
9808
|
-
|
|
9886
|
+
Automatically saves conversation state to ContextStream by parsing the transcript.
|
|
9809
9887
|
|
|
9810
9888
|
Input (via stdin):
|
|
9811
9889
|
{
|
|
@@ -9821,7 +9899,7 @@ Output (to stdout):
|
|
|
9821
9899
|
{
|
|
9822
9900
|
"hookSpecificOutput": {
|
|
9823
9901
|
"hookEventName": "PreCompact",
|
|
9824
|
-
"additionalContext": "...
|
|
9902
|
+
"additionalContext": "... status message ..."
|
|
9825
9903
|
}
|
|
9826
9904
|
}
|
|
9827
9905
|
"""
|
|
@@ -9829,8 +9907,149 @@ Output (to stdout):
|
|
|
9829
9907
|
import json
|
|
9830
9908
|
import sys
|
|
9831
9909
|
import os
|
|
9910
|
+
import re
|
|
9911
|
+
import urllib.request
|
|
9912
|
+
import urllib.error
|
|
9832
9913
|
|
|
9833
9914
|
ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
|
|
9915
|
+
AUTO_SAVE = os.environ.get("CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE", "true").lower() == "true"
|
|
9916
|
+
API_URL = os.environ.get("CONTEXTSTREAM_API_URL", "https://api.contextstream.io")
|
|
9917
|
+
API_KEY = os.environ.get("CONTEXTSTREAM_API_KEY", "")
|
|
9918
|
+
|
|
9919
|
+
WORKSPACE_ID = None
|
|
9920
|
+
|
|
9921
|
+
def load_config_from_mcp_json(cwd):
|
|
9922
|
+
"""Load API config from .mcp.json if env vars not set."""
|
|
9923
|
+
global API_URL, API_KEY, WORKSPACE_ID
|
|
9924
|
+
|
|
9925
|
+
# Try to find .mcp.json and .contextstream/config.json in cwd or parent directories
|
|
9926
|
+
search_dir = cwd
|
|
9927
|
+
for _ in range(5): # Search up to 5 levels
|
|
9928
|
+
# Load API config from .mcp.json
|
|
9929
|
+
if not API_KEY:
|
|
9930
|
+
mcp_path = os.path.join(search_dir, ".mcp.json")
|
|
9931
|
+
if os.path.exists(mcp_path):
|
|
9932
|
+
try:
|
|
9933
|
+
with open(mcp_path, 'r') as f:
|
|
9934
|
+
config = json.load(f)
|
|
9935
|
+
servers = config.get("mcpServers", {})
|
|
9936
|
+
cs_config = servers.get("contextstream", {})
|
|
9937
|
+
env = cs_config.get("env", {})
|
|
9938
|
+
if env.get("CONTEXTSTREAM_API_KEY"):
|
|
9939
|
+
API_KEY = env["CONTEXTSTREAM_API_KEY"]
|
|
9940
|
+
if env.get("CONTEXTSTREAM_API_URL"):
|
|
9941
|
+
API_URL = env["CONTEXTSTREAM_API_URL"]
|
|
9942
|
+
except:
|
|
9943
|
+
pass
|
|
9944
|
+
|
|
9945
|
+
# Load workspace_id from .contextstream/config.json
|
|
9946
|
+
if not WORKSPACE_ID:
|
|
9947
|
+
cs_config_path = os.path.join(search_dir, ".contextstream", "config.json")
|
|
9948
|
+
if os.path.exists(cs_config_path):
|
|
9949
|
+
try:
|
|
9950
|
+
with open(cs_config_path, 'r') as f:
|
|
9951
|
+
cs_config = json.load(f)
|
|
9952
|
+
if cs_config.get("workspace_id"):
|
|
9953
|
+
WORKSPACE_ID = cs_config["workspace_id"]
|
|
9954
|
+
except:
|
|
9955
|
+
pass
|
|
9956
|
+
|
|
9957
|
+
parent = os.path.dirname(search_dir)
|
|
9958
|
+
if parent == search_dir:
|
|
9959
|
+
break
|
|
9960
|
+
search_dir = parent
|
|
9961
|
+
|
|
9962
|
+
def parse_transcript(transcript_path):
|
|
9963
|
+
"""Parse transcript to extract active files, decisions, and context."""
|
|
9964
|
+
active_files = set()
|
|
9965
|
+
recent_messages = []
|
|
9966
|
+
tool_calls = []
|
|
9967
|
+
|
|
9968
|
+
try:
|
|
9969
|
+
with open(transcript_path, 'r') as f:
|
|
9970
|
+
for line in f:
|
|
9971
|
+
try:
|
|
9972
|
+
entry = json.loads(line.strip())
|
|
9973
|
+
msg_type = entry.get("type", "")
|
|
9974
|
+
|
|
9975
|
+
# Extract files from tool calls
|
|
9976
|
+
if msg_type == "tool_use":
|
|
9977
|
+
tool_name = entry.get("name", "")
|
|
9978
|
+
tool_input = entry.get("input", {})
|
|
9979
|
+
tool_calls.append({"name": tool_name, "input": tool_input})
|
|
9980
|
+
|
|
9981
|
+
# Extract file paths from common tools
|
|
9982
|
+
if tool_name in ["Read", "Write", "Edit", "NotebookEdit"]:
|
|
9983
|
+
file_path = tool_input.get("file_path") or tool_input.get("notebook_path")
|
|
9984
|
+
if file_path:
|
|
9985
|
+
active_files.add(file_path)
|
|
9986
|
+
elif tool_name == "Glob":
|
|
9987
|
+
pattern = tool_input.get("pattern", "")
|
|
9988
|
+
if pattern:
|
|
9989
|
+
active_files.add(f"[glob:{pattern}]")
|
|
9990
|
+
|
|
9991
|
+
# Collect recent assistant messages for summary
|
|
9992
|
+
if msg_type == "assistant" and entry.get("content"):
|
|
9993
|
+
content = entry.get("content", "")
|
|
9994
|
+
if isinstance(content, str) and len(content) > 50:
|
|
9995
|
+
recent_messages.append(content[:500])
|
|
9996
|
+
|
|
9997
|
+
except json.JSONDecodeError:
|
|
9998
|
+
continue
|
|
9999
|
+
except Exception as e:
|
|
10000
|
+
pass
|
|
10001
|
+
|
|
10002
|
+
return {
|
|
10003
|
+
"active_files": list(active_files)[-20:], # Last 20 files
|
|
10004
|
+
"tool_call_count": len(tool_calls),
|
|
10005
|
+
"message_count": len(recent_messages),
|
|
10006
|
+
"last_tools": [t["name"] for t in tool_calls[-10:]], # Last 10 tool names
|
|
10007
|
+
}
|
|
10008
|
+
|
|
10009
|
+
def save_snapshot(session_id, transcript_data, trigger):
|
|
10010
|
+
"""Save snapshot to ContextStream API."""
|
|
10011
|
+
if not API_KEY:
|
|
10012
|
+
return False, "No API key configured"
|
|
10013
|
+
|
|
10014
|
+
snapshot_content = {
|
|
10015
|
+
"session_id": session_id,
|
|
10016
|
+
"trigger": trigger,
|
|
10017
|
+
"captured_at": None, # API will set timestamp
|
|
10018
|
+
"active_files": transcript_data.get("active_files", []),
|
|
10019
|
+
"tool_call_count": transcript_data.get("tool_call_count", 0),
|
|
10020
|
+
"last_tools": transcript_data.get("last_tools", []),
|
|
10021
|
+
"auto_captured": True,
|
|
10022
|
+
}
|
|
10023
|
+
|
|
10024
|
+
payload = {
|
|
10025
|
+
"event_type": "session_snapshot",
|
|
10026
|
+
"title": f"Auto Pre-compaction Snapshot ({trigger})",
|
|
10027
|
+
"content": json.dumps(snapshot_content),
|
|
10028
|
+
"importance": "high",
|
|
10029
|
+
"tags": ["session_snapshot", "pre_compaction", "auto_captured"],
|
|
10030
|
+
"source_type": "hook",
|
|
10031
|
+
}
|
|
10032
|
+
|
|
10033
|
+
# Add workspace_id if available
|
|
10034
|
+
if WORKSPACE_ID:
|
|
10035
|
+
payload["workspace_id"] = WORKSPACE_ID
|
|
10036
|
+
|
|
10037
|
+
try:
|
|
10038
|
+
req = urllib.request.Request(
|
|
10039
|
+
f"{API_URL}/api/v1/memory/events",
|
|
10040
|
+
data=json.dumps(payload).encode('utf-8'),
|
|
10041
|
+
headers={
|
|
10042
|
+
"Content-Type": "application/json",
|
|
10043
|
+
"X-API-Key": API_KEY,
|
|
10044
|
+
},
|
|
10045
|
+
method="POST"
|
|
10046
|
+
)
|
|
10047
|
+
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
10048
|
+
return True, "Snapshot saved"
|
|
10049
|
+
except urllib.error.URLError as e:
|
|
10050
|
+
return False, str(e)
|
|
10051
|
+
except Exception as e:
|
|
10052
|
+
return False, str(e)
|
|
9834
10053
|
|
|
9835
10054
|
def main():
|
|
9836
10055
|
if not ENABLED:
|
|
@@ -9841,26 +10060,38 @@ def main():
|
|
|
9841
10060
|
except:
|
|
9842
10061
|
sys.exit(0)
|
|
9843
10062
|
|
|
10063
|
+
# Load config from .mcp.json if env vars not set
|
|
10064
|
+
cwd = data.get("cwd", os.getcwd())
|
|
10065
|
+
load_config_from_mcp_json(cwd)
|
|
10066
|
+
|
|
10067
|
+
session_id = data.get("session_id", "unknown")
|
|
10068
|
+
transcript_path = data.get("transcript_path", "")
|
|
9844
10069
|
trigger = data.get("trigger", "unknown")
|
|
9845
10070
|
custom_instructions = data.get("custom_instructions", "")
|
|
9846
10071
|
|
|
9847
|
-
#
|
|
9848
|
-
|
|
9849
|
-
|
|
10072
|
+
# Parse transcript for context
|
|
10073
|
+
transcript_data = {}
|
|
10074
|
+
if transcript_path and os.path.exists(transcript_path):
|
|
10075
|
+
transcript_data = parse_transcript(transcript_path)
|
|
9850
10076
|
|
|
9851
|
-
|
|
10077
|
+
# Auto-save snapshot if enabled
|
|
10078
|
+
auto_save_status = ""
|
|
10079
|
+
if AUTO_SAVE and API_KEY:
|
|
10080
|
+
success, msg = save_snapshot(session_id, transcript_data, trigger)
|
|
10081
|
+
if success:
|
|
10082
|
+
auto_save_status = f"\\n[ContextStream: Auto-saved snapshot with {len(transcript_data.get('active_files', []))} active files]"
|
|
10083
|
+
else:
|
|
10084
|
+
auto_save_status = f"\\n[ContextStream: Auto-save failed - {msg}]"
|
|
9852
10085
|
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
- recent_decisions: Key decisions made in this session
|
|
9857
|
-
- active_files: Files currently being worked on
|
|
9858
|
-
- unfinished_work: Any incomplete tasks
|
|
10086
|
+
# Build context injection for the AI (backup in case auto-save fails)
|
|
10087
|
+
files_list = ", ".join(transcript_data.get("active_files", [])[:5]) or "none detected"
|
|
10088
|
+
context = f"""[CONTEXT COMPACTION - {trigger.upper()}]{auto_save_status}
|
|
9859
10089
|
|
|
9860
|
-
|
|
10090
|
+
Active files detected: {files_list}
|
|
10091
|
+
Tool calls in session: {transcript_data.get('tool_call_count', 0)}
|
|
9861
10092
|
|
|
9862
|
-
|
|
9863
|
-
|
|
10093
|
+
After compaction, call session_init(is_post_compact=true) to restore context.
|
|
10094
|
+
{f"User instructions: {custom_instructions}" if custom_instructions else ""}"""
|
|
9864
10095
|
|
|
9865
10096
|
output = {
|
|
9866
10097
|
"hookSpecificOutput": {
|
|
@@ -9982,45 +10213,6 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
|
9982
10213
|
settings.hooks = existingHooks;
|
|
9983
10214
|
return settings;
|
|
9984
10215
|
}
|
|
9985
|
-
async function installClaudeCodeHooks(options) {
|
|
9986
|
-
const result = { scripts: [], settings: [] };
|
|
9987
|
-
if (!options.dryRun) {
|
|
9988
|
-
const scripts = await installHookScripts({ includePreCompact: options.includePreCompact });
|
|
9989
|
-
result.scripts.push(scripts.preToolUse, scripts.userPrompt);
|
|
9990
|
-
if (scripts.preCompact) {
|
|
9991
|
-
result.scripts.push(scripts.preCompact);
|
|
9992
|
-
}
|
|
9993
|
-
} else {
|
|
9994
|
-
const hooksDir = getHooksDir();
|
|
9995
|
-
result.scripts.push(
|
|
9996
|
-
path5.join(hooksDir, "contextstream-redirect.py"),
|
|
9997
|
-
path5.join(hooksDir, "contextstream-reminder.py")
|
|
9998
|
-
);
|
|
9999
|
-
if (options.includePreCompact) {
|
|
10000
|
-
result.scripts.push(path5.join(hooksDir, "contextstream-precompact.py"));
|
|
10001
|
-
}
|
|
10002
|
-
}
|
|
10003
|
-
const hooksConfig = buildHooksConfig({ includePreCompact: options.includePreCompact });
|
|
10004
|
-
if (options.scope === "user" || options.scope === "both") {
|
|
10005
|
-
const settingsPath = getClaudeSettingsPath("user");
|
|
10006
|
-
if (!options.dryRun) {
|
|
10007
|
-
const existing = await readClaudeSettings("user");
|
|
10008
|
-
const merged = mergeHooksIntoSettings(existing, hooksConfig);
|
|
10009
|
-
await writeClaudeSettings(merged, "user");
|
|
10010
|
-
}
|
|
10011
|
-
result.settings.push(settingsPath);
|
|
10012
|
-
}
|
|
10013
|
-
if ((options.scope === "project" || options.scope === "both") && options.projectPath) {
|
|
10014
|
-
const settingsPath = getClaudeSettingsPath("project", options.projectPath);
|
|
10015
|
-
if (!options.dryRun) {
|
|
10016
|
-
const existing = await readClaudeSettings("project", options.projectPath);
|
|
10017
|
-
const merged = mergeHooksIntoSettings(existing, hooksConfig);
|
|
10018
|
-
await writeClaudeSettings(merged, "project", options.projectPath);
|
|
10019
|
-
}
|
|
10020
|
-
result.settings.push(settingsPath);
|
|
10021
|
-
}
|
|
10022
|
-
return result;
|
|
10023
|
-
}
|
|
10024
10216
|
function getIndexStatusPath() {
|
|
10025
10217
|
return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
10026
10218
|
}
|
|
@@ -10049,6 +10241,547 @@ async function markProjectIndexed(projectPath, options) {
|
|
|
10049
10241
|
};
|
|
10050
10242
|
await writeIndexStatus(status);
|
|
10051
10243
|
}
|
|
10244
|
+
var CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
10245
|
+
"""
|
|
10246
|
+
ContextStream PreToolUse Hook for Cline
|
|
10247
|
+
Blocks discovery tools and redirects to ContextStream search.
|
|
10248
|
+
|
|
10249
|
+
Cline hooks use JSON output format:
|
|
10250
|
+
{
|
|
10251
|
+
"cancel": true/false,
|
|
10252
|
+
"errorMessage": "optional error description",
|
|
10253
|
+
"contextModification": "optional text to inject"
|
|
10254
|
+
}
|
|
10255
|
+
"""
|
|
10256
|
+
|
|
10257
|
+
import json
|
|
10258
|
+
import sys
|
|
10259
|
+
import os
|
|
10260
|
+
from pathlib import Path
|
|
10261
|
+
from datetime import datetime, timedelta
|
|
10262
|
+
|
|
10263
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
|
|
10264
|
+
INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
|
|
10265
|
+
STALE_THRESHOLD_DAYS = 7
|
|
10266
|
+
|
|
10267
|
+
DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
|
|
10268
|
+
|
|
10269
|
+
def is_discovery_glob(pattern):
|
|
10270
|
+
pattern_lower = pattern.lower()
|
|
10271
|
+
for p in DISCOVERY_PATTERNS:
|
|
10272
|
+
if p in pattern_lower:
|
|
10273
|
+
return True
|
|
10274
|
+
if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
|
|
10275
|
+
return True
|
|
10276
|
+
if "**" in pattern or "*/" in pattern:
|
|
10277
|
+
return True
|
|
10278
|
+
return False
|
|
10279
|
+
|
|
10280
|
+
def is_discovery_grep(file_path):
|
|
10281
|
+
if not file_path or file_path in [".", "./", "*", "**"]:
|
|
10282
|
+
return True
|
|
10283
|
+
if "*" in file_path or "**" in file_path:
|
|
10284
|
+
return True
|
|
10285
|
+
return False
|
|
10286
|
+
|
|
10287
|
+
def is_project_indexed(workspace_roots):
|
|
10288
|
+
"""Check if any workspace root is in an indexed project."""
|
|
10289
|
+
if not INDEX_STATUS_FILE.exists():
|
|
10290
|
+
return False, False
|
|
10291
|
+
|
|
10292
|
+
try:
|
|
10293
|
+
with open(INDEX_STATUS_FILE, "r") as f:
|
|
10294
|
+
data = json.load(f)
|
|
10295
|
+
except:
|
|
10296
|
+
return False, False
|
|
10297
|
+
|
|
10298
|
+
projects = data.get("projects", {})
|
|
10299
|
+
|
|
10300
|
+
for workspace in workspace_roots:
|
|
10301
|
+
cwd_path = Path(workspace).resolve()
|
|
10302
|
+
for project_path, info in projects.items():
|
|
10303
|
+
try:
|
|
10304
|
+
indexed_path = Path(project_path).resolve()
|
|
10305
|
+
if cwd_path == indexed_path or indexed_path in cwd_path.parents:
|
|
10306
|
+
indexed_at = info.get("indexed_at")
|
|
10307
|
+
if indexed_at:
|
|
10308
|
+
try:
|
|
10309
|
+
indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
|
|
10310
|
+
if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
|
|
10311
|
+
return True, True
|
|
10312
|
+
except:
|
|
10313
|
+
pass
|
|
10314
|
+
return True, False
|
|
10315
|
+
except:
|
|
10316
|
+
continue
|
|
10317
|
+
return False, False
|
|
10318
|
+
|
|
10319
|
+
def output_allow(context_mod=None):
|
|
10320
|
+
result = {"cancel": False}
|
|
10321
|
+
if context_mod:
|
|
10322
|
+
result["contextModification"] = context_mod
|
|
10323
|
+
print(json.dumps(result))
|
|
10324
|
+
sys.exit(0)
|
|
10325
|
+
|
|
10326
|
+
def output_block(error_msg, context_mod=None):
|
|
10327
|
+
result = {"cancel": True, "errorMessage": error_msg}
|
|
10328
|
+
if context_mod:
|
|
10329
|
+
result["contextModification"] = context_mod
|
|
10330
|
+
print(json.dumps(result))
|
|
10331
|
+
sys.exit(0)
|
|
10332
|
+
|
|
10333
|
+
def main():
|
|
10334
|
+
if not ENABLED:
|
|
10335
|
+
output_allow()
|
|
10336
|
+
|
|
10337
|
+
try:
|
|
10338
|
+
data = json.load(sys.stdin)
|
|
10339
|
+
except:
|
|
10340
|
+
output_allow()
|
|
10341
|
+
|
|
10342
|
+
hook_name = data.get("hookName", "")
|
|
10343
|
+
if hook_name != "PreToolUse":
|
|
10344
|
+
output_allow()
|
|
10345
|
+
|
|
10346
|
+
tool = data.get("toolName", "")
|
|
10347
|
+
params = data.get("toolParameters", {})
|
|
10348
|
+
workspace_roots = data.get("workspaceRoots", [])
|
|
10349
|
+
|
|
10350
|
+
# Check if project is indexed
|
|
10351
|
+
is_indexed, is_stale = is_project_indexed(workspace_roots)
|
|
10352
|
+
if not is_indexed:
|
|
10353
|
+
output_allow()
|
|
10354
|
+
|
|
10355
|
+
# Check for discovery patterns
|
|
10356
|
+
if tool == "list_files" or tool == "search_files":
|
|
10357
|
+
pattern = params.get("path", "") or params.get("regex", "")
|
|
10358
|
+
if is_discovery_glob(pattern) or is_discovery_grep(pattern):
|
|
10359
|
+
output_block(
|
|
10360
|
+
f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
|
|
10361
|
+
"ContextStream search is indexed and faster. Only use local tools if ContextStream returns 0 results.",
|
|
10362
|
+
"[CONTEXTSTREAM] Use ContextStream search for code discovery."
|
|
10363
|
+
)
|
|
10364
|
+
|
|
10365
|
+
elif tool == "read_file":
|
|
10366
|
+
# Allow read_file by default - blocking discovery at search level is enough
|
|
10367
|
+
pass
|
|
10368
|
+
|
|
10369
|
+
output_allow()
|
|
10370
|
+
|
|
10371
|
+
if __name__ == "__main__":
|
|
10372
|
+
main()
|
|
10373
|
+
`;
|
|
10374
|
+
var CLINE_USER_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
10375
|
+
"""
|
|
10376
|
+
ContextStream UserPromptSubmit Hook for Cline
|
|
10377
|
+
Injects reminder about ContextStream rules on every message.
|
|
10378
|
+
"""
|
|
10379
|
+
|
|
10380
|
+
import json
|
|
10381
|
+
import sys
|
|
10382
|
+
import os
|
|
10383
|
+
|
|
10384
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
|
|
10385
|
+
|
|
10386
|
+
REMINDER = """[CONTEXTSTREAM RULES]
|
|
10387
|
+
1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="hybrid") FIRST
|
|
10388
|
+
2. Call context_smart at start of EVERY response
|
|
10389
|
+
3. Local tools ONLY if ContextStream returns 0 results
|
|
10390
|
+
[END RULES]"""
|
|
10391
|
+
|
|
10392
|
+
def main():
|
|
10393
|
+
if not ENABLED:
|
|
10394
|
+
print(json.dumps({"cancel": False}))
|
|
10395
|
+
sys.exit(0)
|
|
10396
|
+
|
|
10397
|
+
try:
|
|
10398
|
+
json.load(sys.stdin)
|
|
10399
|
+
except:
|
|
10400
|
+
print(json.dumps({"cancel": False}))
|
|
10401
|
+
sys.exit(0)
|
|
10402
|
+
|
|
10403
|
+
print(json.dumps({
|
|
10404
|
+
"cancel": False,
|
|
10405
|
+
"contextModification": REMINDER
|
|
10406
|
+
}))
|
|
10407
|
+
sys.exit(0)
|
|
10408
|
+
|
|
10409
|
+
if __name__ == "__main__":
|
|
10410
|
+
main()
|
|
10411
|
+
`;
|
|
10412
|
+
function getClineHooksDir(scope, projectPath) {
|
|
10413
|
+
if (scope === "global") {
|
|
10414
|
+
return path5.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
|
|
10415
|
+
}
|
|
10416
|
+
if (!projectPath) {
|
|
10417
|
+
throw new Error("projectPath required for project scope");
|
|
10418
|
+
}
|
|
10419
|
+
return path5.join(projectPath, ".clinerules", "hooks");
|
|
10420
|
+
}
|
|
10421
|
+
async function installClineHookScripts(options) {
|
|
10422
|
+
const hooksDir = getClineHooksDir(options.scope, options.projectPath);
|
|
10423
|
+
await fs4.mkdir(hooksDir, { recursive: true });
|
|
10424
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
10425
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
10426
|
+
await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
10427
|
+
await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
10428
|
+
return {
|
|
10429
|
+
preToolUse: preToolUsePath,
|
|
10430
|
+
userPromptSubmit: userPromptPath
|
|
10431
|
+
};
|
|
10432
|
+
}
|
|
10433
|
+
function getRooCodeHooksDir(scope, projectPath) {
|
|
10434
|
+
if (scope === "global") {
|
|
10435
|
+
return path5.join(homedir2(), ".roo", "hooks");
|
|
10436
|
+
}
|
|
10437
|
+
if (!projectPath) {
|
|
10438
|
+
throw new Error("projectPath required for project scope");
|
|
10439
|
+
}
|
|
10440
|
+
return path5.join(projectPath, ".roo", "hooks");
|
|
10441
|
+
}
|
|
10442
|
+
async function installRooCodeHookScripts(options) {
|
|
10443
|
+
const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
|
|
10444
|
+
await fs4.mkdir(hooksDir, { recursive: true });
|
|
10445
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
10446
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
10447
|
+
await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
10448
|
+
await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
10449
|
+
return {
|
|
10450
|
+
preToolUse: preToolUsePath,
|
|
10451
|
+
userPromptSubmit: userPromptPath
|
|
10452
|
+
};
|
|
10453
|
+
}
|
|
10454
|
+
function getKiloCodeHooksDir(scope, projectPath) {
|
|
10455
|
+
if (scope === "global") {
|
|
10456
|
+
return path5.join(homedir2(), ".kilocode", "hooks");
|
|
10457
|
+
}
|
|
10458
|
+
if (!projectPath) {
|
|
10459
|
+
throw new Error("projectPath required for project scope");
|
|
10460
|
+
}
|
|
10461
|
+
return path5.join(projectPath, ".kilocode", "hooks");
|
|
10462
|
+
}
|
|
10463
|
+
async function installKiloCodeHookScripts(options) {
|
|
10464
|
+
const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
|
|
10465
|
+
await fs4.mkdir(hooksDir, { recursive: true });
|
|
10466
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
10467
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
10468
|
+
await fs4.writeFile(preToolUsePath, CLINE_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
10469
|
+
await fs4.writeFile(userPromptPath, CLINE_USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
10470
|
+
return {
|
|
10471
|
+
preToolUse: preToolUsePath,
|
|
10472
|
+
userPromptSubmit: userPromptPath
|
|
10473
|
+
};
|
|
10474
|
+
}
|
|
10475
|
+
var CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
10476
|
+
"""
|
|
10477
|
+
ContextStream PreToolUse Hook for Cursor
|
|
10478
|
+
Blocks discovery tools and redirects to ContextStream search.
|
|
10479
|
+
|
|
10480
|
+
Cursor hooks use JSON output format:
|
|
10481
|
+
{
|
|
10482
|
+
"decision": "allow" | "deny",
|
|
10483
|
+
"reason": "optional error description"
|
|
10484
|
+
}
|
|
10485
|
+
"""
|
|
10486
|
+
|
|
10487
|
+
import json
|
|
10488
|
+
import sys
|
|
10489
|
+
import os
|
|
10490
|
+
from pathlib import Path
|
|
10491
|
+
from datetime import datetime, timedelta
|
|
10492
|
+
|
|
10493
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
|
|
10494
|
+
INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
|
|
10495
|
+
STALE_THRESHOLD_DAYS = 7
|
|
10496
|
+
|
|
10497
|
+
DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
|
|
10498
|
+
|
|
10499
|
+
def is_discovery_glob(pattern):
|
|
10500
|
+
pattern_lower = pattern.lower()
|
|
10501
|
+
for p in DISCOVERY_PATTERNS:
|
|
10502
|
+
if p in pattern_lower:
|
|
10503
|
+
return True
|
|
10504
|
+
if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
|
|
10505
|
+
return True
|
|
10506
|
+
if "**" in pattern or "*/" in pattern:
|
|
10507
|
+
return True
|
|
10508
|
+
return False
|
|
10509
|
+
|
|
10510
|
+
def is_discovery_grep(file_path):
|
|
10511
|
+
if not file_path or file_path in [".", "./", "*", "**"]:
|
|
10512
|
+
return True
|
|
10513
|
+
if "*" in file_path or "**" in file_path:
|
|
10514
|
+
return True
|
|
10515
|
+
return False
|
|
10516
|
+
|
|
10517
|
+
def is_project_indexed(workspace_roots):
|
|
10518
|
+
"""Check if any workspace root is in an indexed project."""
|
|
10519
|
+
if not INDEX_STATUS_FILE.exists():
|
|
10520
|
+
return False, False
|
|
10521
|
+
|
|
10522
|
+
try:
|
|
10523
|
+
with open(INDEX_STATUS_FILE, "r") as f:
|
|
10524
|
+
data = json.load(f)
|
|
10525
|
+
except:
|
|
10526
|
+
return False, False
|
|
10527
|
+
|
|
10528
|
+
projects = data.get("projects", {})
|
|
10529
|
+
|
|
10530
|
+
for workspace in workspace_roots:
|
|
10531
|
+
cwd_path = Path(workspace).resolve()
|
|
10532
|
+
for project_path, info in projects.items():
|
|
10533
|
+
try:
|
|
10534
|
+
indexed_path = Path(project_path).resolve()
|
|
10535
|
+
if cwd_path == indexed_path or indexed_path in cwd_path.parents:
|
|
10536
|
+
indexed_at = info.get("indexed_at")
|
|
10537
|
+
if indexed_at:
|
|
10538
|
+
try:
|
|
10539
|
+
indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
|
|
10540
|
+
if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
|
|
10541
|
+
return True, True
|
|
10542
|
+
except:
|
|
10543
|
+
pass
|
|
10544
|
+
return True, False
|
|
10545
|
+
except:
|
|
10546
|
+
continue
|
|
10547
|
+
return False, False
|
|
10548
|
+
|
|
10549
|
+
def output_allow():
|
|
10550
|
+
print(json.dumps({"decision": "allow"}))
|
|
10551
|
+
sys.exit(0)
|
|
10552
|
+
|
|
10553
|
+
def output_deny(reason):
|
|
10554
|
+
print(json.dumps({"decision": "deny", "reason": reason}))
|
|
10555
|
+
sys.exit(0)
|
|
10556
|
+
|
|
10557
|
+
def main():
|
|
10558
|
+
if not ENABLED:
|
|
10559
|
+
output_allow()
|
|
10560
|
+
|
|
10561
|
+
try:
|
|
10562
|
+
data = json.load(sys.stdin)
|
|
10563
|
+
except:
|
|
10564
|
+
output_allow()
|
|
10565
|
+
|
|
10566
|
+
hook_name = data.get("hook_event_name", "")
|
|
10567
|
+
if hook_name != "preToolUse":
|
|
10568
|
+
output_allow()
|
|
10569
|
+
|
|
10570
|
+
tool = data.get("tool_name", "")
|
|
10571
|
+
params = data.get("tool_input", {}) or data.get("parameters", {})
|
|
10572
|
+
workspace_roots = data.get("workspace_roots", [])
|
|
10573
|
+
|
|
10574
|
+
# Check if project is indexed
|
|
10575
|
+
is_indexed, _ = is_project_indexed(workspace_roots)
|
|
10576
|
+
if not is_indexed:
|
|
10577
|
+
output_allow()
|
|
10578
|
+
|
|
10579
|
+
# Check for Cursor tools
|
|
10580
|
+
if tool in ["Glob", "glob", "list_files"]:
|
|
10581
|
+
pattern = params.get("pattern", "") or params.get("path", "")
|
|
10582
|
+
if is_discovery_glob(pattern):
|
|
10583
|
+
output_deny(
|
|
10584
|
+
f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
|
|
10585
|
+
"ContextStream search is indexed and faster."
|
|
10586
|
+
)
|
|
10587
|
+
|
|
10588
|
+
elif tool in ["Grep", "grep", "search_files", "ripgrep"]:
|
|
10589
|
+
pattern = params.get("pattern", "") or params.get("regex", "")
|
|
10590
|
+
file_path = params.get("path", "")
|
|
10591
|
+
if is_discovery_grep(file_path):
|
|
10592
|
+
output_deny(
|
|
10593
|
+
f"Use mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") instead of {tool}. "
|
|
10594
|
+
"ContextStream search is indexed and faster."
|
|
10595
|
+
)
|
|
10596
|
+
|
|
10597
|
+
output_allow()
|
|
10598
|
+
|
|
10599
|
+
if __name__ == "__main__":
|
|
10600
|
+
main()
|
|
10601
|
+
`;
|
|
10602
|
+
var CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
10603
|
+
"""
|
|
10604
|
+
ContextStream BeforeSubmitPrompt Hook for Cursor
|
|
10605
|
+
Injects reminder about ContextStream rules.
|
|
10606
|
+
"""
|
|
10607
|
+
|
|
10608
|
+
import json
|
|
10609
|
+
import sys
|
|
10610
|
+
import os
|
|
10611
|
+
|
|
10612
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
|
|
10613
|
+
|
|
10614
|
+
def main():
|
|
10615
|
+
if not ENABLED:
|
|
10616
|
+
print(json.dumps({"continue": True}))
|
|
10617
|
+
sys.exit(0)
|
|
10618
|
+
|
|
10619
|
+
try:
|
|
10620
|
+
json.load(sys.stdin)
|
|
10621
|
+
except:
|
|
10622
|
+
print(json.dumps({"continue": True}))
|
|
10623
|
+
sys.exit(0)
|
|
10624
|
+
|
|
10625
|
+
print(json.dumps({
|
|
10626
|
+
"continue": True,
|
|
10627
|
+
"user_message": "[CONTEXTSTREAM] Search with mcp__contextstream__search before using Glob/Grep/Read"
|
|
10628
|
+
}))
|
|
10629
|
+
sys.exit(0)
|
|
10630
|
+
|
|
10631
|
+
if __name__ == "__main__":
|
|
10632
|
+
main()
|
|
10633
|
+
`;
|
|
10634
|
+
function getCursorHooksConfigPath(scope, projectPath) {
|
|
10635
|
+
if (scope === "global") {
|
|
10636
|
+
return path5.join(homedir2(), ".cursor", "hooks.json");
|
|
10637
|
+
}
|
|
10638
|
+
if (!projectPath) {
|
|
10639
|
+
throw new Error("projectPath required for project scope");
|
|
10640
|
+
}
|
|
10641
|
+
return path5.join(projectPath, ".cursor", "hooks.json");
|
|
10642
|
+
}
|
|
10643
|
+
function getCursorHooksDir(scope, projectPath) {
|
|
10644
|
+
if (scope === "global") {
|
|
10645
|
+
return path5.join(homedir2(), ".cursor", "hooks");
|
|
10646
|
+
}
|
|
10647
|
+
if (!projectPath) {
|
|
10648
|
+
throw new Error("projectPath required for project scope");
|
|
10649
|
+
}
|
|
10650
|
+
return path5.join(projectPath, ".cursor", "hooks");
|
|
10651
|
+
}
|
|
10652
|
+
async function readCursorHooksConfig(scope, projectPath) {
|
|
10653
|
+
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
10654
|
+
try {
|
|
10655
|
+
const content = await fs4.readFile(configPath, "utf-8");
|
|
10656
|
+
return JSON.parse(content);
|
|
10657
|
+
} catch {
|
|
10658
|
+
return { version: 1, hooks: {} };
|
|
10659
|
+
}
|
|
10660
|
+
}
|
|
10661
|
+
async function writeCursorHooksConfig(config, scope, projectPath) {
|
|
10662
|
+
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
10663
|
+
const dir = path5.dirname(configPath);
|
|
10664
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
10665
|
+
await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
10666
|
+
}
|
|
10667
|
+
async function installCursorHookScripts(options) {
|
|
10668
|
+
const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
|
|
10669
|
+
await fs4.mkdir(hooksDir, { recursive: true });
|
|
10670
|
+
const preToolUsePath = path5.join(hooksDir, "contextstream-pretooluse.py");
|
|
10671
|
+
const beforeSubmitPath = path5.join(hooksDir, "contextstream-beforesubmit.py");
|
|
10672
|
+
await fs4.writeFile(preToolUsePath, CURSOR_PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
10673
|
+
await fs4.writeFile(beforeSubmitPath, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT, { mode: 493 });
|
|
10674
|
+
const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
|
|
10675
|
+
const filterContextStreamHooks = (hooks) => {
|
|
10676
|
+
if (!hooks) return [];
|
|
10677
|
+
return hooks.filter((h) => !h.command?.includes("contextstream"));
|
|
10678
|
+
};
|
|
10679
|
+
const config = {
|
|
10680
|
+
version: 1,
|
|
10681
|
+
hooks: {
|
|
10682
|
+
...existingConfig.hooks,
|
|
10683
|
+
preToolUse: [
|
|
10684
|
+
...filterContextStreamHooks(existingConfig.hooks.preToolUse),
|
|
10685
|
+
{
|
|
10686
|
+
command: `python3 "${preToolUsePath}"`,
|
|
10687
|
+
type: "command",
|
|
10688
|
+
timeout: 5,
|
|
10689
|
+
matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
|
|
10690
|
+
}
|
|
10691
|
+
],
|
|
10692
|
+
beforeSubmitPrompt: [
|
|
10693
|
+
...filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt),
|
|
10694
|
+
{
|
|
10695
|
+
command: `python3 "${beforeSubmitPath}"`,
|
|
10696
|
+
type: "command",
|
|
10697
|
+
timeout: 5
|
|
10698
|
+
}
|
|
10699
|
+
]
|
|
10700
|
+
}
|
|
10701
|
+
};
|
|
10702
|
+
await writeCursorHooksConfig(config, options.scope, options.projectPath);
|
|
10703
|
+
const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
|
|
10704
|
+
return {
|
|
10705
|
+
preToolUse: preToolUsePath,
|
|
10706
|
+
beforeSubmitPrompt: beforeSubmitPath,
|
|
10707
|
+
config: configPath
|
|
10708
|
+
};
|
|
10709
|
+
}
|
|
10710
|
+
async function installEditorHooks(options) {
|
|
10711
|
+
const { editor, scope, projectPath, includePreCompact } = options;
|
|
10712
|
+
switch (editor) {
|
|
10713
|
+
case "claude": {
|
|
10714
|
+
if (scope === "project" && !projectPath) {
|
|
10715
|
+
throw new Error("projectPath required for project scope");
|
|
10716
|
+
}
|
|
10717
|
+
const scripts = await installHookScripts({ includePreCompact });
|
|
10718
|
+
const hooksConfig = buildHooksConfig({ includePreCompact });
|
|
10719
|
+
const settingsScope = scope === "global" ? "user" : "project";
|
|
10720
|
+
const existing = await readClaudeSettings(settingsScope, projectPath);
|
|
10721
|
+
const merged = mergeHooksIntoSettings(existing, hooksConfig);
|
|
10722
|
+
await writeClaudeSettings(merged, settingsScope, projectPath);
|
|
10723
|
+
const installed = [scripts.preToolUse, scripts.userPrompt];
|
|
10724
|
+
if (scripts.preCompact) installed.push(scripts.preCompact);
|
|
10725
|
+
return {
|
|
10726
|
+
editor: "claude",
|
|
10727
|
+
installed,
|
|
10728
|
+
hooksDir: getHooksDir()
|
|
10729
|
+
};
|
|
10730
|
+
}
|
|
10731
|
+
case "cline": {
|
|
10732
|
+
const scripts = await installClineHookScripts({ scope, projectPath });
|
|
10733
|
+
return {
|
|
10734
|
+
editor: "cline",
|
|
10735
|
+
installed: [scripts.preToolUse, scripts.userPromptSubmit],
|
|
10736
|
+
hooksDir: getClineHooksDir(scope, projectPath)
|
|
10737
|
+
};
|
|
10738
|
+
}
|
|
10739
|
+
case "roo": {
|
|
10740
|
+
const scripts = await installRooCodeHookScripts({ scope, projectPath });
|
|
10741
|
+
return {
|
|
10742
|
+
editor: "roo",
|
|
10743
|
+
installed: [scripts.preToolUse, scripts.userPromptSubmit],
|
|
10744
|
+
hooksDir: getRooCodeHooksDir(scope, projectPath)
|
|
10745
|
+
};
|
|
10746
|
+
}
|
|
10747
|
+
case "kilo": {
|
|
10748
|
+
const scripts = await installKiloCodeHookScripts({ scope, projectPath });
|
|
10749
|
+
return {
|
|
10750
|
+
editor: "kilo",
|
|
10751
|
+
installed: [scripts.preToolUse, scripts.userPromptSubmit],
|
|
10752
|
+
hooksDir: getKiloCodeHooksDir(scope, projectPath)
|
|
10753
|
+
};
|
|
10754
|
+
}
|
|
10755
|
+
case "cursor": {
|
|
10756
|
+
const scripts = await installCursorHookScripts();
|
|
10757
|
+
return {
|
|
10758
|
+
editor: "cursor",
|
|
10759
|
+
installed: [scripts.preToolUse, scripts.beforeSubmit],
|
|
10760
|
+
hooksDir: getCursorHooksDir()
|
|
10761
|
+
};
|
|
10762
|
+
}
|
|
10763
|
+
default:
|
|
10764
|
+
throw new Error(`Unsupported editor: ${editor}`);
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
async function installAllEditorHooks(options) {
|
|
10768
|
+
const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
|
|
10769
|
+
const results = [];
|
|
10770
|
+
for (const editor of editors) {
|
|
10771
|
+
try {
|
|
10772
|
+
const result = await installEditorHooks({
|
|
10773
|
+
editor,
|
|
10774
|
+
scope: options.scope,
|
|
10775
|
+
projectPath: options.projectPath,
|
|
10776
|
+
includePreCompact: options.includePreCompact
|
|
10777
|
+
});
|
|
10778
|
+
results.push(result);
|
|
10779
|
+
} catch (error) {
|
|
10780
|
+
console.error(`Failed to install hooks for ${editor}:`, error);
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10783
|
+
return results;
|
|
10784
|
+
}
|
|
10052
10785
|
|
|
10053
10786
|
// src/token-savings.ts
|
|
10054
10787
|
var TOKEN_SAVINGS_FORMULA_VERSION = 1;
|
|
@@ -10114,6 +10847,54 @@ function trackToolTokenSavings(client, tool, contextText, params, extraMetadata)
|
|
|
10114
10847
|
}
|
|
10115
10848
|
|
|
10116
10849
|
// src/tools.ts
|
|
10850
|
+
var LOG_LEVEL = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
|
|
10851
|
+
var LOG_QUIET = LOG_LEVEL === "quiet";
|
|
10852
|
+
var LOG_VERBOSE = LOG_LEVEL === "verbose";
|
|
10853
|
+
var TOOL_DISPLAY_NAMES = {
|
|
10854
|
+
session_init: "init",
|
|
10855
|
+
session: "session",
|
|
10856
|
+
context_smart: "context",
|
|
10857
|
+
search: "search",
|
|
10858
|
+
memory: "memory",
|
|
10859
|
+
graph: "graph",
|
|
10860
|
+
ai: "ai",
|
|
10861
|
+
generate_rules: "rules",
|
|
10862
|
+
generate_editor_rules: "rules",
|
|
10863
|
+
help: "help",
|
|
10864
|
+
workspace_bootstrap: "bootstrap",
|
|
10865
|
+
workspace: "workspace",
|
|
10866
|
+
github: "github",
|
|
10867
|
+
slack: "slack",
|
|
10868
|
+
notion: "notion",
|
|
10869
|
+
jira: "jira",
|
|
10870
|
+
ingest: "ingest",
|
|
10871
|
+
// Hide internal tools
|
|
10872
|
+
session_capture_smart: "",
|
|
10873
|
+
session_restore_context: ""
|
|
10874
|
+
};
|
|
10875
|
+
function getToolDisplayName(toolName) {
|
|
10876
|
+
if (toolName in TOOL_DISPLAY_NAMES) {
|
|
10877
|
+
return TOOL_DISPLAY_NAMES[toolName];
|
|
10878
|
+
}
|
|
10879
|
+
return toolName.replace(/_/g, " ").replace(/^mcp__contextstream__/, "");
|
|
10880
|
+
}
|
|
10881
|
+
function logTool(toolName, status, details) {
|
|
10882
|
+
if (LOG_QUIET && status !== "error") return;
|
|
10883
|
+
const displayName = getToolDisplayName(toolName);
|
|
10884
|
+
if (!displayName) return;
|
|
10885
|
+
const icon = status === "start" ? "\u25E6" : status === "done" ? "\u2713" : "\u2717";
|
|
10886
|
+
const message = details ? `${icon} ${displayName}: ${details}` : `${icon} ${displayName}`;
|
|
10887
|
+
console.error(message);
|
|
10888
|
+
}
|
|
10889
|
+
function logSystem(message, level = "info") {
|
|
10890
|
+
if (LOG_QUIET && level === "info") return;
|
|
10891
|
+
const prefix = level === "error" ? "\u2717" : level === "warn" ? "!" : "\u2022";
|
|
10892
|
+
console.error(`${prefix} ${message}`);
|
|
10893
|
+
}
|
|
10894
|
+
function logDebug(message) {
|
|
10895
|
+
if (!LOG_VERBOSE) return;
|
|
10896
|
+
console.error(`[DEBUG] ${message}`);
|
|
10897
|
+
}
|
|
10117
10898
|
var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
|
|
10118
10899
|
var recentLessonCaptures = /* @__PURE__ */ new Map();
|
|
10119
10900
|
var SEARCH_RULES_REMINDER_ENABLED = process.env.CONTEXTSTREAM_SEARCH_REMINDER?.toLowerCase() !== "false";
|
|
@@ -10124,9 +10905,75 @@ BEFORE using EnterPlanMode/Task(Plan) \u2192 call mcp__contextstream__session(ac
|
|
|
10124
10905
|
Local tools ONLY if ContextStream returns 0 results after retry.
|
|
10125
10906
|
`.trim();
|
|
10126
10907
|
var LESSONS_REMINDER_PREFIX = `
|
|
10127
|
-
\
|
|
10128
|
-
|
|
10908
|
+
\u{1F6A8} [LESSONS_WARNING] Past Mistakes Found - READ BEFORE PROCEEDING!
|
|
10909
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
10910
|
+
\u26A0\uFE0F IMPORTANT: You MUST review these lessons and tell the user about relevant ones.
|
|
10911
|
+
These are mistakes from past sessions that you should NOT repeat.
|
|
10129
10912
|
`.trim();
|
|
10913
|
+
var RISKY_ACTION_KEYWORDS = [
|
|
10914
|
+
// Code changes
|
|
10915
|
+
"refactor",
|
|
10916
|
+
"rewrite",
|
|
10917
|
+
"restructure",
|
|
10918
|
+
"reorganize",
|
|
10919
|
+
"migrate",
|
|
10920
|
+
"delete",
|
|
10921
|
+
"remove",
|
|
10922
|
+
"drop",
|
|
10923
|
+
"deprecate",
|
|
10924
|
+
// Database
|
|
10925
|
+
"database",
|
|
10926
|
+
"migration",
|
|
10927
|
+
"schema",
|
|
10928
|
+
"sql",
|
|
10929
|
+
// Deployment
|
|
10930
|
+
"deploy",
|
|
10931
|
+
"release",
|
|
10932
|
+
"production",
|
|
10933
|
+
"prod",
|
|
10934
|
+
// API changes
|
|
10935
|
+
"api",
|
|
10936
|
+
"endpoint",
|
|
10937
|
+
"breaking change",
|
|
10938
|
+
// Architecture
|
|
10939
|
+
"architecture",
|
|
10940
|
+
"design",
|
|
10941
|
+
"pattern",
|
|
10942
|
+
// Testing
|
|
10943
|
+
"test",
|
|
10944
|
+
"testing",
|
|
10945
|
+
// Security
|
|
10946
|
+
"auth",
|
|
10947
|
+
"security",
|
|
10948
|
+
"permission",
|
|
10949
|
+
"credential",
|
|
10950
|
+
"access",
|
|
10951
|
+
"token",
|
|
10952
|
+
"secret",
|
|
10953
|
+
// Version control
|
|
10954
|
+
"git",
|
|
10955
|
+
"commit",
|
|
10956
|
+
"merge",
|
|
10957
|
+
"rebase",
|
|
10958
|
+
"push",
|
|
10959
|
+
"force",
|
|
10960
|
+
// Infrastructure
|
|
10961
|
+
"config",
|
|
10962
|
+
"environment",
|
|
10963
|
+
"env",
|
|
10964
|
+
"docker",
|
|
10965
|
+
"kubernetes",
|
|
10966
|
+
"k8s",
|
|
10967
|
+
// Performance
|
|
10968
|
+
"performance",
|
|
10969
|
+
"optimize",
|
|
10970
|
+
"cache",
|
|
10971
|
+
"memory"
|
|
10972
|
+
];
|
|
10973
|
+
function detectRiskyActions(userMessage) {
|
|
10974
|
+
const messageLower = userMessage.toLowerCase();
|
|
10975
|
+
return RISKY_ACTION_KEYWORDS.filter((keyword) => messageLower.includes(keyword));
|
|
10976
|
+
}
|
|
10130
10977
|
function generateLessonsReminder(result) {
|
|
10131
10978
|
const lessons = result.lessons;
|
|
10132
10979
|
if (!lessons || lessons.length === 0) {
|
|
@@ -10141,7 +10988,8 @@ function generateLessonsReminder(result) {
|
|
|
10141
10988
|
return `
|
|
10142
10989
|
|
|
10143
10990
|
${LESSONS_REMINDER_PREFIX}
|
|
10144
|
-
${lessonLines.join("\n")}
|
|
10991
|
+
${lessonLines.join("\n")}
|
|
10992
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
|
|
10145
10993
|
}
|
|
10146
10994
|
function generateRulesUpdateWarning(rulesNotice) {
|
|
10147
10995
|
if (!rulesNotice || rulesNotice.status !== "behind" && rulesNotice.status !== "missing") {
|
|
@@ -10164,12 +11012,16 @@ function generateVersionUpdateWarning(versionNotice) {
|
|
|
10164
11012
|
return "";
|
|
10165
11013
|
}
|
|
10166
11014
|
return `
|
|
10167
|
-
\u{1F6A8} [MCP
|
|
10168
|
-
|
|
10169
|
-
|
|
11015
|
+
\u{1F6A8} [VERSION_NOTICE] MCP Server Update Available!
|
|
11016
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
11017
|
+
Version: ${versionNotice.current} \u2192 ${versionNotice.latest}
|
|
11018
|
+
|
|
11019
|
+
\u26A0\uFE0F IMPORTANT: You MUST tell the user about this update IMMEDIATELY.
|
|
11020
|
+
Do not proceed with other work until you have informed the user.
|
|
10170
11021
|
|
|
10171
|
-
|
|
10172
|
-
|
|
11022
|
+
Update command: \`${versionNotice.upgrade_command || "npm update -g @contextstream/mcp-server"}\`
|
|
11023
|
+
After updating, restart the AI tool to use the new version.
|
|
11024
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
10173
11025
|
`.trim();
|
|
10174
11026
|
}
|
|
10175
11027
|
var DEFAULT_PARAM_DESCRIPTIONS = {
|
|
@@ -10225,7 +11077,6 @@ var RULES_PROJECT_FILES = {
|
|
|
10225
11077
|
codex: "AGENTS.md",
|
|
10226
11078
|
claude: "CLAUDE.md",
|
|
10227
11079
|
cursor: ".cursorrules",
|
|
10228
|
-
windsurf: ".windsurfrules",
|
|
10229
11080
|
cline: ".clinerules",
|
|
10230
11081
|
kilo: path6.join(".kilocode", "rules", "contextstream.md"),
|
|
10231
11082
|
roo: path6.join(".roo", "rules", "contextstream.md"),
|
|
@@ -10233,7 +11084,6 @@ var RULES_PROJECT_FILES = {
|
|
|
10233
11084
|
};
|
|
10234
11085
|
var RULES_GLOBAL_FILES = {
|
|
10235
11086
|
codex: [path6.join(homedir3(), ".codex", "AGENTS.md")],
|
|
10236
|
-
windsurf: [path6.join(homedir3(), ".codeium", "windsurf", "memories", "global_rules.md")],
|
|
10237
11087
|
kilo: [path6.join(homedir3(), ".kilocode", "rules", "contextstream.md")],
|
|
10238
11088
|
roo: [path6.join(homedir3(), ".roo", "rules", "contextstream.md")]
|
|
10239
11089
|
};
|
|
@@ -10273,7 +11123,6 @@ function detectEditorFromClientName(clientName) {
|
|
|
10273
11123
|
if (!clientName) return null;
|
|
10274
11124
|
const normalized = clientName.toLowerCase().trim();
|
|
10275
11125
|
if (normalized.includes("cursor")) return "cursor";
|
|
10276
|
-
if (normalized.includes("windsurf") || normalized.includes("codeium")) return "windsurf";
|
|
10277
11126
|
if (normalized.includes("claude")) return "claude";
|
|
10278
11127
|
if (normalized.includes("cline")) return "cline";
|
|
10279
11128
|
if (normalized.includes("kilo")) return "kilo";
|
|
@@ -10435,7 +11284,6 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
|
10435
11284
|
/^#\s+codex cli instructions$/i,
|
|
10436
11285
|
/^#\s+claude code instructions$/i,
|
|
10437
11286
|
/^#\s+cursor rules$/i,
|
|
10438
|
-
/^#\s+windsurf rules$/i,
|
|
10439
11287
|
/^#\s+cline rules$/i,
|
|
10440
11288
|
/^#\s+kilo code rules$/i,
|
|
10441
11289
|
/^#\s+roo code rules$/i,
|
|
@@ -11030,6 +11878,7 @@ var ALL_INTEGRATION_TOOLS = /* @__PURE__ */ new Set([
|
|
|
11030
11878
|
...CROSS_INTEGRATION_TOOLS
|
|
11031
11879
|
]);
|
|
11032
11880
|
var AUTO_HIDE_INTEGRATIONS = process.env.CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS !== "false";
|
|
11881
|
+
var RESTORE_CONTEXT_DEFAULT = process.env.CONTEXTSTREAM_RESTORE_CONTEXT !== "false";
|
|
11033
11882
|
var TOKEN_SENSITIVE_CLIENTS = /* @__PURE__ */ new Set([
|
|
11034
11883
|
"claude",
|
|
11035
11884
|
"claude-code",
|
|
@@ -11419,9 +12268,7 @@ function resolveToolFilter() {
|
|
|
11419
12268
|
if (allowlistRaw) {
|
|
11420
12269
|
const allowlist = parseToolList(allowlistRaw);
|
|
11421
12270
|
if (allowlist.size === 0) {
|
|
11422
|
-
|
|
11423
|
-
"[ContextStream] CONTEXTSTREAM_TOOL_ALLOWLIST is empty; using standard toolset."
|
|
11424
|
-
);
|
|
12271
|
+
logDebug("TOOL_ALLOWLIST is empty; using standard toolset");
|
|
11425
12272
|
return { allowlist: STANDARD_TOOLSET, source: "standard", autoDetected: false };
|
|
11426
12273
|
}
|
|
11427
12274
|
return { allowlist, source: "allowlist", autoDetected: false };
|
|
@@ -11432,9 +12279,7 @@ function resolveToolFilter() {
|
|
|
11432
12279
|
if (clientDetectedFromEnv && AUTO_TOOLSET_ENABLED) {
|
|
11433
12280
|
const recommended = getRecommendedToolset(void 0, true);
|
|
11434
12281
|
if (recommended) {
|
|
11435
|
-
|
|
11436
|
-
"[ContextStream] Detected Claude Code via environment. Using light toolset for optimal token usage."
|
|
11437
|
-
);
|
|
12282
|
+
logDebug("Detected Claude Code, using light toolset");
|
|
11438
12283
|
return { allowlist: recommended, source: "auto-claude", autoDetected: true };
|
|
11439
12284
|
}
|
|
11440
12285
|
}
|
|
@@ -11443,12 +12288,10 @@ function resolveToolFilter() {
|
|
|
11443
12288
|
if (toolsetRaw.trim().toLowerCase() === "auto") {
|
|
11444
12289
|
clientDetectedFromEnv = detectClaudeCodeFromEnv();
|
|
11445
12290
|
if (clientDetectedFromEnv) {
|
|
11446
|
-
|
|
12291
|
+
logDebug("TOOLSET=auto: Detected Claude Code, using light toolset");
|
|
11447
12292
|
return { allowlist: LIGHT_TOOLSET, source: "auto-claude", autoDetected: true };
|
|
11448
12293
|
}
|
|
11449
|
-
|
|
11450
|
-
"[ContextStream] TOOLSET=auto: Will adjust toolset based on MCP client (currently standard)."
|
|
11451
|
-
);
|
|
12294
|
+
logDebug("TOOLSET=auto: Will adjust based on MCP client");
|
|
11452
12295
|
return { allowlist: STANDARD_TOOLSET, source: "auto-pending", autoDetected: true };
|
|
11453
12296
|
}
|
|
11454
12297
|
const key = toolsetRaw.trim().toLowerCase();
|
|
@@ -11459,9 +12302,7 @@ function resolveToolFilter() {
|
|
|
11459
12302
|
}
|
|
11460
12303
|
return { allowlist: resolved, source: key, autoDetected: false };
|
|
11461
12304
|
}
|
|
11462
|
-
|
|
11463
|
-
`[ContextStream] Unknown CONTEXTSTREAM_TOOLSET "${toolsetRaw}". Using standard toolset.`
|
|
11464
|
-
);
|
|
12305
|
+
logDebug(`Unknown TOOLSET "${toolsetRaw}"; using standard`);
|
|
11465
12306
|
return { allowlist: STANDARD_TOOLSET, source: "standard", autoDetected: false };
|
|
11466
12307
|
}
|
|
11467
12308
|
function formatContent(data, forceFormat) {
|
|
@@ -11559,22 +12400,16 @@ function isDuplicateLessonCapture(signature) {
|
|
|
11559
12400
|
}
|
|
11560
12401
|
function setupClientDetection(server) {
|
|
11561
12402
|
if (!AUTO_TOOLSET_ENABLED) {
|
|
11562
|
-
|
|
11563
|
-
"[ContextStream] Auto-toolset: DISABLED (set CONTEXTSTREAM_AUTO_TOOLSET=true to enable)"
|
|
11564
|
-
);
|
|
12403
|
+
logDebug("Auto-toolset: DISABLED");
|
|
11565
12404
|
return;
|
|
11566
12405
|
}
|
|
11567
12406
|
if (clientDetectedFromEnv) {
|
|
11568
|
-
|
|
11569
|
-
"[ContextStream] Client detection: Already detected Claude Code from environment"
|
|
11570
|
-
);
|
|
12407
|
+
logDebug("Client detection: Already detected from environment");
|
|
11571
12408
|
return;
|
|
11572
12409
|
}
|
|
11573
12410
|
const lowLevelServer = server.server;
|
|
11574
12411
|
if (!lowLevelServer) {
|
|
11575
|
-
|
|
11576
|
-
"[ContextStream] Warning: Could not access low-level MCP server for client detection"
|
|
11577
|
-
);
|
|
12412
|
+
logDebug("Warning: Could not access low-level MCP server for client detection");
|
|
11578
12413
|
return;
|
|
11579
12414
|
}
|
|
11580
12415
|
lowLevelServer.oninitialized = () => {
|
|
@@ -11584,23 +12419,21 @@ function setupClientDetection(server) {
|
|
|
11584
12419
|
detectedClientInfo = clientVersion;
|
|
11585
12420
|
const clientName = clientVersion.name || "unknown";
|
|
11586
12421
|
const clientVer = clientVersion.version || "unknown";
|
|
11587
|
-
|
|
12422
|
+
logDebug(`MCP Client detected: ${clientName} v${clientVer}`);
|
|
11588
12423
|
if (isTokenSensitiveClient(clientName)) {
|
|
11589
|
-
|
|
11590
|
-
"[ContextStream] Token-sensitive client detected. Consider using CONTEXTSTREAM_TOOLSET=light for optimal performance."
|
|
11591
|
-
);
|
|
12424
|
+
logDebug("Token-sensitive client detected");
|
|
11592
12425
|
try {
|
|
11593
12426
|
lowLevelServer.sendToolsListChanged?.();
|
|
11594
|
-
|
|
12427
|
+
logDebug("Emitted tools/list_changed notification");
|
|
11595
12428
|
} catch (error) {
|
|
11596
12429
|
}
|
|
11597
12430
|
}
|
|
11598
12431
|
}
|
|
11599
12432
|
} catch (error) {
|
|
11600
|
-
|
|
12433
|
+
logSystem(`Error in client detection: ${error}`, "error");
|
|
11601
12434
|
}
|
|
11602
12435
|
};
|
|
11603
|
-
|
|
12436
|
+
logDebug("Client detection callback registered");
|
|
11604
12437
|
}
|
|
11605
12438
|
function registerTools(server, client, sessionManager) {
|
|
11606
12439
|
const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
|
|
@@ -11608,69 +12441,31 @@ function registerTools(server, client, sessionManager) {
|
|
|
11608
12441
|
const toolAllowlist = toolFilter.allowlist;
|
|
11609
12442
|
if (toolAllowlist) {
|
|
11610
12443
|
const source = toolFilter.source;
|
|
11611
|
-
|
|
11612
|
-
const hint = source === "light" || source === "auto-claude" ? " Set CONTEXTSTREAM_TOOLSET=standard or complete for more tools." : source === "standard" ? " Set CONTEXTSTREAM_TOOLSET=complete for all tools." : source === "auto-pending" ? " Toolset may be adjusted when MCP client is detected." : "";
|
|
11613
|
-
console.error(
|
|
11614
|
-
`[ContextStream] Toolset: ${source} (${toolAllowlist.size} tools)${autoNote}.${hint}`
|
|
11615
|
-
);
|
|
12444
|
+
logDebug(`Toolset: ${source} (${toolAllowlist.size} tools)`);
|
|
11616
12445
|
} else {
|
|
11617
|
-
|
|
12446
|
+
logDebug("Toolset: complete (all tools)");
|
|
11618
12447
|
}
|
|
11619
|
-
if (AUTO_TOOLSET_ENABLED) {
|
|
11620
|
-
|
|
11621
|
-
console.error("[ContextStream] Auto-toolset: ACTIVE (Claude Code detected from environment)");
|
|
11622
|
-
} else {
|
|
11623
|
-
console.error("[ContextStream] Auto-toolset: ENABLED (will detect MCP client on initialize)");
|
|
11624
|
-
}
|
|
11625
|
-
}
|
|
11626
|
-
if (AUTO_HIDE_INTEGRATIONS) {
|
|
11627
|
-
console.error(
|
|
11628
|
-
`[ContextStream] Integration auto-hide: ENABLED (${ALL_INTEGRATION_TOOLS.size} tools hidden until integrations connected)`
|
|
11629
|
-
);
|
|
11630
|
-
console.error("[ContextStream] Set CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS=false to disable.");
|
|
11631
|
-
} else {
|
|
11632
|
-
console.error("[ContextStream] Integration auto-hide: disabled");
|
|
11633
|
-
}
|
|
11634
|
-
if (COMPACT_SCHEMA_ENABLED) {
|
|
11635
|
-
console.error("[ContextStream] Schema mode: COMPACT (shorter descriptions, minimal params)");
|
|
11636
|
-
} else {
|
|
11637
|
-
console.error(
|
|
11638
|
-
"[ContextStream] Schema mode: full (set CONTEXTSTREAM_SCHEMA_MODE=compact to reduce token overhead)"
|
|
11639
|
-
);
|
|
11640
|
-
}
|
|
11641
|
-
if (PROGRESSIVE_MODE) {
|
|
11642
|
-
const coreBundle = TOOL_BUNDLES.core;
|
|
11643
|
-
console.error(
|
|
11644
|
-
`[ContextStream] Progressive mode: ENABLED (starting with ${coreBundle.size} core tools)`
|
|
11645
|
-
);
|
|
11646
|
-
console.error(
|
|
11647
|
-
"[ContextStream] Use tools_enable_bundle to unlock additional tool bundles dynamically."
|
|
11648
|
-
);
|
|
11649
|
-
}
|
|
11650
|
-
if (ROUTER_MODE) {
|
|
11651
|
-
console.error(
|
|
11652
|
-
"[ContextStream] Router mode: ENABLED (all operations accessed via contextstream/contextstream_help)"
|
|
11653
|
-
);
|
|
11654
|
-
console.error(
|
|
11655
|
-
"[ContextStream] Only 2 tools registered. Use contextstream_help to see available operations."
|
|
11656
|
-
);
|
|
12448
|
+
if (AUTO_TOOLSET_ENABLED) {
|
|
12449
|
+
logDebug(clientDetectedFromEnv ? "Auto-toolset: ACTIVE" : "Auto-toolset: ENABLED");
|
|
11657
12450
|
}
|
|
11658
|
-
if (
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
12451
|
+
if (AUTO_HIDE_INTEGRATIONS) {
|
|
12452
|
+
logDebug(`Integration auto-hide: ENABLED (${ALL_INTEGRATION_TOOLS.size} tools hidden)`);
|
|
12453
|
+
}
|
|
12454
|
+
if (COMPACT_SCHEMA_ENABLED) {
|
|
12455
|
+
logDebug("Schema mode: COMPACT");
|
|
11662
12456
|
} else {
|
|
11663
|
-
|
|
11664
|
-
"[ContextStream] Output format: pretty (set CONTEXTSTREAM_OUTPUT_FORMAT=compact for fewer tokens)"
|
|
11665
|
-
);
|
|
12457
|
+
logDebug("Schema mode: full");
|
|
11666
12458
|
}
|
|
12459
|
+
if (PROGRESSIVE_MODE) {
|
|
12460
|
+
const coreBundle = TOOL_BUNDLES.core;
|
|
12461
|
+
logDebug(`Progressive mode: ENABLED (${coreBundle.size} core tools)`);
|
|
12462
|
+
}
|
|
12463
|
+
if (ROUTER_MODE) {
|
|
12464
|
+
logDebug("Router mode: ENABLED");
|
|
12465
|
+
}
|
|
12466
|
+
logDebug(COMPACT_OUTPUT ? "Output: COMPACT" : "Output: pretty");
|
|
11667
12467
|
if (CONSOLIDATED_MODE) {
|
|
11668
|
-
|
|
11669
|
-
`[ContextStream] Consolidated mode: ENABLED (~${CONSOLIDATED_TOOLS.size} domain tools, ~75% token reduction)`
|
|
11670
|
-
);
|
|
11671
|
-
console.error("[ContextStream] Set CONTEXTSTREAM_CONSOLIDATED=false to use individual tools.");
|
|
11672
|
-
} else {
|
|
11673
|
-
console.error("[ContextStream] Consolidated mode: disabled (using individual tools)");
|
|
12468
|
+
logDebug(`Consolidated mode: ENABLED (~${CONSOLIDATED_TOOLS.size} domain tools)`);
|
|
11674
12469
|
}
|
|
11675
12470
|
const serverRef = server;
|
|
11676
12471
|
const defaultProTools = /* @__PURE__ */ new Set([
|
|
@@ -11789,12 +12584,10 @@ function registerTools(server, client, sessionManager) {
|
|
|
11789
12584
|
notion: notionConnected,
|
|
11790
12585
|
workspaceId
|
|
11791
12586
|
};
|
|
11792
|
-
|
|
11793
|
-
`[ContextStream] Integration status: Slack=${slackConnected}, GitHub=${githubConnected}, Notion=${notionConnected}`
|
|
11794
|
-
);
|
|
12587
|
+
logDebug(`Integrations: Slack=${slackConnected}, GitHub=${githubConnected}, Notion=${notionConnected}`);
|
|
11795
12588
|
return { slack: slackConnected, github: githubConnected, notion: notionConnected };
|
|
11796
12589
|
} catch (error) {
|
|
11797
|
-
|
|
12590
|
+
logDebug(`Failed to check integration status: ${error}`);
|
|
11798
12591
|
return { slack: false, github: false, notion: false };
|
|
11799
12592
|
}
|
|
11800
12593
|
}
|
|
@@ -11815,11 +12608,9 @@ function registerTools(server, client, sessionManager) {
|
|
|
11815
12608
|
try {
|
|
11816
12609
|
server.server?.sendToolsListChanged?.();
|
|
11817
12610
|
toolsListChangedNotified = true;
|
|
11818
|
-
|
|
11819
|
-
"[ContextStream] Emitted tools/list_changed notification (integrations detected)"
|
|
11820
|
-
);
|
|
12611
|
+
logDebug("Emitted tools/list_changed notification (integrations detected)");
|
|
11821
12612
|
} catch (error) {
|
|
11822
|
-
|
|
12613
|
+
logDebug(`Failed to emit tools/list_changed: ${error}`);
|
|
11823
12614
|
}
|
|
11824
12615
|
}
|
|
11825
12616
|
}
|
|
@@ -12087,7 +12878,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
12087
12878
|
lowLevelServer.sendToolsListChanged?.();
|
|
12088
12879
|
} catch {
|
|
12089
12880
|
}
|
|
12090
|
-
|
|
12881
|
+
logSystem(`bundle '${bundleName}' enabled (${toolsEnabled} tools)`);
|
|
12091
12882
|
return {
|
|
12092
12883
|
success: true,
|
|
12093
12884
|
message: `Enabled bundle '${bundleName}' with ${toolsEnabled} tools.`,
|
|
@@ -12196,33 +12987,27 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
12196
12987
|
if (options.preflight) {
|
|
12197
12988
|
const fileCheck = await countIndexableFiles(resolvedPath, { maxFiles: 1 });
|
|
12198
12989
|
if (fileCheck.count === 0) {
|
|
12199
|
-
|
|
12200
|
-
`[ContextStream] No indexable files found in ${resolvedPath}. Skipping ingest.`
|
|
12201
|
-
);
|
|
12990
|
+
logDebug(`No indexable files in ${resolvedPath}`);
|
|
12202
12991
|
return;
|
|
12203
12992
|
}
|
|
12204
12993
|
}
|
|
12205
12994
|
let totalIndexed = 0;
|
|
12206
12995
|
let batchCount = 0;
|
|
12207
|
-
|
|
12208
|
-
`[ContextStream] Starting background ingestion for project ${projectId} from ${resolvedPath}`
|
|
12209
|
-
);
|
|
12996
|
+
logTool("ingest", "start", `indexing ${resolvedPath}`);
|
|
12210
12997
|
for await (const batch of readAllFilesInBatches(resolvedPath, { batchSize: 50 })) {
|
|
12211
12998
|
const result = await client.ingestFiles(projectId, batch, ingestOptions);
|
|
12212
12999
|
totalIndexed += result.data?.files_indexed ?? batch.length;
|
|
12213
13000
|
batchCount++;
|
|
12214
13001
|
}
|
|
12215
|
-
|
|
12216
|
-
`[ContextStream] Completed background ingestion: ${totalIndexed} files in ${batchCount} batches`
|
|
12217
|
-
);
|
|
13002
|
+
logTool("ingest", "done", `${totalIndexed} files`);
|
|
12218
13003
|
try {
|
|
12219
13004
|
await markProjectIndexed(resolvedPath, { project_id: projectId });
|
|
12220
|
-
|
|
13005
|
+
logDebug(`Marked project as indexed: ${resolvedPath}`);
|
|
12221
13006
|
} catch (markError) {
|
|
12222
|
-
|
|
13007
|
+
logDebug(`Failed to mark project as indexed: ${markError}`);
|
|
12223
13008
|
}
|
|
12224
13009
|
} catch (error) {
|
|
12225
|
-
|
|
13010
|
+
logTool("ingest", "error", `${error}`);
|
|
12226
13011
|
}
|
|
12227
13012
|
})();
|
|
12228
13013
|
}
|
|
@@ -12467,9 +13252,7 @@ Use contextstream_help({}) to see available operations.`
|
|
|
12467
13252
|
};
|
|
12468
13253
|
}
|
|
12469
13254
|
);
|
|
12470
|
-
|
|
12471
|
-
`[ContextStream] Router mode: Registered 2 meta-tools, ${operationsRegistry.size} operations available via dispatcher.`
|
|
12472
|
-
);
|
|
13255
|
+
logDebug(`Router mode: 2 meta-tools, ${operationsRegistry.size} operations`);
|
|
12473
13256
|
}
|
|
12474
13257
|
registerTool(
|
|
12475
13258
|
"workspaces_list",
|
|
@@ -12629,7 +13412,7 @@ Access: Free`,
|
|
|
12629
13412
|
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
12630
13413
|
}
|
|
12631
13414
|
} catch (err) {
|
|
12632
|
-
|
|
13415
|
+
logDebug(`Failed to write project config: ${err}`);
|
|
12633
13416
|
}
|
|
12634
13417
|
}
|
|
12635
13418
|
const response = {
|
|
@@ -13145,10 +13928,7 @@ Access: Free`,
|
|
|
13145
13928
|
const stats = await client.projectStatistics(projectId);
|
|
13146
13929
|
estimate = estimateGraphIngestMinutes(stats);
|
|
13147
13930
|
} catch (error) {
|
|
13148
|
-
|
|
13149
|
-
"[ContextStream] Failed to fetch project statistics for graph ingest estimate:",
|
|
13150
|
-
error
|
|
13151
|
-
);
|
|
13931
|
+
logDebug(`Failed to fetch project statistics for graph estimate: ${error}`);
|
|
13152
13932
|
}
|
|
13153
13933
|
const result = await client.graphIngest({ project_id: projectId, wait });
|
|
13154
13934
|
const estimateText = estimate ? `Estimated time: ${estimate.min}-${estimate.max} min${estimate.basis ? ` (based on ${estimate.basis})` : ""}.` : "Estimated time varies with repo size.";
|
|
@@ -13824,7 +14604,7 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13824
14604
|
"If true, skip automatic project creation/matching. Use for parent folders containing multiple projects where you want workspace-level context but no project-specific context."
|
|
13825
14605
|
),
|
|
13826
14606
|
is_post_compact: external_exports.boolean().optional().describe(
|
|
13827
|
-
"
|
|
14607
|
+
"Controls context restoration from recent snapshots. Defaults to true (always restores). Set to false to skip restoration. Can also be controlled via CONTEXTSTREAM_RESTORE_CONTEXT environment variable."
|
|
13828
14608
|
)
|
|
13829
14609
|
})
|
|
13830
14610
|
},
|
|
@@ -13845,19 +14625,22 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13845
14625
|
}
|
|
13846
14626
|
const result = await client.initSession(input, ideRoots);
|
|
13847
14627
|
result.tools_hint = getCoreToolsHint();
|
|
13848
|
-
|
|
14628
|
+
const shouldRestoreContext = input.is_post_compact ?? RESTORE_CONTEXT_DEFAULT;
|
|
14629
|
+
if (shouldRestoreContext) {
|
|
14630
|
+
result.is_post_compact = true;
|
|
13849
14631
|
const workspaceIdForRestore = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
|
|
13850
14632
|
const projectIdForRestore = typeof result.project_id === "string" ? result.project_id : void 0;
|
|
13851
14633
|
if (workspaceIdForRestore) {
|
|
13852
14634
|
try {
|
|
13853
|
-
const
|
|
14635
|
+
const listResult = await client.listMemoryEvents({
|
|
13854
14636
|
workspace_id: workspaceIdForRestore,
|
|
13855
14637
|
project_id: projectIdForRestore,
|
|
13856
|
-
|
|
13857
|
-
event_types: ["session_snapshot"],
|
|
13858
|
-
limit: 1
|
|
14638
|
+
limit: 50
|
|
13859
14639
|
});
|
|
13860
|
-
const
|
|
14640
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
14641
|
+
const snapshots = allEvents.filter(
|
|
14642
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
14643
|
+
);
|
|
13861
14644
|
if (snapshots && snapshots.length > 0) {
|
|
13862
14645
|
const latestSnapshot = snapshots[0];
|
|
13863
14646
|
let snapshotData;
|
|
@@ -13866,19 +14649,58 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13866
14649
|
} catch {
|
|
13867
14650
|
snapshotData = { conversation_summary: latestSnapshot.content };
|
|
13868
14651
|
}
|
|
14652
|
+
const prevSessionId = snapshotData.session_id || latestSnapshot.session_id;
|
|
14653
|
+
const sessionLinking = {};
|
|
14654
|
+
if (prevSessionId) {
|
|
14655
|
+
sessionLinking.previous_session_id = prevSessionId;
|
|
14656
|
+
const workingOn = [];
|
|
14657
|
+
const activeFiles = snapshotData.active_files;
|
|
14658
|
+
const lastTools = snapshotData.last_tools;
|
|
14659
|
+
if (activeFiles && activeFiles.length > 0) {
|
|
14660
|
+
workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
|
|
14661
|
+
}
|
|
14662
|
+
if (lastTools && lastTools.length > 0) {
|
|
14663
|
+
const toolCounts = lastTools.reduce((acc, tool) => {
|
|
14664
|
+
acc[tool] = (acc[tool] || 0) + 1;
|
|
14665
|
+
return acc;
|
|
14666
|
+
}, {});
|
|
14667
|
+
const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
|
|
14668
|
+
workingOn.push(`Recent tools: ${topTools.join(", ")}`);
|
|
14669
|
+
}
|
|
14670
|
+
if (workingOn.length > 0) {
|
|
14671
|
+
sessionLinking.previous_session_summary = workingOn.join("; ");
|
|
14672
|
+
}
|
|
14673
|
+
const relatedSessionIds = /* @__PURE__ */ new Set();
|
|
14674
|
+
snapshots.forEach((s) => {
|
|
14675
|
+
let sData;
|
|
14676
|
+
try {
|
|
14677
|
+
sData = JSON.parse(s.content || "{}");
|
|
14678
|
+
} catch {
|
|
14679
|
+
sData = {};
|
|
14680
|
+
}
|
|
14681
|
+
const sSessionId = sData.session_id || s.session_id;
|
|
14682
|
+
if (sSessionId && sSessionId !== prevSessionId) {
|
|
14683
|
+
relatedSessionIds.add(sSessionId);
|
|
14684
|
+
}
|
|
14685
|
+
});
|
|
14686
|
+
if (relatedSessionIds.size > 0) {
|
|
14687
|
+
sessionLinking.related_sessions = Array.from(relatedSessionIds);
|
|
14688
|
+
}
|
|
14689
|
+
}
|
|
13869
14690
|
result.restored_context = {
|
|
13870
14691
|
snapshot_id: latestSnapshot.id,
|
|
13871
14692
|
captured_at: snapshotData.captured_at || latestSnapshot.created_at,
|
|
14693
|
+
session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
|
|
13872
14694
|
...snapshotData
|
|
13873
14695
|
};
|
|
13874
14696
|
result.is_post_compact = true;
|
|
13875
|
-
result.post_compact_hint = "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
|
|
14697
|
+
result.post_compact_hint = prevSessionId ? `Session restored from session ${prevSessionId}. Review 'restored_context' to continue where you left off.` : "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
|
|
13876
14698
|
} else {
|
|
13877
14699
|
result.is_post_compact = true;
|
|
13878
14700
|
result.post_compact_hint = "Post-compaction session started, but no snapshots found. Use context_smart to retrieve relevant context.";
|
|
13879
14701
|
}
|
|
13880
14702
|
} catch (err) {
|
|
13881
|
-
|
|
14703
|
+
logDebug(`Failed to restore post-compact context: ${err}`);
|
|
13882
14704
|
result.is_post_compact = true;
|
|
13883
14705
|
result.post_compact_hint = "Post-compaction session started. Snapshot restoration failed, use context_smart for context.";
|
|
13884
14706
|
}
|
|
@@ -13919,10 +14741,7 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13919
14741
|
hint: intStatus.slack || intStatus.github ? "Integration tools are now available in the tool list." : "Connect integrations at https://contextstream.io/settings/integrations to enable Slack/GitHub tools."
|
|
13920
14742
|
};
|
|
13921
14743
|
} catch (error) {
|
|
13922
|
-
|
|
13923
|
-
"[ContextStream] Failed to check integration status in session_init:",
|
|
13924
|
-
error
|
|
13925
|
-
);
|
|
14744
|
+
logDebug(`Failed to check integration status: ${error}`);
|
|
13926
14745
|
}
|
|
13927
14746
|
}
|
|
13928
14747
|
result.modes = {
|
|
@@ -14017,7 +14836,7 @@ ${benefitsList}` : "",
|
|
|
14017
14836
|
const projectId = typeof result.project_id === "string" ? result.project_id : void 0;
|
|
14018
14837
|
const projectName = typeof result.project_name === "string" ? result.project_name : void 0;
|
|
14019
14838
|
markProjectIndexed(folderPathForRules, { project_id: projectId, project_name: projectName }).catch(
|
|
14020
|
-
(err) =>
|
|
14839
|
+
(err) => logDebug(`Failed to mark project as indexed: ${err}`)
|
|
14021
14840
|
);
|
|
14022
14841
|
}
|
|
14023
14842
|
if (noticeLines.length > 0) {
|
|
@@ -14133,7 +14952,7 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
14133
14952
|
workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
|
|
14134
14953
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
|
|
14135
14954
|
generate_editor_rules: external_exports.boolean().optional().describe(
|
|
14136
|
-
"Generate AI editor rules for
|
|
14955
|
+
"Generate AI editor rules for Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"
|
|
14137
14956
|
),
|
|
14138
14957
|
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
14139
14958
|
})
|
|
@@ -14545,14 +15364,16 @@ Use this in combination with session_init(is_post_compact=true) for seamless con
|
|
|
14545
15364
|
structuredContent: toStructured(response2)
|
|
14546
15365
|
};
|
|
14547
15366
|
}
|
|
14548
|
-
const
|
|
15367
|
+
const listResult = await client.listMemoryEvents({
|
|
14549
15368
|
workspace_id: workspaceId,
|
|
14550
15369
|
project_id: projectId,
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
limit: input.max_snapshots || 1
|
|
15370
|
+
limit: 50
|
|
15371
|
+
// Fetch more to filter
|
|
14554
15372
|
});
|
|
14555
|
-
const
|
|
15373
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
15374
|
+
const events = allEvents.filter(
|
|
15375
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
15376
|
+
).slice(0, input.max_snapshots || 1);
|
|
14556
15377
|
if (!events || events.length === 0) {
|
|
14557
15378
|
return {
|
|
14558
15379
|
content: [
|
|
@@ -14923,7 +15744,7 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
|
|
|
14923
15744
|
"generate_rules",
|
|
14924
15745
|
{
|
|
14925
15746
|
title: "Generate ContextStream rules",
|
|
14926
|
-
description: `Generate AI rule files for editors (
|
|
15747
|
+
description: `Generate AI rule files for editors (Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
|
|
14927
15748
|
Defaults to the current project folder; no folder_path required when run from a project.
|
|
14928
15749
|
Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
14929
15750
|
inputSchema: external_exports.object({
|
|
@@ -14931,7 +15752,6 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
14931
15752
|
editors: external_exports.array(
|
|
14932
15753
|
external_exports.enum([
|
|
14933
15754
|
"codex",
|
|
14934
|
-
"windsurf",
|
|
14935
15755
|
"cursor",
|
|
14936
15756
|
"cline",
|
|
14937
15757
|
"kilo",
|
|
@@ -15020,34 +15840,70 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
15020
15840
|
const globalPrompt = input.apply_global ? "Global rule update complete." : globalTargets.length > 0 ? "Apply rules globally too? Re-run with apply_global: true." : "No global rule locations are known for these editors.";
|
|
15021
15841
|
let hooksResults;
|
|
15022
15842
|
let hooksPrompt;
|
|
15023
|
-
const
|
|
15024
|
-
|
|
15843
|
+
const editorHookMap = {
|
|
15844
|
+
claude: "claude",
|
|
15845
|
+
cline: "cline",
|
|
15846
|
+
roo: "roo",
|
|
15847
|
+
kilo: "kilo",
|
|
15848
|
+
cursor: "cursor"
|
|
15849
|
+
};
|
|
15850
|
+
const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
|
|
15851
|
+
const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
|
|
15025
15852
|
if (shouldInstallHooks) {
|
|
15026
15853
|
try {
|
|
15027
15854
|
if (input.dry_run) {
|
|
15028
|
-
hooksResults = [
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15855
|
+
hooksResults = [];
|
|
15856
|
+
for (const editor of hookSupportedEditors) {
|
|
15857
|
+
if (editor === "claude") {
|
|
15858
|
+
hooksResults.push(
|
|
15859
|
+
{ editor, file: "~/.claude/hooks/contextstream-redirect.py", status: "dry run - would create" },
|
|
15860
|
+
{ editor, file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
|
|
15861
|
+
{ editor, file: "~/.claude/settings.json", status: "dry run - would update" }
|
|
15862
|
+
);
|
|
15863
|
+
if (input.include_pre_compact) {
|
|
15864
|
+
hooksResults.push({ editor, file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
|
|
15865
|
+
}
|
|
15866
|
+
} else if (editor === "cline") {
|
|
15867
|
+
hooksResults.push(
|
|
15868
|
+
{ editor, file: "~/Documents/Cline/Rules/Hooks/PreToolUse", status: "dry run - would create" },
|
|
15869
|
+
{ editor, file: "~/Documents/Cline/Rules/Hooks/UserPromptSubmit", status: "dry run - would create" }
|
|
15870
|
+
);
|
|
15871
|
+
} else if (editor === "roo") {
|
|
15872
|
+
hooksResults.push(
|
|
15873
|
+
{ editor, file: "~/.roo/hooks/PreToolUse", status: "dry run - would create" },
|
|
15874
|
+
{ editor, file: "~/.roo/hooks/UserPromptSubmit", status: "dry run - would create" }
|
|
15875
|
+
);
|
|
15876
|
+
} else if (editor === "kilo") {
|
|
15877
|
+
hooksResults.push(
|
|
15878
|
+
{ editor, file: "~/.kilocode/hooks/PreToolUse", status: "dry run - would create" },
|
|
15879
|
+
{ editor, file: "~/.kilocode/hooks/UserPromptSubmit", status: "dry run - would create" }
|
|
15880
|
+
);
|
|
15881
|
+
} else if (editor === "cursor") {
|
|
15882
|
+
hooksResults.push(
|
|
15883
|
+
{ editor, file: "~/.cursor/hooks/contextstream-pretooluse.py", status: "dry run - would create" },
|
|
15884
|
+
{ editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
|
|
15885
|
+
{ editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
|
|
15886
|
+
);
|
|
15887
|
+
}
|
|
15035
15888
|
}
|
|
15036
15889
|
} else {
|
|
15037
|
-
|
|
15038
|
-
|
|
15890
|
+
hooksResults = [];
|
|
15891
|
+
const allHookResults = await installAllEditorHooks({
|
|
15892
|
+
scope: "global",
|
|
15893
|
+
editors: hookSupportedEditors,
|
|
15039
15894
|
includePreCompact: input.include_pre_compact
|
|
15040
15895
|
});
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15896
|
+
for (const result of allHookResults) {
|
|
15897
|
+
for (const file of result.installed) {
|
|
15898
|
+
hooksResults.push({ editor: result.editor, file, status: "created" });
|
|
15899
|
+
}
|
|
15900
|
+
}
|
|
15045
15901
|
}
|
|
15046
15902
|
} catch (err) {
|
|
15047
15903
|
hooksResults = [{ file: "hooks", status: `error: ${err.message}` }];
|
|
15048
15904
|
}
|
|
15049
|
-
} else if (
|
|
15050
|
-
hooksPrompt = "Hooks skipped.
|
|
15905
|
+
} else if (hookSupportedEditors.length > 0 && input.install_hooks === false) {
|
|
15906
|
+
hooksPrompt = "Hooks skipped. AI may use default tools instead of ContextStream search.";
|
|
15051
15907
|
}
|
|
15052
15908
|
const summary = {
|
|
15053
15909
|
folder: folderPath,
|
|
@@ -15069,7 +15925,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
15069
15925
|
"generate_editor_rules",
|
|
15070
15926
|
{
|
|
15071
15927
|
title: "Generate editor AI rules",
|
|
15072
|
-
description: `Generate AI rule files for editors (
|
|
15928
|
+
description: `Generate AI rule files for editors (Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
|
|
15073
15929
|
These rules instruct the AI to automatically use ContextStream for memory and context.
|
|
15074
15930
|
Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
15075
15931
|
inputSchema: external_exports.object({
|
|
@@ -15077,7 +15933,6 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
15077
15933
|
editors: external_exports.array(
|
|
15078
15934
|
external_exports.enum([
|
|
15079
15935
|
"codex",
|
|
15080
|
-
"windsurf",
|
|
15081
15936
|
"cursor",
|
|
15082
15937
|
"cline",
|
|
15083
15938
|
"kilo",
|
|
@@ -15426,6 +16281,52 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
15426
16281
|
}
|
|
15427
16282
|
sessionManager.addTokens(input.user_message);
|
|
15428
16283
|
}
|
|
16284
|
+
let postCompactContext = "";
|
|
16285
|
+
let postCompactRestored = false;
|
|
16286
|
+
if (sessionManager && sessionManager.shouldRestorePostCompact() && workspaceId) {
|
|
16287
|
+
try {
|
|
16288
|
+
const listResult = await client.listMemoryEvents({
|
|
16289
|
+
workspace_id: workspaceId,
|
|
16290
|
+
project_id: projectId,
|
|
16291
|
+
limit: 20
|
|
16292
|
+
});
|
|
16293
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
16294
|
+
const snapshotEvent = allEvents.find(
|
|
16295
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.tags?.includes("session_snapshot")
|
|
16296
|
+
);
|
|
16297
|
+
if (snapshotEvent && snapshotEvent.content) {
|
|
16298
|
+
let snapshotData;
|
|
16299
|
+
try {
|
|
16300
|
+
snapshotData = JSON.parse(snapshotEvent.content);
|
|
16301
|
+
} catch {
|
|
16302
|
+
snapshotData = { conversation_summary: snapshotEvent.content };
|
|
16303
|
+
}
|
|
16304
|
+
const summary = snapshotData.conversation_summary || snapshotData.summary || "";
|
|
16305
|
+
const decisions = snapshotData.key_decisions || [];
|
|
16306
|
+
const unfinished = snapshotData.unfinished_work || snapshotData.pending_tasks || [];
|
|
16307
|
+
const files = snapshotData.active_files || [];
|
|
16308
|
+
const parts = [];
|
|
16309
|
+
parts.push("\u{1F4CB} [POST-COMPACTION CONTEXT RESTORED]");
|
|
16310
|
+
if (summary) parts.push(`Summary: ${summary}`);
|
|
16311
|
+
if (Array.isArray(decisions) && decisions.length > 0) {
|
|
16312
|
+
parts.push(`Decisions: ${decisions.slice(0, 5).join("; ")}`);
|
|
16313
|
+
}
|
|
16314
|
+
if (Array.isArray(unfinished) && unfinished.length > 0) {
|
|
16315
|
+
parts.push(`Unfinished: ${unfinished.slice(0, 3).join("; ")}`);
|
|
16316
|
+
}
|
|
16317
|
+
if (Array.isArray(files) && files.length > 0) {
|
|
16318
|
+
parts.push(`Active files: ${files.slice(0, 5).join(", ")}`);
|
|
16319
|
+
}
|
|
16320
|
+
parts.push("---");
|
|
16321
|
+
postCompactContext = parts.join("\n") + "\n\n";
|
|
16322
|
+
postCompactRestored = true;
|
|
16323
|
+
sessionManager.markPostCompactRestoreCompleted();
|
|
16324
|
+
logSystem("context restored");
|
|
16325
|
+
}
|
|
16326
|
+
} catch (err) {
|
|
16327
|
+
logDebug(`Failed to restore post-compact context: ${err}`);
|
|
16328
|
+
}
|
|
16329
|
+
}
|
|
15429
16330
|
const result = await client.getSmartContext({
|
|
15430
16331
|
user_message: input.user_message,
|
|
15431
16332
|
workspace_id: workspaceId,
|
|
@@ -15466,8 +16367,44 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
15466
16367
|
project_id: projectId,
|
|
15467
16368
|
max_tokens: input.max_tokens
|
|
15468
16369
|
});
|
|
15469
|
-
|
|
15470
|
-
const
|
|
16370
|
+
let lessonsWarningLine = "";
|
|
16371
|
+
const riskyKeywords = detectRiskyActions(input.user_message);
|
|
16372
|
+
if (riskyKeywords.length > 0 && workspaceId) {
|
|
16373
|
+
try {
|
|
16374
|
+
const lessons = await client.getHighPriorityLessons({
|
|
16375
|
+
workspace_id: workspaceId,
|
|
16376
|
+
project_id: projectId,
|
|
16377
|
+
context_hint: riskyKeywords.join(" "),
|
|
16378
|
+
limit: 5
|
|
16379
|
+
});
|
|
16380
|
+
if (lessons.length > 0) {
|
|
16381
|
+
const lessonLines = lessons.slice(0, 5).map((l, i) => {
|
|
16382
|
+
const severity = l.severity === "critical" ? "\u{1F6A8}" : l.severity === "high" ? "\u26A0\uFE0F" : "\u{1F4DD}";
|
|
16383
|
+
const title = l.title || "Untitled lesson";
|
|
16384
|
+
const prevention = l.prevention || "";
|
|
16385
|
+
return `${i + 1}. ${severity} ${title}${prevention ? `: ${prevention.slice(0, 100)}` : ""}`;
|
|
16386
|
+
});
|
|
16387
|
+
lessonsWarningLine = `
|
|
16388
|
+
|
|
16389
|
+
\u{1F6A8} [LESSONS_WARNING] Relevant Lessons for "${riskyKeywords.slice(0, 3).join(", ")}"
|
|
16390
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
16391
|
+
\u26A0\uFE0F IMPORTANT: You MUST tell the user about these lessons before proceeding.
|
|
16392
|
+
These are past mistakes that may be relevant to the current task.
|
|
16393
|
+
|
|
16394
|
+
${lessonLines.join("\n")}
|
|
16395
|
+
|
|
16396
|
+
Action: Review each lesson and explain to the user how you will avoid these mistakes.
|
|
16397
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
|
|
16398
|
+
}
|
|
16399
|
+
} catch {
|
|
16400
|
+
}
|
|
16401
|
+
}
|
|
16402
|
+
if (!lessonsWarningLine) {
|
|
16403
|
+
const hasLessonsInContext = result.context.includes("|L:") || result.context.includes("L:") || result.context.toLowerCase().includes("lesson");
|
|
16404
|
+
if (hasLessonsInContext) {
|
|
16405
|
+
lessonsWarningLine = "\n\n\u26A0\uFE0F [LESSONS_WARNING] Lessons found in context - review the L: items above before making changes.";
|
|
16406
|
+
}
|
|
16407
|
+
}
|
|
15471
16408
|
const searchRulesLine = SEARCH_RULES_REMINDER_ENABLED ? `
|
|
15472
16409
|
|
|
15473
16410
|
${SEARCH_RULES_REMINDER}` : "";
|
|
@@ -15475,12 +16412,18 @@ ${SEARCH_RULES_REMINDER}` : "";
|
|
|
15475
16412
|
if (result.context_pressure) {
|
|
15476
16413
|
const cp = result.context_pressure;
|
|
15477
16414
|
if (cp.level === "critical") {
|
|
16415
|
+
if (sessionManager) {
|
|
16416
|
+
sessionManager.markHighContextPressure();
|
|
16417
|
+
}
|
|
15478
16418
|
contextPressureWarning = `
|
|
15479
16419
|
|
|
15480
16420
|
\u{1F6A8} [CONTEXT PRESSURE: CRITICAL] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
15481
16421
|
Action: ${cp.suggested_action === "save_now" ? 'SAVE STATE NOW - Call session(action="capture") to preserve conversation state before compaction.' : cp.suggested_action}
|
|
15482
16422
|
The conversation may compact soon. Save important decisions, insights, and progress immediately.`;
|
|
15483
16423
|
} else if (cp.level === "high") {
|
|
16424
|
+
if (sessionManager) {
|
|
16425
|
+
sessionManager.markHighContextPressure();
|
|
16426
|
+
}
|
|
15484
16427
|
contextPressureWarning = `
|
|
15485
16428
|
|
|
15486
16429
|
\u26A0\uFE0F [CONTEXT PRESSURE: HIGH] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
@@ -15498,14 +16441,16 @@ ${versionWarningLine}` : "",
|
|
|
15498
16441
|
contextPressureWarning,
|
|
15499
16442
|
searchRulesLine
|
|
15500
16443
|
].filter(Boolean).join("");
|
|
16444
|
+
const finalContext = postCompactContext + result.context;
|
|
16445
|
+
const enrichedResultWithRestore = postCompactRestored ? { ...enrichedResult, post_compact_restored: true } : enrichedResult;
|
|
15501
16446
|
return {
|
|
15502
16447
|
content: [
|
|
15503
16448
|
{
|
|
15504
16449
|
type: "text",
|
|
15505
|
-
text:
|
|
16450
|
+
text: finalContext + footer + allWarnings
|
|
15506
16451
|
}
|
|
15507
16452
|
],
|
|
15508
|
-
structuredContent: toStructured(
|
|
16453
|
+
structuredContent: toStructured(enrichedResultWithRestore)
|
|
15509
16454
|
};
|
|
15510
16455
|
}
|
|
15511
16456
|
);
|
|
@@ -16574,7 +17519,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16574
17519
|
"session",
|
|
16575
17520
|
{
|
|
16576
17521
|
title: "Session",
|
|
16577
|
-
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
17522
|
+
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
16578
17523
|
inputSchema: external_exports.object({
|
|
16579
17524
|
action: external_exports.enum([
|
|
16580
17525
|
"capture",
|
|
@@ -16592,7 +17537,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16592
17537
|
"capture_plan",
|
|
16593
17538
|
"get_plan",
|
|
16594
17539
|
"update_plan",
|
|
16595
|
-
"list_plans"
|
|
17540
|
+
"list_plans",
|
|
17541
|
+
// Context restore
|
|
17542
|
+
"restore_context"
|
|
16596
17543
|
]).describe("Action to perform"),
|
|
16597
17544
|
workspace_id: external_exports.string().uuid().optional(),
|
|
16598
17545
|
project_id: external_exports.string().uuid().optional(),
|
|
@@ -16614,7 +17561,8 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16614
17561
|
"lesson",
|
|
16615
17562
|
"warning",
|
|
16616
17563
|
"frustration",
|
|
16617
|
-
"conversation"
|
|
17564
|
+
"conversation",
|
|
17565
|
+
"session_snapshot"
|
|
16618
17566
|
]).optional().describe("Event type for capture"),
|
|
16619
17567
|
importance: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
|
|
16620
17568
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
@@ -16664,7 +17612,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16664
17612
|
status: external_exports.enum(["draft", "active", "completed", "archived", "abandoned"]).optional().describe("Plan status"),
|
|
16665
17613
|
due_at: external_exports.string().optional().describe("Due date for plan (ISO timestamp)"),
|
|
16666
17614
|
source_tool: external_exports.string().optional().describe("Tool that generated this plan"),
|
|
16667
|
-
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan")
|
|
17615
|
+
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan"),
|
|
17616
|
+
// Restore context params
|
|
17617
|
+
snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
|
|
17618
|
+
max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
|
|
16668
17619
|
})
|
|
16669
17620
|
},
|
|
16670
17621
|
async (input) => {
|
|
@@ -16970,6 +17921,143 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16970
17921
|
structuredContent: toStructured(result)
|
|
16971
17922
|
};
|
|
16972
17923
|
}
|
|
17924
|
+
case "restore_context": {
|
|
17925
|
+
if (!workspaceId) {
|
|
17926
|
+
return errorResult(
|
|
17927
|
+
"restore_context requires workspace_id. Call session_init first."
|
|
17928
|
+
);
|
|
17929
|
+
}
|
|
17930
|
+
if (input.snapshot_id) {
|
|
17931
|
+
const eventResult = await client.getEvent(input.snapshot_id);
|
|
17932
|
+
const event = eventResult?.data || eventResult;
|
|
17933
|
+
if (!event || !event.content) {
|
|
17934
|
+
return errorResult(
|
|
17935
|
+
`Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
|
|
17936
|
+
);
|
|
17937
|
+
}
|
|
17938
|
+
let snapshotData;
|
|
17939
|
+
try {
|
|
17940
|
+
snapshotData = JSON.parse(event.content);
|
|
17941
|
+
} catch {
|
|
17942
|
+
snapshotData = { conversation_summary: event.content };
|
|
17943
|
+
}
|
|
17944
|
+
const sessionId = snapshotData.session_id || event.session_id;
|
|
17945
|
+
const sessionLinking2 = {};
|
|
17946
|
+
if (sessionId) {
|
|
17947
|
+
sessionLinking2.previous_session_id = sessionId;
|
|
17948
|
+
const workingOn = [];
|
|
17949
|
+
const activeFiles = snapshotData.active_files;
|
|
17950
|
+
const lastTools = snapshotData.last_tools;
|
|
17951
|
+
if (activeFiles && activeFiles.length > 0) {
|
|
17952
|
+
workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
|
|
17953
|
+
}
|
|
17954
|
+
if (lastTools && lastTools.length > 0) {
|
|
17955
|
+
const toolCounts = lastTools.reduce((acc, tool) => {
|
|
17956
|
+
acc[tool] = (acc[tool] || 0) + 1;
|
|
17957
|
+
return acc;
|
|
17958
|
+
}, {});
|
|
17959
|
+
const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
|
|
17960
|
+
workingOn.push(`Recent tools: ${topTools.join(", ")}`);
|
|
17961
|
+
}
|
|
17962
|
+
if (workingOn.length > 0) {
|
|
17963
|
+
sessionLinking2.previous_session_summary = workingOn.join("; ");
|
|
17964
|
+
}
|
|
17965
|
+
}
|
|
17966
|
+
const response2 = {
|
|
17967
|
+
restored: true,
|
|
17968
|
+
snapshot_id: event.id,
|
|
17969
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
17970
|
+
session_linking: Object.keys(sessionLinking2).length > 0 ? sessionLinking2 : void 0,
|
|
17971
|
+
...snapshotData,
|
|
17972
|
+
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."
|
|
17973
|
+
};
|
|
17974
|
+
return {
|
|
17975
|
+
content: [{ type: "text", text: formatContent(response2) }],
|
|
17976
|
+
structuredContent: toStructured(response2)
|
|
17977
|
+
};
|
|
17978
|
+
}
|
|
17979
|
+
const listResult = await client.listMemoryEvents({
|
|
17980
|
+
workspace_id: workspaceId,
|
|
17981
|
+
project_id: projectId,
|
|
17982
|
+
limit: 50
|
|
17983
|
+
// Fetch more to filter
|
|
17984
|
+
});
|
|
17985
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
17986
|
+
const snapshotEvents = allEvents.filter(
|
|
17987
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
17988
|
+
).slice(0, input.max_snapshots || 1);
|
|
17989
|
+
if (!snapshotEvents || snapshotEvents.length === 0) {
|
|
17990
|
+
return {
|
|
17991
|
+
content: [
|
|
17992
|
+
{
|
|
17993
|
+
type: "text",
|
|
17994
|
+
text: formatContent({
|
|
17995
|
+
restored: false,
|
|
17996
|
+
message: "No session snapshots found. Use session_capture_smart to save state before compaction.",
|
|
17997
|
+
hint: "Start fresh or use session_init to get recent context."
|
|
17998
|
+
})
|
|
17999
|
+
}
|
|
18000
|
+
]
|
|
18001
|
+
};
|
|
18002
|
+
}
|
|
18003
|
+
const snapshots = snapshotEvents.map((event) => {
|
|
18004
|
+
let snapshotData;
|
|
18005
|
+
try {
|
|
18006
|
+
snapshotData = JSON.parse(event.content || "{}");
|
|
18007
|
+
} catch {
|
|
18008
|
+
snapshotData = { conversation_summary: event.content };
|
|
18009
|
+
}
|
|
18010
|
+
return {
|
|
18011
|
+
snapshot_id: event.id,
|
|
18012
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
18013
|
+
session_id: snapshotData.session_id || event.session_id,
|
|
18014
|
+
...snapshotData
|
|
18015
|
+
};
|
|
18016
|
+
});
|
|
18017
|
+
const latestSnapshot = snapshots[0];
|
|
18018
|
+
const sessionLinking = {};
|
|
18019
|
+
if (latestSnapshot?.session_id) {
|
|
18020
|
+
sessionLinking.previous_session_id = latestSnapshot.session_id;
|
|
18021
|
+
const workingOn = [];
|
|
18022
|
+
const activeFiles = latestSnapshot.active_files;
|
|
18023
|
+
const lastTools = latestSnapshot.last_tools;
|
|
18024
|
+
if (activeFiles && activeFiles.length > 0) {
|
|
18025
|
+
workingOn.push(`Files: ${activeFiles.slice(0, 5).join(", ")}${activeFiles.length > 5 ? ` (+${activeFiles.length - 5} more)` : ""}`);
|
|
18026
|
+
}
|
|
18027
|
+
if (lastTools && lastTools.length > 0) {
|
|
18028
|
+
const toolCounts = lastTools.reduce((acc, tool) => {
|
|
18029
|
+
acc[tool] = (acc[tool] || 0) + 1;
|
|
18030
|
+
return acc;
|
|
18031
|
+
}, {});
|
|
18032
|
+
const topTools = Object.entries(toolCounts).sort(([, a], [, b]) => b - a).slice(0, 3).map(([tool]) => tool);
|
|
18033
|
+
workingOn.push(`Recent tools: ${topTools.join(", ")}`);
|
|
18034
|
+
}
|
|
18035
|
+
if (workingOn.length > 0) {
|
|
18036
|
+
sessionLinking.previous_session_summary = workingOn.join("; ");
|
|
18037
|
+
}
|
|
18038
|
+
const relatedSessionIds = /* @__PURE__ */ new Set();
|
|
18039
|
+
snapshots.forEach((s) => {
|
|
18040
|
+
if (s.session_id && s.session_id !== latestSnapshot.session_id) {
|
|
18041
|
+
relatedSessionIds.add(s.session_id);
|
|
18042
|
+
}
|
|
18043
|
+
});
|
|
18044
|
+
if (relatedSessionIds.size > 0) {
|
|
18045
|
+
sessionLinking.related_sessions = Array.from(relatedSessionIds);
|
|
18046
|
+
}
|
|
18047
|
+
}
|
|
18048
|
+
const response = {
|
|
18049
|
+
restored: true,
|
|
18050
|
+
snapshots_found: snapshots.length,
|
|
18051
|
+
latest: snapshots[0],
|
|
18052
|
+
all_snapshots: snapshots.length > 1 ? snapshots : void 0,
|
|
18053
|
+
session_linking: Object.keys(sessionLinking).length > 0 ? sessionLinking : void 0,
|
|
18054
|
+
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."
|
|
18055
|
+
};
|
|
18056
|
+
return {
|
|
18057
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
18058
|
+
structuredContent: toStructured(response)
|
|
18059
|
+
};
|
|
18060
|
+
}
|
|
16973
18061
|
default:
|
|
16974
18062
|
return errorResult(`Unknown action: ${input.action}`);
|
|
16975
18063
|
}
|
|
@@ -18673,9 +19761,7 @@ Each domain tool has an 'action' parameter for specific operations.` : "";
|
|
|
18673
19761
|
}
|
|
18674
19762
|
}
|
|
18675
19763
|
);
|
|
18676
|
-
|
|
18677
|
-
`[ContextStream] Consolidated mode: Registered ${CONSOLIDATED_TOOLS.size} domain tools.`
|
|
18678
|
-
);
|
|
19764
|
+
logDebug(`Consolidated mode: ${CONSOLIDATED_TOOLS.size} domain tools`);
|
|
18679
19765
|
}
|
|
18680
19766
|
}
|
|
18681
19767
|
function registerLimitedTools(server) {
|
|
@@ -18710,7 +19796,7 @@ After setup, restart your editor to enable all ContextStream tools.`
|
|
|
18710
19796
|
};
|
|
18711
19797
|
}
|
|
18712
19798
|
);
|
|
18713
|
-
|
|
19799
|
+
logSystem("limited mode (run setup)");
|
|
18714
19800
|
}
|
|
18715
19801
|
|
|
18716
19802
|
// src/resources.ts
|
|
@@ -19492,7 +20578,7 @@ function registerPrompts(server) {
|
|
|
19492
20578
|
"First:",
|
|
19493
20579
|
"- If you can infer the project folder path from the environment/IDE roots, use it.",
|
|
19494
20580
|
"- Otherwise ask me for an absolute folder path.",
|
|
19495
|
-
"- Ask which editor(s) (codex,
|
|
20581
|
+
"- Ask which editor(s) (codex,cursor,cline,kilo,roo,claude,aider,antigravity) or default to all.",
|
|
19496
20582
|
"",
|
|
19497
20583
|
"Then call `generate_rules` and confirm which files were created/updated.",
|
|
19498
20584
|
"Ask if the user also wants to apply rules globally (pass apply_global: true)."
|
|
@@ -19534,8 +20620,7 @@ function registerPrompts(server) {
|
|
|
19534
20620
|
}
|
|
19535
20621
|
|
|
19536
20622
|
// src/session-manager.ts
|
|
19537
|
-
var SessionManager = class {
|
|
19538
|
-
// Conservative default for 100k context window
|
|
20623
|
+
var SessionManager = class _SessionManager {
|
|
19539
20624
|
constructor(server, client) {
|
|
19540
20625
|
this.server = server;
|
|
19541
20626
|
this.client = client;
|
|
@@ -19547,8 +20632,30 @@ var SessionManager = class {
|
|
|
19547
20632
|
this.contextSmartCalled = false;
|
|
19548
20633
|
this.warningShown = false;
|
|
19549
20634
|
// Token tracking for context pressure calculation
|
|
20635
|
+
// Note: MCP servers cannot see actual token usage (AI responses, thinking, system prompts).
|
|
20636
|
+
// We use a heuristic: tracked tokens + (turns * estimated tokens per turn)
|
|
19550
20637
|
this.sessionTokens = 0;
|
|
19551
20638
|
this.contextThreshold = 7e4;
|
|
20639
|
+
// Conservative default for 100k context window
|
|
20640
|
+
this.conversationTurns = 0;
|
|
20641
|
+
// Continuous checkpointing
|
|
20642
|
+
this.toolCallCount = 0;
|
|
20643
|
+
this.checkpointInterval = 20;
|
|
20644
|
+
// Save checkpoint every N tool calls
|
|
20645
|
+
this.lastCheckpointAt = 0;
|
|
20646
|
+
this.activeFiles = /* @__PURE__ */ new Set();
|
|
20647
|
+
this.recentToolCalls = [];
|
|
20648
|
+
this.checkpointEnabled = process.env.CONTEXTSTREAM_CHECKPOINT_ENABLED?.toLowerCase() === "true";
|
|
20649
|
+
// Post-compaction restoration tracking
|
|
20650
|
+
// Tracks when context pressure was high/critical so we can detect post-compaction state
|
|
20651
|
+
this.lastHighPressureAt = null;
|
|
20652
|
+
this.lastHighPressureTokens = 0;
|
|
20653
|
+
this.postCompactRestoreCompleted = false;
|
|
20654
|
+
}
|
|
20655
|
+
static {
|
|
20656
|
+
// Each conversation turn typically includes: user message (~500), AI response (~1500),
|
|
20657
|
+
// system prompt overhead (~500), and reasoning (~1500). Conservative estimate: 3000/turn
|
|
20658
|
+
this.TOKENS_PER_TURN_ESTIMATE = 3e3;
|
|
19552
20659
|
}
|
|
19553
20660
|
/**
|
|
19554
20661
|
* Check if session has been auto-initialized
|
|
@@ -19591,17 +20698,40 @@ var SessionManager = class {
|
|
|
19591
20698
|
this.folderPath = path9;
|
|
19592
20699
|
}
|
|
19593
20700
|
/**
|
|
19594
|
-
* Mark that context_smart has been called in this session
|
|
20701
|
+
* Mark that context_smart has been called in this session.
|
|
20702
|
+
* Also increments the conversation turn counter for token estimation.
|
|
19595
20703
|
*/
|
|
19596
20704
|
markContextSmartCalled() {
|
|
19597
20705
|
this.contextSmartCalled = true;
|
|
20706
|
+
this.conversationTurns++;
|
|
19598
20707
|
}
|
|
19599
20708
|
/**
|
|
19600
20709
|
* Get current session token count for context pressure calculation.
|
|
20710
|
+
*
|
|
20711
|
+
* This returns an ESTIMATED count based on:
|
|
20712
|
+
* 1. Tokens tracked through ContextStream tools (actual)
|
|
20713
|
+
* 2. Estimated tokens per conversation turn (heuristic)
|
|
20714
|
+
*
|
|
20715
|
+
* Note: MCP servers cannot see actual AI token usage (responses, thinking,
|
|
20716
|
+
* system prompts). This estimate helps provide a more realistic context
|
|
20717
|
+
* pressure signal.
|
|
19601
20718
|
*/
|
|
19602
20719
|
getSessionTokens() {
|
|
20720
|
+
const turnEstimate = this.conversationTurns * _SessionManager.TOKENS_PER_TURN_ESTIMATE;
|
|
20721
|
+
return this.sessionTokens + turnEstimate;
|
|
20722
|
+
}
|
|
20723
|
+
/**
|
|
20724
|
+
* Get the raw tracked tokens (without turn-based estimation).
|
|
20725
|
+
*/
|
|
20726
|
+
getRawTrackedTokens() {
|
|
19603
20727
|
return this.sessionTokens;
|
|
19604
20728
|
}
|
|
20729
|
+
/**
|
|
20730
|
+
* Get the current conversation turn count.
|
|
20731
|
+
*/
|
|
20732
|
+
getConversationTurns() {
|
|
20733
|
+
return this.conversationTurns;
|
|
20734
|
+
}
|
|
19605
20735
|
/**
|
|
19606
20736
|
* Get the context threshold (max tokens before compaction warning).
|
|
19607
20737
|
*/
|
|
@@ -19640,6 +20770,52 @@ var SessionManager = class {
|
|
|
19640
20770
|
*/
|
|
19641
20771
|
resetTokenCount() {
|
|
19642
20772
|
this.sessionTokens = 0;
|
|
20773
|
+
this.conversationTurns = 0;
|
|
20774
|
+
}
|
|
20775
|
+
/**
|
|
20776
|
+
* Record that context pressure is high/critical.
|
|
20777
|
+
* Called when context_smart returns high or critical pressure level.
|
|
20778
|
+
*/
|
|
20779
|
+
markHighContextPressure() {
|
|
20780
|
+
this.lastHighPressureAt = Date.now();
|
|
20781
|
+
this.lastHighPressureTokens = this.getSessionTokens();
|
|
20782
|
+
}
|
|
20783
|
+
/**
|
|
20784
|
+
* Check if we should attempt post-compaction restoration.
|
|
20785
|
+
*
|
|
20786
|
+
* Detection heuristic:
|
|
20787
|
+
* 1. We recorded high/critical context pressure recently (within 10 minutes)
|
|
20788
|
+
* 2. Current token count is very low (< 5000) compared to when pressure was high
|
|
20789
|
+
* 3. We haven't already restored in this session
|
|
20790
|
+
*
|
|
20791
|
+
* This indicates compaction likely happened and we should restore context.
|
|
20792
|
+
*/
|
|
20793
|
+
shouldRestorePostCompact() {
|
|
20794
|
+
if (this.postCompactRestoreCompleted) {
|
|
20795
|
+
return false;
|
|
20796
|
+
}
|
|
20797
|
+
if (!this.lastHighPressureAt) {
|
|
20798
|
+
return false;
|
|
20799
|
+
}
|
|
20800
|
+
const elapsed = Date.now() - this.lastHighPressureAt;
|
|
20801
|
+
if (elapsed > 10 * 60 * 1e3) {
|
|
20802
|
+
return false;
|
|
20803
|
+
}
|
|
20804
|
+
const currentTokens = this.getSessionTokens();
|
|
20805
|
+
const tokenDrop = this.lastHighPressureTokens - currentTokens;
|
|
20806
|
+
if (currentTokens > 1e4 || tokenDrop < this.lastHighPressureTokens * 0.5) {
|
|
20807
|
+
return false;
|
|
20808
|
+
}
|
|
20809
|
+
return true;
|
|
20810
|
+
}
|
|
20811
|
+
/**
|
|
20812
|
+
* Mark post-compaction restoration as completed.
|
|
20813
|
+
* Prevents multiple restoration attempts in the same session.
|
|
20814
|
+
*/
|
|
20815
|
+
markPostCompactRestoreCompleted() {
|
|
20816
|
+
this.postCompactRestoreCompleted = true;
|
|
20817
|
+
this.lastHighPressureAt = null;
|
|
20818
|
+
this.lastHighPressureTokens = 0;
|
|
19643
20819
|
}
|
|
19644
20820
|
/**
|
|
19645
20821
|
* Check if context_smart has been called and warn if not.
|
|
@@ -19905,6 +21081,112 @@ var SessionManager = class {
|
|
|
19905
21081
|
parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
19906
21082
|
return parts.join("\n");
|
|
19907
21083
|
}
|
|
21084
|
+
// =========================================================================
|
|
21085
|
+
// Continuous Checkpointing
|
|
21086
|
+
// =========================================================================
|
|
21087
|
+
/**
|
|
21088
|
+
* Track a tool call for checkpointing purposes.
|
|
21089
|
+
* Call this after each tool execution to track files and trigger periodic checkpoints.
|
|
21090
|
+
*/
|
|
21091
|
+
trackToolCall(toolName, input) {
|
|
21092
|
+
this.toolCallCount++;
|
|
21093
|
+
this.recentToolCalls.push({ name: toolName, timestamp: Date.now() });
|
|
21094
|
+
if (this.recentToolCalls.length > 50) {
|
|
21095
|
+
this.recentToolCalls = this.recentToolCalls.slice(-50);
|
|
21096
|
+
}
|
|
21097
|
+
if (input) {
|
|
21098
|
+
const filePath = input.file_path || input.notebook_path || input.path;
|
|
21099
|
+
if (filePath && typeof filePath === "string") {
|
|
21100
|
+
this.activeFiles.add(filePath);
|
|
21101
|
+
if (this.activeFiles.size > 30) {
|
|
21102
|
+
const arr = Array.from(this.activeFiles);
|
|
21103
|
+
this.activeFiles = new Set(arr.slice(-30));
|
|
21104
|
+
}
|
|
21105
|
+
}
|
|
21106
|
+
}
|
|
21107
|
+
this.maybeCheckpoint();
|
|
21108
|
+
}
|
|
21109
|
+
/**
|
|
21110
|
+
* Save a checkpoint if the interval has been reached.
|
|
21111
|
+
*/
|
|
21112
|
+
async maybeCheckpoint() {
|
|
21113
|
+
if (!this.checkpointEnabled || !this.initialized || !this.context) {
|
|
21114
|
+
return;
|
|
21115
|
+
}
|
|
21116
|
+
const callsSinceLastCheckpoint = this.toolCallCount - this.lastCheckpointAt;
|
|
21117
|
+
if (callsSinceLastCheckpoint < this.checkpointInterval) {
|
|
21118
|
+
return;
|
|
21119
|
+
}
|
|
21120
|
+
this.lastCheckpointAt = this.toolCallCount;
|
|
21121
|
+
await this.saveCheckpoint("periodic");
|
|
21122
|
+
}
|
|
21123
|
+
/**
|
|
21124
|
+
* Get the list of active files being worked on.
|
|
21125
|
+
*/
|
|
21126
|
+
getActiveFiles() {
|
|
21127
|
+
return Array.from(this.activeFiles);
|
|
21128
|
+
}
|
|
21129
|
+
/**
|
|
21130
|
+
* Get recent tool call names.
|
|
21131
|
+
*/
|
|
21132
|
+
getRecentToolNames() {
|
|
21133
|
+
return this.recentToolCalls.map((t) => t.name);
|
|
21134
|
+
}
|
|
21135
|
+
/**
|
|
21136
|
+
* Get the current tool call count.
|
|
21137
|
+
*/
|
|
21138
|
+
getToolCallCount() {
|
|
21139
|
+
return this.toolCallCount;
|
|
21140
|
+
}
|
|
21141
|
+
/**
|
|
21142
|
+
* Save a checkpoint snapshot to ContextStream.
|
|
21143
|
+
*/
|
|
21144
|
+
async saveCheckpoint(trigger) {
|
|
21145
|
+
if (!this.initialized || !this.context) {
|
|
21146
|
+
return false;
|
|
21147
|
+
}
|
|
21148
|
+
const workspaceId = this.context.workspace_id;
|
|
21149
|
+
if (!workspaceId) {
|
|
21150
|
+
return false;
|
|
21151
|
+
}
|
|
21152
|
+
const checkpointData = {
|
|
21153
|
+
trigger,
|
|
21154
|
+
checkpoint_number: Math.floor(this.toolCallCount / this.checkpointInterval),
|
|
21155
|
+
tool_call_count: this.toolCallCount,
|
|
21156
|
+
session_tokens: this.sessionTokens,
|
|
21157
|
+
active_files: this.getActiveFiles(),
|
|
21158
|
+
recent_tools: this.getRecentToolNames().slice(-10),
|
|
21159
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21160
|
+
auto_captured: true
|
|
21161
|
+
};
|
|
21162
|
+
try {
|
|
21163
|
+
await this.client.captureContext({
|
|
21164
|
+
workspace_id: workspaceId,
|
|
21165
|
+
project_id: this.context.project_id,
|
|
21166
|
+
event_type: "session_snapshot",
|
|
21167
|
+
title: `Checkpoint #${checkpointData.checkpoint_number} (${trigger})`,
|
|
21168
|
+
content: JSON.stringify(checkpointData),
|
|
21169
|
+
importance: trigger === "periodic" ? "low" : "medium",
|
|
21170
|
+
tags: ["session_snapshot", "checkpoint", trigger]
|
|
21171
|
+
});
|
|
21172
|
+
return true;
|
|
21173
|
+
} catch (err) {
|
|
21174
|
+
console.error("[ContextStream] Failed to save checkpoint:", err);
|
|
21175
|
+
return false;
|
|
21176
|
+
}
|
|
21177
|
+
}
|
|
21178
|
+
/**
|
|
21179
|
+
* Enable or disable continuous checkpointing.
|
|
21180
|
+
*/
|
|
21181
|
+
setCheckpointEnabled(enabled) {
|
|
21182
|
+
this.checkpointEnabled = enabled;
|
|
21183
|
+
}
|
|
21184
|
+
/**
|
|
21185
|
+
* Set the checkpoint interval (tool calls between checkpoints).
|
|
21186
|
+
*/
|
|
21187
|
+
setCheckpointInterval(interval) {
|
|
21188
|
+
this.checkpointInterval = Math.max(5, interval);
|
|
21189
|
+
}
|
|
19908
21190
|
};
|
|
19909
21191
|
|
|
19910
21192
|
// src/http-gateway.ts
|
|
@@ -20268,7 +21550,6 @@ var EDITOR_LABELS = {
|
|
|
20268
21550
|
codex: "Codex CLI",
|
|
20269
21551
|
claude: "Claude Code",
|
|
20270
21552
|
cursor: "Cursor / VS Code",
|
|
20271
|
-
windsurf: "Windsurf",
|
|
20272
21553
|
cline: "Cline",
|
|
20273
21554
|
kilo: "Kilo Code",
|
|
20274
21555
|
roo: "Roo Code",
|
|
@@ -20347,7 +21628,6 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
|
20347
21628
|
/^#\s+codex cli instructions$/i,
|
|
20348
21629
|
/^#\s+claude code instructions$/i,
|
|
20349
21630
|
/^#\s+cursor rules$/i,
|
|
20350
|
-
/^#\s+windsurf rules$/i,
|
|
20351
21631
|
/^#\s+cline rules$/i,
|
|
20352
21632
|
/^#\s+kilo code rules$/i,
|
|
20353
21633
|
/^#\s+roo code rules$/i,
|
|
@@ -20489,8 +21769,6 @@ function globalRulesPathForEditor(editor) {
|
|
|
20489
21769
|
return path8.join(home, ".codex", "AGENTS.md");
|
|
20490
21770
|
case "claude":
|
|
20491
21771
|
return path8.join(home, ".claude", "CLAUDE.md");
|
|
20492
|
-
case "windsurf":
|
|
20493
|
-
return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
20494
21772
|
case "cline":
|
|
20495
21773
|
return path8.join(home, "Documents", "Cline", "Rules", "contextstream.md");
|
|
20496
21774
|
case "kilo":
|
|
@@ -20537,25 +21815,6 @@ async function isClaudeInstalled() {
|
|
|
20537
21815
|
}
|
|
20538
21816
|
return anyPathExists(candidates);
|
|
20539
21817
|
}
|
|
20540
|
-
async function isWindsurfInstalled() {
|
|
20541
|
-
const home = homedir5();
|
|
20542
|
-
const candidates = [
|
|
20543
|
-
path8.join(home, ".codeium"),
|
|
20544
|
-
path8.join(home, ".codeium", "windsurf"),
|
|
20545
|
-
path8.join(home, ".config", "codeium")
|
|
20546
|
-
];
|
|
20547
|
-
if (process.platform === "darwin") {
|
|
20548
|
-
candidates.push(path8.join(home, "Library", "Application Support", "Windsurf"));
|
|
20549
|
-
candidates.push(path8.join(home, "Library", "Application Support", "Codeium"));
|
|
20550
|
-
} else if (process.platform === "win32") {
|
|
20551
|
-
const appData = process.env.APPDATA;
|
|
20552
|
-
if (appData) {
|
|
20553
|
-
candidates.push(path8.join(appData, "Windsurf"));
|
|
20554
|
-
candidates.push(path8.join(appData, "Codeium"));
|
|
20555
|
-
}
|
|
20556
|
-
}
|
|
20557
|
-
return anyPathExists(candidates);
|
|
20558
|
-
}
|
|
20559
21818
|
async function isClineInstalled() {
|
|
20560
21819
|
const home = homedir5();
|
|
20561
21820
|
const candidates = [
|
|
@@ -20634,8 +21893,6 @@ async function isEditorInstalled(editor) {
|
|
|
20634
21893
|
return isClaudeInstalled();
|
|
20635
21894
|
case "cursor":
|
|
20636
21895
|
return isCursorInstalled();
|
|
20637
|
-
case "windsurf":
|
|
20638
|
-
return isWindsurfInstalled();
|
|
20639
21896
|
case "cline":
|
|
20640
21897
|
return isClineInstalled();
|
|
20641
21898
|
case "kilo":
|
|
@@ -20660,6 +21917,9 @@ function buildContextStreamMcpServer(params) {
|
|
|
20660
21917
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
20661
21918
|
}
|
|
20662
21919
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
21920
|
+
if (params.restoreContextEnabled === false) {
|
|
21921
|
+
env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
|
|
21922
|
+
}
|
|
20663
21923
|
if (params.showTiming) {
|
|
20664
21924
|
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20665
21925
|
}
|
|
@@ -20685,6 +21945,9 @@ function buildContextStreamVsCodeServer(params) {
|
|
|
20685
21945
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
20686
21946
|
}
|
|
20687
21947
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
21948
|
+
if (params.restoreContextEnabled === false) {
|
|
21949
|
+
env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
|
|
21950
|
+
}
|
|
20688
21951
|
if (params.showTiming) {
|
|
20689
21952
|
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20690
21953
|
}
|
|
@@ -20788,6 +22051,8 @@ async function upsertCodexTomlConfig(filePath, params) {
|
|
|
20788
22051
|
` : "";
|
|
20789
22052
|
const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
|
|
20790
22053
|
`;
|
|
22054
|
+
const restoreContextLine = params.restoreContextEnabled === false ? `CONTEXTSTREAM_RESTORE_CONTEXT = "false"
|
|
22055
|
+
` : "";
|
|
20791
22056
|
const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
|
|
20792
22057
|
` : "";
|
|
20793
22058
|
const commandLine = IS_WINDOWS ? `command = "cmd"
|
|
@@ -20803,7 +22068,7 @@ args = ["-y", "@contextstream/mcp-server"]
|
|
|
20803
22068
|
[mcp_servers.contextstream.env]
|
|
20804
22069
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
20805
22070
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
20806
|
-
` + toolsetLine + contextPackLine + showTimingLine;
|
|
22071
|
+
` + toolsetLine + contextPackLine + restoreContextLine + showTimingLine;
|
|
20807
22072
|
if (!exists) {
|
|
20808
22073
|
await fs7.writeFile(filePath, block.trimStart(), "utf8");
|
|
20809
22074
|
return "created";
|
|
@@ -20933,6 +22198,26 @@ async function runSetupWizard(args) {
|
|
|
20933
22198
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
20934
22199
|
if (dryRun) console.log("DRY RUN: no files will be written.\n");
|
|
20935
22200
|
else console.log("");
|
|
22201
|
+
const versionNotice = await getUpdateNotice();
|
|
22202
|
+
if (versionNotice?.behind) {
|
|
22203
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
22204
|
+
console.log(`\u26A0\uFE0F You're running an outdated version (v${versionNotice.current})`);
|
|
22205
|
+
console.log(` Latest version is v${versionNotice.latest}`);
|
|
22206
|
+
console.log("");
|
|
22207
|
+
console.log(" To use the latest version, exit and run:");
|
|
22208
|
+
console.log(" npx -y @contextstream/mcp-server@latest setup");
|
|
22209
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
22210
|
+
console.log("");
|
|
22211
|
+
const continueAnyway = normalizeInput(
|
|
22212
|
+
await rl.question("Continue with current version anyway? [y/N]: ")
|
|
22213
|
+
).toLowerCase();
|
|
22214
|
+
if (continueAnyway !== "y" && continueAnyway !== "yes") {
|
|
22215
|
+
console.log("\nExiting. Run the command above to use the latest version.");
|
|
22216
|
+
rl.close();
|
|
22217
|
+
return;
|
|
22218
|
+
}
|
|
22219
|
+
console.log("");
|
|
22220
|
+
}
|
|
20936
22221
|
const savedCreds = await readSavedCredentials();
|
|
20937
22222
|
const apiUrlDefault = normalizeApiUrl(
|
|
20938
22223
|
process.env.CONTEXTSTREAM_API_URL || savedCreds?.api_url || "https://api.contextstream.io"
|
|
@@ -21125,11 +22410,7 @@ Code: ${device.user_code}`);
|
|
|
21125
22410
|
}
|
|
21126
22411
|
}
|
|
21127
22412
|
}
|
|
21128
|
-
|
|
21129
|
-
console.log(" 1) Standard (recommended) \u2014 concise, high-signal (lower token overhead)");
|
|
21130
|
-
console.log(" 2) Enhanced \u2014 more guidance + examples (higher token overhead)");
|
|
21131
|
-
const modeChoice = normalizeInput(await rl.question("Choose [1/2] (default 1): ")) || "1";
|
|
21132
|
-
const mode = modeChoice === "2" ? "full" : "minimal";
|
|
22413
|
+
const mode = "full";
|
|
21133
22414
|
const detectedPlanName = await client.getPlanName();
|
|
21134
22415
|
const detectedGraphTier = await client.getGraphTier();
|
|
21135
22416
|
const graphTierLabel = detectedGraphTier === "full" ? "full graph" : detectedGraphTier === "lite" ? "graph-lite" : "none";
|
|
@@ -21165,11 +22446,16 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
21165
22446
|
console.log(" Useful for debugging performance; disabled by default.");
|
|
21166
22447
|
const showTimingChoice = normalizeInput(await rl.question("Show response timing? [y/N]: "));
|
|
21167
22448
|
const showTiming = showTimingChoice.toLowerCase() === "y" || showTimingChoice.toLowerCase() === "yes";
|
|
22449
|
+
console.log("\nAutomatic Context Restoration:");
|
|
22450
|
+
console.log(" Automatically restore context from recent snapshots on every session_init.");
|
|
22451
|
+
console.log(" This enables seamless continuation across conversations and after compaction.");
|
|
22452
|
+
console.log(" Enabled by default; disable if you prefer explicit control.");
|
|
22453
|
+
const restoreContextChoice = normalizeInput(await rl.question("Enable automatic context restoration? [Y/n]: "));
|
|
22454
|
+
const restoreContextEnabled = !(restoreContextChoice.toLowerCase() === "n" || restoreContextChoice.toLowerCase() === "no");
|
|
21168
22455
|
const editors = [
|
|
21169
22456
|
"codex",
|
|
21170
22457
|
"claude",
|
|
21171
22458
|
"cursor",
|
|
21172
|
-
"windsurf",
|
|
21173
22459
|
"cline",
|
|
21174
22460
|
"kilo",
|
|
21175
22461
|
"roo",
|
|
@@ -21215,7 +22501,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
21215
22501
|
console.log(" 1) Global");
|
|
21216
22502
|
console.log(" 2) Project");
|
|
21217
22503
|
console.log(" 3) Both");
|
|
21218
|
-
const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default
|
|
22504
|
+
const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 2): ")) || "2";
|
|
21219
22505
|
const scope = scopeChoice === "1" ? "global" : scopeChoice === "2" ? "project" : "both";
|
|
21220
22506
|
console.log("\nInstall MCP server config as:");
|
|
21221
22507
|
if (hasCodex && !hasProjectMcpEditors) {
|
|
@@ -21232,27 +22518,29 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
21232
22518
|
);
|
|
21233
22519
|
}
|
|
21234
22520
|
}
|
|
21235
|
-
const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "
|
|
22521
|
+
const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "2";
|
|
21236
22522
|
const mcpChoice = normalizeInput(
|
|
21237
22523
|
await rl.question(
|
|
21238
22524
|
`Choose [${hasCodex && !hasProjectMcpEditors ? "1/2" : "1/2/3/4"}] (default ${mcpChoiceDefault}): `
|
|
21239
22525
|
)
|
|
21240
22526
|
) || mcpChoiceDefault;
|
|
21241
22527
|
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
21242
|
-
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming });
|
|
22528
|
+
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming, restoreContextEnabled });
|
|
21243
22529
|
const mcpServerClaude = buildContextStreamMcpServer({
|
|
21244
22530
|
apiUrl,
|
|
21245
22531
|
apiKey,
|
|
21246
22532
|
toolset,
|
|
21247
22533
|
contextPackEnabled,
|
|
21248
|
-
showTiming
|
|
22534
|
+
showTiming,
|
|
22535
|
+
restoreContextEnabled
|
|
21249
22536
|
});
|
|
21250
22537
|
const vsCodeServer = buildContextStreamVsCodeServer({
|
|
21251
22538
|
apiUrl,
|
|
21252
22539
|
apiKey,
|
|
21253
22540
|
toolset,
|
|
21254
22541
|
contextPackEnabled,
|
|
21255
|
-
showTiming
|
|
22542
|
+
showTiming,
|
|
22543
|
+
restoreContextEnabled
|
|
21256
22544
|
});
|
|
21257
22545
|
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
21258
22546
|
if (needsGlobalMcpConfig) {
|
|
@@ -21272,24 +22560,13 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
21272
22560
|
apiKey,
|
|
21273
22561
|
toolset,
|
|
21274
22562
|
contextPackEnabled,
|
|
21275
|
-
showTiming
|
|
22563
|
+
showTiming,
|
|
22564
|
+
restoreContextEnabled
|
|
21276
22565
|
});
|
|
21277
22566
|
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
21278
22567
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
21279
22568
|
continue;
|
|
21280
22569
|
}
|
|
21281
|
-
if (editor === "windsurf") {
|
|
21282
|
-
const filePath = path8.join(homedir5(), ".codeium", "windsurf", "mcp_config.json");
|
|
21283
|
-
if (dryRun) {
|
|
21284
|
-
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
21285
|
-
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
21286
|
-
continue;
|
|
21287
|
-
}
|
|
21288
|
-
const status = await upsertJsonMcpConfig(filePath, mcpServer);
|
|
21289
|
-
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
21290
|
-
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
21291
|
-
continue;
|
|
21292
|
-
}
|
|
21293
22570
|
if (editor === "claude") {
|
|
21294
22571
|
const desktopPath = claudeDesktopConfigPath();
|
|
21295
22572
|
if (desktopPath) {
|
|
@@ -21354,53 +22631,68 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
21354
22631
|
}
|
|
21355
22632
|
}
|
|
21356
22633
|
}
|
|
21357
|
-
|
|
22634
|
+
const HOOKS_SUPPORTED_EDITORS = {
|
|
22635
|
+
claude: "claude",
|
|
22636
|
+
cursor: "cursor",
|
|
22637
|
+
cline: "cline",
|
|
22638
|
+
roo: "roo",
|
|
22639
|
+
kilo: "kilo",
|
|
22640
|
+
codex: null,
|
|
22641
|
+
// No hooks API
|
|
22642
|
+
aider: null,
|
|
22643
|
+
// No hooks API
|
|
22644
|
+
antigravity: null
|
|
22645
|
+
// No hooks API
|
|
22646
|
+
};
|
|
22647
|
+
const hookEligibleEditors = configuredEditors.filter(
|
|
22648
|
+
(e) => HOOKS_SUPPORTED_EDITORS[e] !== null
|
|
22649
|
+
);
|
|
22650
|
+
if (hookEligibleEditors.length > 0) {
|
|
21358
22651
|
console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
21359
|
-
console.log("\u2502
|
|
22652
|
+
console.log("\u2502 AI Editor Hooks (Recommended) \u2502");
|
|
21360
22653
|
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
21361
22654
|
console.log("");
|
|
21362
|
-
console.log(" Problem:
|
|
21363
|
-
console.log("
|
|
21364
|
-
console.log(" This happens because instructions decay over long conversations.");
|
|
22655
|
+
console.log(" Problem: AI editors often use their default tools (Grep/Glob/Search)");
|
|
22656
|
+
console.log(" instead of ContextStream smart search. Instructions decay over long chats.");
|
|
21365
22657
|
console.log("");
|
|
21366
22658
|
console.log(" Solution: Install hooks that:");
|
|
21367
|
-
console.log(" \u2713
|
|
21368
|
-
console.log(" \u2713
|
|
21369
|
-
console.log(" \u2713 Inject reminders
|
|
21370
|
-
console.log("
|
|
22659
|
+
console.log(" \u2713 Use ContextStream (indexed, faster) with default tool use");
|
|
22660
|
+
console.log(" \u2713 Use ContextStream plans (persistent) with default tool use");
|
|
22661
|
+
console.log(" \u2713 Inject reminders to keep rules in context");
|
|
22662
|
+
console.log("");
|
|
22663
|
+
console.log(` Hooks available for: ${hookEligibleEditors.map((e) => EDITOR_LABELS[e]).join(", ")}`);
|
|
21371
22664
|
console.log("");
|
|
21372
22665
|
console.log(" You can disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
|
|
21373
22666
|
console.log("");
|
|
21374
22667
|
const installHooks = normalizeInput(
|
|
21375
|
-
await rl.question("Install
|
|
22668
|
+
await rl.question("Install editor hooks? [Y/n] (recommended): ")
|
|
21376
22669
|
).toLowerCase();
|
|
21377
22670
|
if (installHooks !== "n" && installHooks !== "no") {
|
|
21378
|
-
|
|
21379
|
-
|
|
21380
|
-
|
|
21381
|
-
|
|
21382
|
-
|
|
21383
|
-
|
|
21384
|
-
|
|
21385
|
-
|
|
21386
|
-
const result = await
|
|
21387
|
-
|
|
21388
|
-
|
|
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}`);
|
|
22671
|
+
for (const editor of hookEligibleEditors) {
|
|
22672
|
+
const hookEditor = HOOKS_SUPPORTED_EDITORS[editor];
|
|
22673
|
+
if (!hookEditor) continue;
|
|
22674
|
+
try {
|
|
22675
|
+
if (dryRun) {
|
|
22676
|
+
console.log(`- ${EDITOR_LABELS[editor]}: would install hooks`);
|
|
22677
|
+
continue;
|
|
22678
|
+
}
|
|
22679
|
+
const result = await installEditorHooks({
|
|
22680
|
+
editor: hookEditor,
|
|
22681
|
+
scope: "global"
|
|
21394
22682
|
});
|
|
22683
|
+
for (const script of result.installed) {
|
|
22684
|
+
writeActions.push({ kind: "hooks", target: script, status: "created" });
|
|
22685
|
+
console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
|
|
22686
|
+
}
|
|
22687
|
+
} catch (err) {
|
|
22688
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22689
|
+
console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
|
|
21395
22690
|
}
|
|
21396
|
-
console.log(" Hooks installed. Disable with CONTEXTSTREAM_HOOK_ENABLED=false");
|
|
21397
|
-
} catch (err) {
|
|
21398
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
21399
|
-
console.log(`- Failed to install hooks: ${message}`);
|
|
21400
22691
|
}
|
|
22692
|
+
console.log(" Hooks installed. Disable with CONTEXTSTREAM_HOOK_ENABLED=false");
|
|
21401
22693
|
} else {
|
|
21402
22694
|
console.log("- Skipped hooks installation.");
|
|
21403
|
-
console.log(" Note: Without hooks,
|
|
22695
|
+
console.log(" Note: Without hooks, AI may still use default tools instead of ContextStream.");
|
|
21404
22696
|
}
|
|
21405
22697
|
}
|
|
21406
22698
|
console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
@@ -21729,11 +23021,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
21729
23021
|
"- Toggle Response Timing with CONTEXTSTREAM_SHOW_TIMING=true|false."
|
|
21730
23022
|
);
|
|
21731
23023
|
console.log("");
|
|
21732
|
-
console.log("You're set
|
|
21733
|
-
console.log(' 1) "session summary"');
|
|
21734
|
-
console.log(` 2) "remember we're using PostgreSQL"`);
|
|
21735
|
-
console.log(' 3) "what did we decide about auth?"');
|
|
21736
|
-
console.log("");
|
|
23024
|
+
console.log("You're all set! ContextStream gives your AI persistent memory, semantic code search, and cross-session context.");
|
|
21737
23025
|
console.log("More at: https://contextstream.io/docs/mcp");
|
|
21738
23026
|
} finally {
|
|
21739
23027
|
rl.close();
|
|
@@ -21883,13 +23171,21 @@ async function main() {
|
|
|
21883
23171
|
if (ENABLE_PROMPTS2) {
|
|
21884
23172
|
registerPrompts(server);
|
|
21885
23173
|
}
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
21889
|
-
|
|
23174
|
+
const logLevel = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
|
|
23175
|
+
const logQuiet = logLevel === "quiet";
|
|
23176
|
+
const logVerbose = logLevel === "verbose";
|
|
23177
|
+
if (!logQuiet) {
|
|
23178
|
+
console.error(`\u2501\u2501\u2501 ContextStream v${VERSION} \u2501\u2501\u2501`);
|
|
23179
|
+
}
|
|
23180
|
+
if (logVerbose) {
|
|
23181
|
+
console.error(` API: ${config.apiUrl}`);
|
|
23182
|
+
console.error(` Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "None"}`);
|
|
23183
|
+
}
|
|
21890
23184
|
const transport = new StdioServerTransport();
|
|
21891
23185
|
await server.connect(transport);
|
|
21892
|
-
|
|
23186
|
+
if (!logQuiet) {
|
|
23187
|
+
console.error("\u2713 ready");
|
|
23188
|
+
}
|
|
21893
23189
|
showFirstRunMessage();
|
|
21894
23190
|
checkForUpdates().catch(() => {
|
|
21895
23191
|
});
|