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