@contextstream/mcp-server 0.4.67 → 0.4.71
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/README.md +156 -139
- package/dist/hooks/on-read.js +169 -11
- package/dist/hooks/pre-tool-use.js +87 -5
- package/dist/hooks/runner.js +110 -31
- package/dist/hooks/session-init.js +14 -13
- package/dist/hooks/user-prompt-submit.js +12 -12
- package/dist/index.js +1550 -714
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<div align="center">
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx @contextstream/mcp-server@latest setup
|
|
27
|
+
npx --prefer-online -y @contextstream/mcp-server@latest setup
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
</div>
|
|
@@ -35,6 +35,36 @@ npx @contextstream/mcp-server@latest setup
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
+
## Get Started (VS Code + Copilot)
|
|
39
|
+
|
|
40
|
+
### Option 1: Rust MCP (recommended)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -fsSL https://contextstream.io/scripts/mcp.sh | bash
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
irm https://contextstream.io/scripts/mcp.ps1 | iex
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then run:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
contextstream-mcp setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Option 2: Node MCP
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx --prefer-online -y @contextstream/mcp-server@latest setup
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
After setup, restart VS Code/Copilot.
|
|
63
|
+
|
|
64
|
+
**Works with:** Claude Code • Cursor • VS Code • Claude Desktop • Codex CLI • OpenCode • Antigravity
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
38
68
|
## This Isn't Just Memory. This Is Intelligence.
|
|
39
69
|
|
|
40
70
|
Other tools give your AI a notepad. **ContextStream gives it a brain.**
|
|
@@ -76,56 +106,6 @@ Long conversation? ContextStream tracks token usage, auto-saves critical state,
|
|
|
76
106
|
|
|
77
107
|
---
|
|
78
108
|
|
|
79
|
-
## Choose Your Runtime (VS Code/Copilot)
|
|
80
|
-
|
|
81
|
-
For VS Code + Copilot users, we recommend the Rust runtime because it gives the lowest-friction local install and startup path.
|
|
82
|
-
|
|
83
|
-
### Recommended: Rust MCP
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
curl -fsSL https://contextstream.io/scripts/mcp.sh | bash
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```powershell
|
|
90
|
-
irm https://contextstream.io/scripts/mcp.ps1 | iex
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Alternative: Node MCP server
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
npx --prefer-online -y @contextstream/mcp-server@latest setup
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Marketplace install limitation
|
|
100
|
-
|
|
101
|
-
MCP marketplace installs for npm packages can install and run the package entrypoint, but they do not run arbitrary shell bootstrap commands such as `curl ... | bash` or `irm ... | iex`. That means Rust bootstrap must currently be a separate explicit user step unless/ until the Rust runtime is distributed in a marketplace-compatible package format.
|
|
102
|
-
|
|
103
|
-
## Quickest Post-Install Path (VS Code + Copilot)
|
|
104
|
-
|
|
105
|
-
1. Install a runtime (Rust recommended).
|
|
106
|
-
2. Run the setup wizard:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
contextstream-mcp setup
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
3. In wizard prompts, keep Copilot selected and confirm canonical file writes.
|
|
113
|
-
4. Restart VS Code/Copilot.
|
|
114
|
-
|
|
115
|
-
This path is the default recommendation. Manual JSON config snippets below are backup options.
|
|
116
|
-
|
|
117
|
-
## Setup Takes 30 Seconds (Node)
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npx --prefer-online -y @contextstream/mcp-server@latest setup
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The wizard handles authentication, configuration, editor integration, and optional hooks that supercharge your workflow.
|
|
124
|
-
|
|
125
|
-
**Works with:** Claude Code • Cursor • VS Code • Claude Desktop • Codex CLI • OpenCode • Antigravity
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
109
|
## The Tools Your AI Gets
|
|
130
110
|
|
|
131
111
|
```
|
|
@@ -144,6 +124,18 @@ Your AI uses these automatically. You just code.
|
|
|
144
124
|
|
|
145
125
|
---
|
|
146
126
|
|
|
127
|
+
## Global Fallback Workspace (Unmapped Folders)
|
|
128
|
+
|
|
129
|
+
ContextStream now supports a catch-all mode for random folders (for example `~` or ad-hoc dirs) that are not associated with a project/workspace yet.
|
|
130
|
+
|
|
131
|
+
- `init(...)` resolves normal folder mappings first (`.contextstream/config.json`, parent/global mappings).
|
|
132
|
+
- If no mapping exists, it uses a single hidden global fallback workspace (`.contextstream-global`) in workspace-only mode.
|
|
133
|
+
- Context/memory/session tools continue to work without hard setup errors.
|
|
134
|
+
- Project-bound actions (for example `project(action="ingest_local")`) return guided remediation to create/select a project instead of failing with a raw `project_id required` error.
|
|
135
|
+
- As soon as you enter a mapped project folder, that real workspace/project is prioritized and replaces fallback scope.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
147
139
|
## Manual Configuration
|
|
148
140
|
|
|
149
141
|
> Skip this if you ran the setup wizard.
|
|
@@ -152,8 +144,8 @@ Your AI uses these automatically. You just code.
|
|
|
152
144
|
<summary><b>Claude Code</b></summary>
|
|
153
145
|
|
|
154
146
|
```bash
|
|
155
|
-
claude mcp add contextstream -- npx @contextstream/mcp-server
|
|
156
|
-
claude mcp update contextstream -e CONTEXTSTREAM_API_KEY=your_key
|
|
147
|
+
claude mcp add contextstream -- npx --prefer-online -y @contextstream/mcp-server@latest
|
|
148
|
+
claude mcp update contextstream -e CONTEXTSTREAM_API_URL=https://api.contextstream.io -e CONTEXTSTREAM_API_KEY=your_key
|
|
157
149
|
```
|
|
158
150
|
|
|
159
151
|
</details>
|
|
@@ -166,8 +158,11 @@ claude mcp update contextstream -e CONTEXTSTREAM_API_KEY=your_key
|
|
|
166
158
|
"mcpServers": {
|
|
167
159
|
"contextstream": {
|
|
168
160
|
"command": "npx",
|
|
169
|
-
"args": ["-y", "@contextstream/mcp-server"],
|
|
170
|
-
"env": {
|
|
161
|
+
"args": ["--prefer-online", "-y", "@contextstream/mcp-server@latest"],
|
|
162
|
+
"env": {
|
|
163
|
+
"CONTEXTSTREAM_API_URL": "https://api.contextstream.io",
|
|
164
|
+
"CONTEXTSTREAM_API_KEY": "your_key"
|
|
165
|
+
}
|
|
171
166
|
}
|
|
172
167
|
}
|
|
173
168
|
}
|
|
@@ -222,7 +217,26 @@ For the local variant, export `CONTEXTSTREAM_API_KEY` before launching OpenCode.
|
|
|
222
217
|
<details>
|
|
223
218
|
<summary><b>VS Code</b></summary>
|
|
224
219
|
|
|
225
|
-
For GitHub Copilot in VS Code,
|
|
220
|
+
For GitHub Copilot in VS Code, the easiest path is the hosted remote MCP with built-in OAuth. Marketplace installs should write this remote server definition automatically.
|
|
221
|
+
|
|
222
|
+
**Hosted remote MCP (recommended)**
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"servers": {
|
|
227
|
+
"contextstream": {
|
|
228
|
+
"type": "http",
|
|
229
|
+
"url": "https://mcp.contextstream.io/mcp?default_context_mode=fast"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
On first use, VS Code should prompt you to authorize ContextStream in the browser and then complete setup without an API key in the config file.
|
|
236
|
+
|
|
237
|
+
`npx @contextstream/mcp-server@latest setup` now defaults VS Code/Copilot to this hosted remote when you are using the production ContextStream cloud. To force a local runtime instead, run setup with `CONTEXTSTREAM_VSCODE_MCP_MODE=local`.
|
|
238
|
+
|
|
239
|
+
For self-hosted or non-default API deployments, local runtime remains the default:
|
|
226
240
|
|
|
227
241
|
**Rust MCP (recommended)**
|
|
228
242
|
|
|
@@ -233,7 +247,15 @@ For GitHub Copilot in VS Code, use project-level MCP at `.vscode/mcp.json`.
|
|
|
233
247
|
"type": "stdio",
|
|
234
248
|
"command": "contextstream-mcp",
|
|
235
249
|
"args": [],
|
|
236
|
-
"env": {
|
|
250
|
+
"env": {
|
|
251
|
+
"CONTEXTSTREAM_API_URL": "https://api.contextstream.io",
|
|
252
|
+
"CONTEXTSTREAM_API_KEY": "your_key",
|
|
253
|
+
"CONTEXTSTREAM_TOOLSET": "complete",
|
|
254
|
+
"CONTEXTSTREAM_TRANSCRIPTS_ENABLED": "true",
|
|
255
|
+
"CONTEXTSTREAM_HOOK_TRANSCRIPTS_ENABLED": "true",
|
|
256
|
+
"CONTEXTSTREAM_SEARCH_LIMIT": "15",
|
|
257
|
+
"CONTEXTSTREAM_SEARCH_MAX_CHARS": "2400"
|
|
258
|
+
}
|
|
237
259
|
}
|
|
238
260
|
}
|
|
239
261
|
}
|
|
@@ -247,8 +269,16 @@ For GitHub Copilot in VS Code, use project-level MCP at `.vscode/mcp.json`.
|
|
|
247
269
|
"contextstream": {
|
|
248
270
|
"type": "stdio",
|
|
249
271
|
"command": "npx",
|
|
250
|
-
"args": ["-y", "@contextstream/mcp-server"],
|
|
251
|
-
"env": {
|
|
272
|
+
"args": ["--prefer-online", "-y", "@contextstream/mcp-server@latest"],
|
|
273
|
+
"env": {
|
|
274
|
+
"CONTEXTSTREAM_API_URL": "https://api.contextstream.io",
|
|
275
|
+
"CONTEXTSTREAM_API_KEY": "your_key",
|
|
276
|
+
"CONTEXTSTREAM_TOOLSET": "complete",
|
|
277
|
+
"CONTEXTSTREAM_TRANSCRIPTS_ENABLED": "true",
|
|
278
|
+
"CONTEXTSTREAM_HOOK_TRANSCRIPTS_ENABLED": "true",
|
|
279
|
+
"CONTEXTSTREAM_SEARCH_LIMIT": "15",
|
|
280
|
+
"CONTEXTSTREAM_SEARCH_MAX_CHARS": "2400"
|
|
281
|
+
}
|
|
252
282
|
}
|
|
253
283
|
}
|
|
254
284
|
}
|
|
@@ -275,7 +305,15 @@ Or add to `~/.copilot/mcp-config.json` (pick one runtime):
|
|
|
275
305
|
"contextstream": {
|
|
276
306
|
"command": "contextstream-mcp",
|
|
277
307
|
"args": [],
|
|
278
|
-
"env": {
|
|
308
|
+
"env": {
|
|
309
|
+
"CONTEXTSTREAM_API_URL": "https://api.contextstream.io",
|
|
310
|
+
"CONTEXTSTREAM_API_KEY": "your_key",
|
|
311
|
+
"CONTEXTSTREAM_TOOLSET": "complete",
|
|
312
|
+
"CONTEXTSTREAM_TRANSCRIPTS_ENABLED": "true",
|
|
313
|
+
"CONTEXTSTREAM_HOOK_TRANSCRIPTS_ENABLED": "true",
|
|
314
|
+
"CONTEXTSTREAM_SEARCH_LIMIT": "15",
|
|
315
|
+
"CONTEXTSTREAM_SEARCH_MAX_CHARS": "2400"
|
|
316
|
+
}
|
|
279
317
|
}
|
|
280
318
|
}
|
|
281
319
|
}
|
|
@@ -288,8 +326,16 @@ Or add to `~/.copilot/mcp-config.json` (pick one runtime):
|
|
|
288
326
|
"mcpServers": {
|
|
289
327
|
"contextstream": {
|
|
290
328
|
"command": "npx",
|
|
291
|
-
"args": ["-y", "@contextstream/mcp-server"],
|
|
292
|
-
"env": {
|
|
329
|
+
"args": ["--prefer-online", "-y", "@contextstream/mcp-server@latest"],
|
|
330
|
+
"env": {
|
|
331
|
+
"CONTEXTSTREAM_API_URL": "https://api.contextstream.io",
|
|
332
|
+
"CONTEXTSTREAM_API_KEY": "your_key",
|
|
333
|
+
"CONTEXTSTREAM_TOOLSET": "complete",
|
|
334
|
+
"CONTEXTSTREAM_TRANSCRIPTS_ENABLED": "true",
|
|
335
|
+
"CONTEXTSTREAM_HOOK_TRANSCRIPTS_ENABLED": "true",
|
|
336
|
+
"CONTEXTSTREAM_SEARCH_LIMIT": "15",
|
|
337
|
+
"CONTEXTSTREAM_SEARCH_MAX_CHARS": "2400"
|
|
338
|
+
}
|
|
293
339
|
}
|
|
294
340
|
}
|
|
295
341
|
}
|
|
@@ -301,83 +347,54 @@ For more information, see the [GitHub Copilot CLI documentation](https://docs.gi
|
|
|
301
347
|
|
|
302
348
|
---
|
|
303
349
|
|
|
304
|
-
## VS Code + Copilot
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
- **Claude Code**
|
|
354
|
-
- Confirm hooks exist in `~/.claude/settings.json` or project `.claude/settings.json`.
|
|
355
|
-
- Verify ContextStream hook commands are present for `PreToolUse`, `UserPromptSubmit`, `SessionStart`, and `PreCompact`.
|
|
356
|
-
- Check `CONTEXTSTREAM_HOOK_ENABLED` is not set to `false`.
|
|
357
|
-
- **Cursor**
|
|
358
|
-
- Confirm `.cursor/hooks.json` includes `preToolUse` and `beforeSubmitPrompt`.
|
|
359
|
-
- Verify `beforeMCPExecution` / `beforeShellExecution` / `beforeReadFile` hook entries exist after setup.
|
|
360
|
-
- If hooks are stale, rerun setup to regenerate ContextStream entries without deleting user hooks.
|
|
361
|
-
- **Antigravity**
|
|
362
|
-
- Verify `~/.gemini/antigravity/mcp_config.json` has a healthy `contextstream` server block.
|
|
363
|
-
- Since hooks are unavailable, enforce manual discipline: `init(...)` then `context(...)`, and `search(mode="auto", ...)` before local scans.
|
|
364
|
-
- Re-index when search appears stale and retry ContextStream search before fallback.
|
|
365
|
-
- **Windsurf**
|
|
366
|
-
- Confirm `~/.codeium/windsurf/mcp_config.json` includes `contextstream`.
|
|
367
|
-
- Confirm hooks in `~/.codeium/windsurf/hooks.json` include `pre_mcp_tool_use` and `pre_user_prompt`.
|
|
368
|
-
- If behavior is stale, rerun setup to regenerate ContextStream hook entries while preserving user hooks.
|
|
369
|
-
- **All editors**
|
|
370
|
-
- Validate JSON shape (`mcpServers` vs `servers`) and remove trailing commas/comments.
|
|
371
|
-
- Keep first-call protocol strict: first turn `init(...)` then `context(...)`.
|
|
372
|
-
- Preserve non-ContextStream entries when regenerating configs.
|
|
373
|
-
|
|
374
|
-
## Rust/Node Parity Checklist
|
|
375
|
-
|
|
376
|
-
- Claude hook matrix includes current lifecycle events in both Rust and Node setup flows.
|
|
377
|
-
- Cursor hook matrix includes tool/MCP/shell/file/session enforcement hooks in both implementations.
|
|
378
|
-
- Windsurf hook matrix includes pre/post Cascade hook coverage in both implementations.
|
|
379
|
-
- Antigravity remains explicit no-hooks with strengthened rules guidance in both implementations.
|
|
380
|
-
- Hook/rules installers stay idempotent and avoid deleting non-ContextStream user entries.
|
|
350
|
+
## VS Code + Copilot Tips
|
|
351
|
+
|
|
352
|
+
- Run setup once and keep both config files:
|
|
353
|
+
- `~/.copilot/mcp-config.json`
|
|
354
|
+
- `.vscode/mcp.json`
|
|
355
|
+
- Rust install: use `contextstream-mcp` as the command.
|
|
356
|
+
- Node install: use `npx --prefer-online -y @contextstream/mcp-server@latest` as the command.
|
|
357
|
+
- Force local VS Code/Copilot setup with `CONTEXTSTREAM_VSCODE_MCP_MODE=local`.
|
|
358
|
+
- Force hosted remote VS Code/Copilot setup with `CONTEXTSTREAM_VSCODE_MCP_MODE=remote`.
|
|
359
|
+
- Use `mcpServers` in Copilot CLI config and `servers` in VS Code config.
|
|
360
|
+
|
|
361
|
+
## Quick Troubleshooting
|
|
362
|
+
|
|
363
|
+
- Remove duplicate ContextStream entries across Workspace/User config scopes.
|
|
364
|
+
- Check `CONTEXTSTREAM_API_URL` and `CONTEXTSTREAM_API_KEY` are set.
|
|
365
|
+
- Remove stale version pins like `@contextstream/mcp-server@0.3.xx`.
|
|
366
|
+
- Restart VS Code/Copilot after config changes.
|
|
367
|
+
|
|
368
|
+
## Known Limitations
|
|
369
|
+
|
|
370
|
+
### HTTP transport OAuth and vscode.dev dependency
|
|
371
|
+
|
|
372
|
+
The hosted HTTP MCP transport (`https://mcp.contextstream.io/mcp`) uses OAuth authentication that routes through `vscode.dev` for the redirect flow. This can fail in environments where `vscode.dev` is blocked (corporate networks, regional restrictions, CDN-level blocks).
|
|
373
|
+
|
|
374
|
+
**Workaround:** Use the stdio transport (Rust binary or Node.js) with API key authentication instead:
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"contextstream": {
|
|
379
|
+
"type": "stdio",
|
|
380
|
+
"command": "npx",
|
|
381
|
+
"args": ["-y", "@contextstream/mcp-server@latest"],
|
|
382
|
+
"env": {
|
|
383
|
+
"CONTEXTSTREAM_API_KEY": "your-api-key"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### SDK version compatibility
|
|
390
|
+
|
|
391
|
+
`@modelcontextprotocol/sdk` versions 1.28.0 and above introduce breaking changes. The `package.json` pins the SDK to `>=1.25.1 <1.28.0` to prevent incompatible resolutions. If you experience Zod schema errors on startup, ensure your SDK version is below 1.28.0.
|
|
392
|
+
|
|
393
|
+
## Marketplace Note
|
|
394
|
+
|
|
395
|
+
The MCP marketplace entry now targets the hosted remote MCP at `https://mcp.contextstream.io/mcp?default_context_mode=fast` so VS Code can use the native OAuth flow instead of writing a local npm-based stdio config.
|
|
396
|
+
|
|
397
|
+
Use the Rust or Node local runtime configs above only when you explicitly want local execution, custom/self-hosted endpoints, or editor environments that do not support the hosted remote flow.
|
|
381
398
|
|
|
382
399
|
---
|
|
383
400
|
|
package/dist/hooks/on-read.js
CHANGED
|
@@ -1,9 +1,153 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/hooks/on-read.ts
|
|
4
|
+
import * as fs2 from "node:fs";
|
|
5
|
+
import * as path2 from "node:path";
|
|
6
|
+
import { homedir as homedir2 } from "node:os";
|
|
7
|
+
|
|
8
|
+
// src/hot-paths.ts
|
|
4
9
|
import * as fs from "node:fs";
|
|
5
10
|
import * as path from "node:path";
|
|
6
11
|
import { homedir } from "node:os";
|
|
12
|
+
var STORE_VERSION = 1;
|
|
13
|
+
var STORE_DIR = path.join(homedir(), ".contextstream");
|
|
14
|
+
var STORE_FILE = path.join(STORE_DIR, "hot-paths.json");
|
|
15
|
+
var MAX_PATHS_PER_SCOPE = 400;
|
|
16
|
+
var HALF_LIFE_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
17
|
+
function clamp(value, min, max) {
|
|
18
|
+
return Math.max(min, Math.min(max, value));
|
|
19
|
+
}
|
|
20
|
+
function normalizePathKey(input) {
|
|
21
|
+
return input.replace(/\\/g, "/").trim();
|
|
22
|
+
}
|
|
23
|
+
function toScopeKey(input) {
|
|
24
|
+
const workspace = input.workspace_id || "none";
|
|
25
|
+
const project = input.project_id || "none";
|
|
26
|
+
return `${workspace}:${project}`;
|
|
27
|
+
}
|
|
28
|
+
function signalWeight(signal) {
|
|
29
|
+
switch (signal) {
|
|
30
|
+
case "search_result":
|
|
31
|
+
return 1.8;
|
|
32
|
+
case "activity_edit":
|
|
33
|
+
return 1.4;
|
|
34
|
+
case "activity_focus":
|
|
35
|
+
return 1.2;
|
|
36
|
+
case "activity_read":
|
|
37
|
+
default:
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function looksBroadQuery(query) {
|
|
42
|
+
const q = query.trim().toLowerCase();
|
|
43
|
+
if (!q) return true;
|
|
44
|
+
if (q.length <= 2) return true;
|
|
45
|
+
if (/^\*+$/.test(q) || q === "all files" || q === "everything") return true;
|
|
46
|
+
const tokens = q.split(/\s+/).filter(Boolean);
|
|
47
|
+
return tokens.length > 12;
|
|
48
|
+
}
|
|
49
|
+
var HotPathStore = class {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.data = { version: STORE_VERSION, scopes: {} };
|
|
52
|
+
this.load();
|
|
53
|
+
}
|
|
54
|
+
recordPaths(scope, paths, signal) {
|
|
55
|
+
if (paths.length === 0) return;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const scopeKey = toScopeKey(scope);
|
|
58
|
+
const profile = this.ensureScope(scopeKey);
|
|
59
|
+
const weight = signalWeight(signal);
|
|
60
|
+
for (const raw of paths) {
|
|
61
|
+
const pathKey = normalizePathKey(raw);
|
|
62
|
+
if (!pathKey) continue;
|
|
63
|
+
const current = profile.paths[pathKey] || { score: 0, last_seen: now, hits: 0 };
|
|
64
|
+
const decayed = this.decayedScore(current.score, current.last_seen, now);
|
|
65
|
+
profile.paths[pathKey] = {
|
|
66
|
+
score: decayed + weight,
|
|
67
|
+
last_seen: now,
|
|
68
|
+
hits: current.hits + 1
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
profile.updated_at = now;
|
|
72
|
+
this.pruneScope(profile);
|
|
73
|
+
this.persist();
|
|
74
|
+
}
|
|
75
|
+
buildHint(input) {
|
|
76
|
+
const scopeKey = toScopeKey(input);
|
|
77
|
+
const profile = this.data.scopes[scopeKey];
|
|
78
|
+
if (!profile) return void 0;
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const baseEntries = Object.entries(profile.paths).map(([filePath, entry]) => ({
|
|
81
|
+
path: filePath,
|
|
82
|
+
score: this.decayedScore(entry.score, entry.last_seen, now)
|
|
83
|
+
})).filter((entry) => entry.score > 0.05).sort((a, b) => b.score - a.score);
|
|
84
|
+
const active = (input.active_paths || []).map(normalizePathKey).filter(Boolean);
|
|
85
|
+
const merged = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const entry of baseEntries.slice(0, Math.max(12, input.limit || 8))) {
|
|
87
|
+
merged.set(entry.path, {
|
|
88
|
+
path: entry.path,
|
|
89
|
+
score: Number(entry.score.toFixed(4)),
|
|
90
|
+
source: "history"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const activePath of active) {
|
|
94
|
+
const existing = merged.get(activePath);
|
|
95
|
+
if (existing) {
|
|
96
|
+
existing.score = Number((existing.score + 0.9).toFixed(4));
|
|
97
|
+
} else {
|
|
98
|
+
merged.set(activePath, { path: activePath, score: 0.9, source: "active" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const limit = clamp(input.limit ?? 8, 1, 12);
|
|
102
|
+
const entries = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
103
|
+
if (entries.length === 0) return void 0;
|
|
104
|
+
const scoreSum = entries.reduce((sum, item) => sum + item.score, 0);
|
|
105
|
+
const normalized = clamp(scoreSum / (limit * 2.5), 0, 1);
|
|
106
|
+
const confidencePenalty = looksBroadQuery(input.query) ? 0.55 : 1;
|
|
107
|
+
const confidence = Number((normalized * confidencePenalty).toFixed(3));
|
|
108
|
+
return {
|
|
109
|
+
entries,
|
|
110
|
+
confidence,
|
|
111
|
+
generated_at: new Date(now).toISOString(),
|
|
112
|
+
profile_version: STORE_VERSION
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
decayedScore(score, lastSeenMs, nowMs) {
|
|
116
|
+
const elapsed = Math.max(0, nowMs - lastSeenMs);
|
|
117
|
+
const decay = Math.pow(0.5, elapsed / HALF_LIFE_MS);
|
|
118
|
+
return score * decay;
|
|
119
|
+
}
|
|
120
|
+
ensureScope(scopeKey) {
|
|
121
|
+
if (!this.data.scopes[scopeKey]) {
|
|
122
|
+
this.data.scopes[scopeKey] = { paths: {}, updated_at: Date.now() };
|
|
123
|
+
}
|
|
124
|
+
return this.data.scopes[scopeKey];
|
|
125
|
+
}
|
|
126
|
+
pruneScope(profile) {
|
|
127
|
+
const entries = Object.entries(profile.paths);
|
|
128
|
+
if (entries.length <= MAX_PATHS_PER_SCOPE) return;
|
|
129
|
+
entries.sort((a, b) => b[1].score - a[1].score).slice(MAX_PATHS_PER_SCOPE).forEach(([key]) => delete profile.paths[key]);
|
|
130
|
+
}
|
|
131
|
+
load() {
|
|
132
|
+
try {
|
|
133
|
+
if (!fs.existsSync(STORE_FILE)) return;
|
|
134
|
+
const parsed = JSON.parse(fs.readFileSync(STORE_FILE, "utf-8"));
|
|
135
|
+
if (parsed?.version !== STORE_VERSION || !parsed.scopes) return;
|
|
136
|
+
this.data = parsed;
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
persist() {
|
|
141
|
+
try {
|
|
142
|
+
fs.mkdirSync(STORE_DIR, { recursive: true });
|
|
143
|
+
fs.writeFileSync(STORE_FILE, JSON.stringify(this.data));
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var globalHotPathStore = new HotPathStore();
|
|
149
|
+
|
|
150
|
+
// src/hooks/on-read.ts
|
|
7
151
|
var ENABLED = process.env.CONTEXTSTREAM_READ_HOOK_ENABLED !== "false";
|
|
8
152
|
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
9
153
|
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
@@ -11,13 +155,13 @@ var WORKSPACE_ID = null;
|
|
|
11
155
|
var recentCaptures = /* @__PURE__ */ new Set();
|
|
12
156
|
var CAPTURE_WINDOW_MS = 6e4;
|
|
13
157
|
function loadConfigFromMcpJson(cwd) {
|
|
14
|
-
let searchDir =
|
|
158
|
+
let searchDir = path2.resolve(cwd);
|
|
15
159
|
for (let i = 0; i < 5; i++) {
|
|
16
160
|
if (!API_KEY) {
|
|
17
|
-
const mcpPath =
|
|
18
|
-
if (
|
|
161
|
+
const mcpPath = path2.join(searchDir, ".mcp.json");
|
|
162
|
+
if (fs2.existsSync(mcpPath)) {
|
|
19
163
|
try {
|
|
20
|
-
const content =
|
|
164
|
+
const content = fs2.readFileSync(mcpPath, "utf-8");
|
|
21
165
|
const config = JSON.parse(content);
|
|
22
166
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
23
167
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -31,10 +175,10 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
31
175
|
}
|
|
32
176
|
}
|
|
33
177
|
if (!WORKSPACE_ID) {
|
|
34
|
-
const csConfigPath =
|
|
35
|
-
if (
|
|
178
|
+
const csConfigPath = path2.join(searchDir, ".contextstream", "config.json");
|
|
179
|
+
if (fs2.existsSync(csConfigPath)) {
|
|
36
180
|
try {
|
|
37
|
-
const content =
|
|
181
|
+
const content = fs2.readFileSync(csConfigPath, "utf-8");
|
|
38
182
|
const csConfig = JSON.parse(content);
|
|
39
183
|
if (csConfig.workspace_id) {
|
|
40
184
|
WORKSPACE_ID = csConfig.workspace_id;
|
|
@@ -43,15 +187,15 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
43
187
|
}
|
|
44
188
|
}
|
|
45
189
|
}
|
|
46
|
-
const parentDir =
|
|
190
|
+
const parentDir = path2.dirname(searchDir);
|
|
47
191
|
if (parentDir === searchDir) break;
|
|
48
192
|
searchDir = parentDir;
|
|
49
193
|
}
|
|
50
194
|
if (!API_KEY) {
|
|
51
|
-
const homeMcpPath =
|
|
52
|
-
if (
|
|
195
|
+
const homeMcpPath = path2.join(homedir2(), ".mcp.json");
|
|
196
|
+
if (fs2.existsSync(homeMcpPath)) {
|
|
53
197
|
try {
|
|
54
|
-
const content =
|
|
198
|
+
const content = fs2.readFileSync(homeMcpPath, "utf-8");
|
|
55
199
|
const config = JSON.parse(content);
|
|
56
200
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
57
201
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -136,11 +280,25 @@ async function runOnReadHook() {
|
|
|
136
280
|
case "Read":
|
|
137
281
|
target = input.tool_input?.file_path || "";
|
|
138
282
|
resultSummary = `Read file: ${target}`;
|
|
283
|
+
if (target) {
|
|
284
|
+
globalHotPathStore.recordPaths(
|
|
285
|
+
{ workspace_id: WORKSPACE_ID || void 0, project_id: void 0 },
|
|
286
|
+
[target],
|
|
287
|
+
"activity_read"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
139
290
|
break;
|
|
140
291
|
case "Glob":
|
|
141
292
|
target = input.tool_input?.pattern || "";
|
|
142
293
|
const globFiles = input.tool_result?.files || [];
|
|
143
294
|
resultSummary = `Found ${globFiles.length} files matching ${target}`;
|
|
295
|
+
if (globFiles.length > 0) {
|
|
296
|
+
globalHotPathStore.recordPaths(
|
|
297
|
+
{ workspace_id: WORKSPACE_ID || void 0, project_id: void 0 },
|
|
298
|
+
globFiles.slice(0, 30),
|
|
299
|
+
"activity_focus"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
144
302
|
break;
|
|
145
303
|
case "Grep":
|
|
146
304
|
target = input.tool_input?.pattern || "";
|