@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 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": { "CONTEXTSTREAM_API_KEY": "your_key" }
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, use project-level MCP at `.vscode/mcp.json`.
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": { "CONTEXTSTREAM_API_KEY": "your_key" }
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": { "CONTEXTSTREAM_API_KEY": "your_key" }
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": { "CONTEXTSTREAM_API_KEY": "your_key" }
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": { "CONTEXTSTREAM_API_KEY": "your_key" }
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 Canonical Setup
305
-
306
- Select Copilot in setup; setup handles both configs automatically.
307
-
308
- For the most reliable Copilot behavior with ContextStream, configure all three artifacts:
309
-
310
- 1. **Global Copilot CLI MCP config**: `~/.copilot/mcp-config.json`
311
- 2. **Project VS Code MCP config**: `.vscode/mcp.json`
312
- 3. **Project rules and skill files**:
313
- - `.github/copilot-instructions.md`
314
- - `.github/skills/contextstream-workflow/SKILL.md`
315
-
316
- This gives you MCP connectivity plus explicit no-hooks workflow guidance for Copilot sessions.
317
-
318
- If you installed Rust MCP, use `contextstream-mcp` as the command in both MCP files. If you installed via npm/marketplace, use `npx --prefer-online -y @contextstream/mcp-server@latest`.
319
-
320
- ## Troubleshooting (Why Copilot/VS Code "isn't working")
321
-
322
- - **Wrong config location**: verify both `~/.copilot/mcp-config.json` and `.vscode/mcp.json` for project-specific VS Code usage.
323
- - **Malformed JSON**: remove comments/trailing commas in MCP JSON files.
324
- - **Stale config shape**: ensure root keys are correct (`mcpServers` for Copilot CLI, `servers` for VS Code).
325
- - **Rules missing**: ensure `.github/copilot-instructions.md` and the companion `SKILL.md` exist.
326
- - **Context discipline not followed**: first turn must call `init(...)` then `context(...)`; subsequent turns should call `context(...)` first.
327
- - **Indexing not ready**: after setup, allow indexing to complete; retry `search(mode="auto", ...)` before falling back to local scans.
328
-
329
- ## Migration Notes
330
-
331
- - If you previously configured only one path, migrate to the canonical setup above.
332
- - If migrating from Node to Rust MCP, update both command fields (`~/.copilot/mcp-config.json` and `.vscode/mcp.json`) from `npx ... @contextstream/mcp-server@latest` to `contextstream-mcp`.
333
- - If older rules exist, regenerate rules and replace stale instructions with the current Copilot files.
334
- - Preserve other MCP servers in your JSON files; only update the `contextstream` entry.
335
-
336
- ## Marketplace + Rust MCP Feasibility
337
-
338
- - Current `server.json` marketplace metadata for this package points to npm install, which is why one-click install resolves to Node MCP.
339
- - Marketplace clients do not execute external bootstrap scripts during package install, so they cannot trigger Rust install commands directly.
340
- - Once Rust MCP is published in a marketplace-supported package form (for example an additional registry package entry), `server.json` can expose both Node and Rust install targets for true one-click runtime choice.
341
-
342
- ## Hook Coverage Matrix (Claude, Cursor, Antigravity)
343
-
344
- | Editor | Hook support | ContextStream strategy |
345
- |--------|--------------|------------------------|
346
- | Claude Code | Full lifecycle hooks | Hard enforcement + reminders + lifecycle persistence hooks |
347
- | Cursor | Lifecycle hooks (tool/MCP/shell/file/session) | Hard enforcement + reminder hooks + post-action indexing hooks |
348
- | Windsurf | Cascade hooks (pre/post tool and response events) | Hard enforcement via pre hooks + post-write/session hooks |
349
- | Antigravity | No documented lifecycle hooks | Strict rules-first flow + no-hooks operational guardrails |
350
-
351
- ## Troubleshooting (ContextStream was skipped)
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
 
@@ -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 = path.resolve(cwd);
158
+ let searchDir = path2.resolve(cwd);
15
159
  for (let i = 0; i < 5; i++) {
16
160
  if (!API_KEY) {
17
- const mcpPath = path.join(searchDir, ".mcp.json");
18
- if (fs.existsSync(mcpPath)) {
161
+ const mcpPath = path2.join(searchDir, ".mcp.json");
162
+ if (fs2.existsSync(mcpPath)) {
19
163
  try {
20
- const content = fs.readFileSync(mcpPath, "utf-8");
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 = path.join(searchDir, ".contextstream", "config.json");
35
- if (fs.existsSync(csConfigPath)) {
178
+ const csConfigPath = path2.join(searchDir, ".contextstream", "config.json");
179
+ if (fs2.existsSync(csConfigPath)) {
36
180
  try {
37
- const content = fs.readFileSync(csConfigPath, "utf-8");
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 = path.dirname(searchDir);
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 = path.join(homedir(), ".mcp.json");
52
- if (fs.existsSync(homeMcpPath)) {
195
+ const homeMcpPath = path2.join(homedir2(), ".mcp.json");
196
+ if (fs2.existsSync(homeMcpPath)) {
53
197
  try {
54
- const content = fs.readFileSync(homeMcpPath, "utf-8");
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 || "";