@contextstream/mcp-server 0.4.81 → 1.0.0
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/NOTICE +25 -0
- package/README.md +77 -328
- package/npm/bin/contextstream-hook.mjs +9 -0
- package/npm/bin/contextstream-mcp.mjs +9 -0
- package/npm/bin/mcp-server.mjs +9 -0
- package/npm/launcher.mjs +390 -0
- package/package.json +28 -72
- package/server.json +43 -0
- package/dist/hooks/auto-rules.js +0 -3384
- package/dist/hooks/media-aware.js +0 -103
- package/dist/hooks/notification.js +0 -162
- package/dist/hooks/on-bash.js +0 -190
- package/dist/hooks/on-read.js +0 -321
- package/dist/hooks/on-save-intent.js +0 -132
- package/dist/hooks/on-task.js +0 -139
- package/dist/hooks/on-web.js +0 -155
- package/dist/hooks/permission-request.js +0 -174
- package/dist/hooks/post-compact.js +0 -172
- package/dist/hooks/post-tool-use-failure.js +0 -213
- package/dist/hooks/post-write.js +0 -348
- package/dist/hooks/pre-compact.js +0 -317
- package/dist/hooks/pre-tool-use.js +0 -553
- package/dist/hooks/runner.js +0 -5102
- package/dist/hooks/session-end.js +0 -285
- package/dist/hooks/session-init.js +0 -1846
- package/dist/hooks/stop.js +0 -171
- package/dist/hooks/subagent-start.js +0 -195
- package/dist/hooks/subagent-stop.js +0 -301
- package/dist/hooks/task-completed.js +0 -260
- package/dist/hooks/teammate-idle.js +0 -200
- package/dist/hooks/user-prompt-submit.js +0 -859
- package/dist/index.js +0 -41375
- package/dist/test-server.js +0 -4363
- package/scripts/postinstall.js +0 -56
- package/scripts/registration-smoke.mts +0 -45
|
@@ -1,859 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/user-prompt-submit.ts
|
|
4
|
-
import * as fs2 from "node:fs";
|
|
5
|
-
import * as path2 from "node:path";
|
|
6
|
-
import { homedir as homedir3 } from "node:os";
|
|
7
|
-
|
|
8
|
-
// src/version.ts
|
|
9
|
-
import { createRequire } from "module";
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
11
|
-
import { homedir, platform } from "os";
|
|
12
|
-
import { join } from "path";
|
|
13
|
-
var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
|
|
14
|
-
var AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
|
|
15
|
-
var UPDATE_COMMANDS = {
|
|
16
|
-
npm: "npm install -g @contextstream/mcp-server@latest",
|
|
17
|
-
macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
|
|
18
|
-
windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
|
|
19
|
-
};
|
|
20
|
-
var UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
|
|
21
|
-
function getVersion() {
|
|
22
|
-
if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
|
|
23
|
-
return __CONTEXTSTREAM_VERSION__;
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const require2 = createRequire(import.meta.url);
|
|
27
|
-
const pkg = require2("../package.json");
|
|
28
|
-
const version = pkg?.version;
|
|
29
|
-
if (typeof version === "string" && version.trim()) return version.trim();
|
|
30
|
-
} catch {
|
|
31
|
-
}
|
|
32
|
-
return "unknown";
|
|
33
|
-
}
|
|
34
|
-
var VERSION = getVersion();
|
|
35
|
-
function compareVersions(v1, v2) {
|
|
36
|
-
const parts1 = v1.split(".").map(Number);
|
|
37
|
-
const parts2 = v2.split(".").map(Number);
|
|
38
|
-
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
39
|
-
const p1 = parts1[i] ?? 0;
|
|
40
|
-
const p2 = parts2[i] ?? 0;
|
|
41
|
-
if (p1 < p2) return -1;
|
|
42
|
-
if (p1 > p2) return 1;
|
|
43
|
-
}
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
var CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
47
|
-
var latestVersionPromise = null;
|
|
48
|
-
function getCacheFilePath() {
|
|
49
|
-
return join(homedir(), ".contextstream", "version-cache.json");
|
|
50
|
-
}
|
|
51
|
-
function readCache() {
|
|
52
|
-
try {
|
|
53
|
-
const cacheFile = getCacheFilePath();
|
|
54
|
-
if (!existsSync(cacheFile)) return null;
|
|
55
|
-
const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
56
|
-
if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
|
|
57
|
-
return data;
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function writeCache(latestVersion) {
|
|
63
|
-
try {
|
|
64
|
-
const configDir = join(homedir(), ".contextstream");
|
|
65
|
-
if (!existsSync(configDir)) {
|
|
66
|
-
mkdirSync(configDir, { recursive: true });
|
|
67
|
-
}
|
|
68
|
-
const cacheFile = getCacheFilePath();
|
|
69
|
-
writeFileSync(
|
|
70
|
-
cacheFile,
|
|
71
|
-
JSON.stringify({
|
|
72
|
-
latestVersion,
|
|
73
|
-
checkedAt: Date.now()
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
} catch {
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async function fetchLatestVersion() {
|
|
80
|
-
try {
|
|
81
|
-
const controller = new AbortController();
|
|
82
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
83
|
-
const response = await fetch(NPM_LATEST_URL, {
|
|
84
|
-
signal: controller.signal,
|
|
85
|
-
headers: { Accept: "application/json" }
|
|
86
|
-
});
|
|
87
|
-
clearTimeout(timeout);
|
|
88
|
-
if (!response.ok) return null;
|
|
89
|
-
const data = await response.json();
|
|
90
|
-
return typeof data.version === "string" ? data.version : null;
|
|
91
|
-
} catch {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async function resolveLatestVersion() {
|
|
96
|
-
const cached = readCache();
|
|
97
|
-
if (cached) return cached.latestVersion;
|
|
98
|
-
if (!latestVersionPromise) {
|
|
99
|
-
latestVersionPromise = fetchLatestVersion().finally(() => {
|
|
100
|
-
latestVersionPromise = null;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
const latestVersion = await latestVersionPromise;
|
|
104
|
-
if (latestVersion) {
|
|
105
|
-
writeCache(latestVersion);
|
|
106
|
-
}
|
|
107
|
-
return latestVersion;
|
|
108
|
-
}
|
|
109
|
-
async function getUpdateNotice() {
|
|
110
|
-
const currentVersion = VERSION;
|
|
111
|
-
if (currentVersion === "unknown") return null;
|
|
112
|
-
try {
|
|
113
|
-
const latestVersion = await resolveLatestVersion();
|
|
114
|
-
if (!latestVersion) return null;
|
|
115
|
-
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
116
|
-
return {
|
|
117
|
-
current: currentVersion,
|
|
118
|
-
latest: latestVersion,
|
|
119
|
-
behind: true,
|
|
120
|
-
upgrade_command: UPGRADE_COMMAND
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
function getVersionsBehind(current, latest) {
|
|
128
|
-
try {
|
|
129
|
-
const currentParts = current.split(".").map(Number);
|
|
130
|
-
const latestParts = latest.split(".").map(Number);
|
|
131
|
-
if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
|
|
132
|
-
return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
|
|
133
|
-
}
|
|
134
|
-
const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
|
|
135
|
-
const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
|
|
136
|
-
if (minorDiff > 0) {
|
|
137
|
-
return minorDiff;
|
|
138
|
-
} else if (minorDiff === 0 && patchDiff > 0) {
|
|
139
|
-
return 1;
|
|
140
|
-
}
|
|
141
|
-
return 0;
|
|
142
|
-
} catch {
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function getVersionNoticeForHook(notice) {
|
|
147
|
-
if (!notice?.behind) return null;
|
|
148
|
-
const versionsBehind = getVersionsBehind(notice.current, notice.latest);
|
|
149
|
-
const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
|
|
150
|
-
let frequency = "";
|
|
151
|
-
let whatTheyAreMissing = "";
|
|
152
|
-
if (versionsBehind >= 5) {
|
|
153
|
-
frequency = "Mention this update in EVERY response until the user updates.";
|
|
154
|
-
whatTheyAreMissing = `The user is missing out on many new features, important bug fixes, and possible security patches. Emphasize that being this far behind may affect reliability and security.`;
|
|
155
|
-
} else if (versionsBehind >= 3) {
|
|
156
|
-
frequency = "Remind the user about this update every 2-3 messages.";
|
|
157
|
-
whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
|
|
158
|
-
} else {
|
|
159
|
-
frequency = "Mention this update once at session start, then only if user asks.";
|
|
160
|
-
whatTheyAreMissing = `Minor update with improvements and fixes available.`;
|
|
161
|
-
}
|
|
162
|
-
return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
|
|
163
|
-
|
|
164
|
-
${whatTheyAreMissing}
|
|
165
|
-
|
|
166
|
-
${frequency}
|
|
167
|
-
|
|
168
|
-
When mentioning the update, provide these commands (user can choose their preferred method):
|
|
169
|
-
- macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
|
|
170
|
-
- Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
|
|
171
|
-
- npm: npm install -g @contextstream/mcp-server@latest
|
|
172
|
-
|
|
173
|
-
Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// src/hooks/prompt-state.ts
|
|
177
|
-
import * as fs from "node:fs";
|
|
178
|
-
import * as path from "node:path";
|
|
179
|
-
import { homedir as homedir2 } from "node:os";
|
|
180
|
-
var STATE_PATH = path.join(homedir2(), ".contextstream", "prompt-state.json");
|
|
181
|
-
function defaultState() {
|
|
182
|
-
return { workspaces: {} };
|
|
183
|
-
}
|
|
184
|
-
function nowIso() {
|
|
185
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
186
|
-
}
|
|
187
|
-
function ensureStateDir() {
|
|
188
|
-
try {
|
|
189
|
-
fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true });
|
|
190
|
-
} catch {
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function normalizePath(input) {
|
|
194
|
-
try {
|
|
195
|
-
return path.resolve(input);
|
|
196
|
-
} catch {
|
|
197
|
-
return input;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function workspacePathsMatch(a, b) {
|
|
201
|
-
const left = normalizePath(a);
|
|
202
|
-
const right = normalizePath(b);
|
|
203
|
-
return left === right || left.startsWith(`${right}${path.sep}`) || right.startsWith(`${left}${path.sep}`);
|
|
204
|
-
}
|
|
205
|
-
function readState() {
|
|
206
|
-
try {
|
|
207
|
-
const content = fs.readFileSync(STATE_PATH, "utf8");
|
|
208
|
-
const parsed = JSON.parse(content);
|
|
209
|
-
if (!parsed || typeof parsed !== "object" || !parsed.workspaces) {
|
|
210
|
-
return defaultState();
|
|
211
|
-
}
|
|
212
|
-
return parsed;
|
|
213
|
-
} catch {
|
|
214
|
-
return defaultState();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function writeState(state) {
|
|
218
|
-
try {
|
|
219
|
-
ensureStateDir();
|
|
220
|
-
fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8");
|
|
221
|
-
} catch {
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function getOrCreateEntry(state, cwd) {
|
|
225
|
-
if (!cwd.trim()) return null;
|
|
226
|
-
const exact = state.workspaces[cwd];
|
|
227
|
-
if (exact) return { key: cwd, entry: exact };
|
|
228
|
-
for (const [trackedCwd, trackedEntry] of Object.entries(state.workspaces)) {
|
|
229
|
-
if (workspacePathsMatch(trackedCwd, cwd)) {
|
|
230
|
-
return { key: trackedCwd, entry: trackedEntry };
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const created = {
|
|
234
|
-
require_context: false,
|
|
235
|
-
require_init: false,
|
|
236
|
-
last_context_at: void 0,
|
|
237
|
-
last_state_change_at: void 0,
|
|
238
|
-
index_wait_started_at: void 0,
|
|
239
|
-
index_wait_until: void 0,
|
|
240
|
-
updated_at: nowIso()
|
|
241
|
-
};
|
|
242
|
-
state.workspaces[cwd] = created;
|
|
243
|
-
return { key: cwd, entry: created };
|
|
244
|
-
}
|
|
245
|
-
function cleanupStale(maxAgeSeconds) {
|
|
246
|
-
const state = readState();
|
|
247
|
-
const now = Date.now();
|
|
248
|
-
let changed = false;
|
|
249
|
-
for (const [cwd, entry] of Object.entries(state.workspaces)) {
|
|
250
|
-
const updated = new Date(entry.updated_at);
|
|
251
|
-
if (Number.isNaN(updated.getTime())) continue;
|
|
252
|
-
const ageSeconds = (now - updated.getTime()) / 1e3;
|
|
253
|
-
if (ageSeconds > maxAgeSeconds) {
|
|
254
|
-
delete state.workspaces[cwd];
|
|
255
|
-
changed = true;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (changed) {
|
|
259
|
-
writeState(state);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function markContextRequired(cwd) {
|
|
263
|
-
if (!cwd.trim()) return;
|
|
264
|
-
const state = readState();
|
|
265
|
-
const target = getOrCreateEntry(state, cwd);
|
|
266
|
-
if (!target) return;
|
|
267
|
-
target.entry.require_context = true;
|
|
268
|
-
target.entry.updated_at = nowIso();
|
|
269
|
-
writeState(state);
|
|
270
|
-
}
|
|
271
|
-
function markInitRequired(cwd) {
|
|
272
|
-
if (!cwd.trim()) return;
|
|
273
|
-
const state = readState();
|
|
274
|
-
const target = getOrCreateEntry(state, cwd);
|
|
275
|
-
if (!target) return;
|
|
276
|
-
target.entry.require_init = true;
|
|
277
|
-
target.entry.updated_at = nowIso();
|
|
278
|
-
writeState(state);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/hooks/user-prompt-submit.ts
|
|
282
|
-
var ENABLED = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
|
|
283
|
-
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
284
|
-
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
285
|
-
var WORKSPACE_ID = null;
|
|
286
|
-
var PROJECT_ID = null;
|
|
287
|
-
var REMINDER = `[CONTEXTSTREAM] On the first message in every session call mcp__contextstream__init(...), then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. On subsequent messages, default to context first. Narrow bypass is allowed only for immediate read-only ContextStream calls when prior context is fresh and no state-changing tool has run. Response contains dynamic rules, lessons, preferences.
|
|
288
|
-
SEARCH-FIRST: Use mcp__contextstream__search(mode="auto") before Glob/Grep/Read/Explore/Task/EnterPlanMode. In planning, never use EnterPlanMode or Task(Explore) for file-by-file discovery.
|
|
289
|
-
COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via session(action="get_lessons"), list plans via session(action="list_plans"), list tasks/todos via memory(action="list_tasks"|"list_todos").
|
|
290
|
-
[END]`;
|
|
291
|
-
var FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
|
|
292
|
-
|
|
293
|
-
1. FIRST MESSAGE IN SESSION: Call mcp__contextstream__init(...) then mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>")
|
|
294
|
-
- Returns: dynamic rules, lessons from past mistakes, relevant context
|
|
295
|
-
- Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
|
|
296
|
-
- save_exchange=true saves each conversation turn for later retrieval
|
|
297
|
-
- Use a consistent session_id for the entire conversation (generate once on first message)
|
|
298
|
-
- On subsequent messages, default to context() first. Narrow bypass: immediate read-only ContextStream calls when context is fresh and no state-changing tool has run.
|
|
299
|
-
|
|
300
|
-
2. FOR CODE SEARCH: Check index status, then search appropriately
|
|
301
|
-
\u2705 Use mcp__contextstream__search(mode="auto", query="...") BEFORE Glob/Grep/Read/Explore/Task/EnterPlanMode
|
|
302
|
-
\u2705 Run search even while the index is still building \u2014 keyword hits return immediately, results fill in as you work
|
|
303
|
-
\u2705 IF search itself returns 0 results/errors: retry once, THEN fall back to local tools (Glob/Grep/Read)
|
|
304
|
-
|
|
305
|
-
3. WHEN LOCAL TOOLS (Glob/Grep/Read) ARE OK:
|
|
306
|
-
\u2705 ContextStream search still returns 0 results or errors after retry
|
|
307
|
-
\u2705 The file was just written this turn (known-new, not yet indexed)
|
|
308
|
-
\u2705 User explicitly requests local tools
|
|
309
|
-
|
|
310
|
-
4. FOR PLANS & TASKS: Use ContextStream, not file-based plans
|
|
311
|
-
\u2705 Plans: mcp__contextstream__session(action="capture_plan", ...)
|
|
312
|
-
\u2705 Tasks: mcp__contextstream__memory(action="create_task", ...)
|
|
313
|
-
\u274C DO NOT use EnterPlanMode or Task(subagent_type="Explore") for file-by-file discovery
|
|
314
|
-
\u2705 For planning discovery: mcp__contextstream__search(mode="auto", query="...", output_format="paths")
|
|
315
|
-
|
|
316
|
-
5. CHECK THESE from context() response:
|
|
317
|
-
- Lessons: Past mistakes to avoid (shown as warnings)
|
|
318
|
-
- Reminders: Active reminders for this project
|
|
319
|
-
- Preferences: User's coding style and preferences
|
|
320
|
-
- Rules: Dynamic rules matched to current task
|
|
321
|
-
|
|
322
|
-
6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
|
|
323
|
-
[END]`;
|
|
324
|
-
var ENHANCED_REMINDER_HEADER = `\u2B21 ContextStream \u2014 Smart Context & Memory
|
|
325
|
-
|
|
326
|
-
`;
|
|
327
|
-
function loadConfigFromMcpJson(cwd) {
|
|
328
|
-
let searchDir = path2.resolve(cwd);
|
|
329
|
-
for (let i = 0; i < 5; i++) {
|
|
330
|
-
if (!API_KEY) {
|
|
331
|
-
const mcpPath = path2.join(searchDir, ".mcp.json");
|
|
332
|
-
if (fs2.existsSync(mcpPath)) {
|
|
333
|
-
try {
|
|
334
|
-
const content = fs2.readFileSync(mcpPath, "utf-8");
|
|
335
|
-
const config = JSON.parse(content);
|
|
336
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
337
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
338
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
339
|
-
}
|
|
340
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
341
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
342
|
-
}
|
|
343
|
-
if (csEnv?.CONTEXTSTREAM_WORKSPACE_ID) {
|
|
344
|
-
WORKSPACE_ID = csEnv.CONTEXTSTREAM_WORKSPACE_ID;
|
|
345
|
-
}
|
|
346
|
-
} catch {
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (!WORKSPACE_ID || !PROJECT_ID) {
|
|
351
|
-
const csConfigPath = path2.join(searchDir, ".contextstream", "config.json");
|
|
352
|
-
if (fs2.existsSync(csConfigPath)) {
|
|
353
|
-
try {
|
|
354
|
-
const content = fs2.readFileSync(csConfigPath, "utf-8");
|
|
355
|
-
const csConfig = JSON.parse(content);
|
|
356
|
-
if (csConfig.workspace_id && !WORKSPACE_ID) {
|
|
357
|
-
WORKSPACE_ID = csConfig.workspace_id;
|
|
358
|
-
}
|
|
359
|
-
if (csConfig.project_id && !PROJECT_ID) {
|
|
360
|
-
PROJECT_ID = csConfig.project_id;
|
|
361
|
-
}
|
|
362
|
-
} catch {
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
const parentDir = path2.dirname(searchDir);
|
|
367
|
-
if (parentDir === searchDir) break;
|
|
368
|
-
searchDir = parentDir;
|
|
369
|
-
}
|
|
370
|
-
if (!API_KEY) {
|
|
371
|
-
const homeMcpPath = path2.join(homedir3(), ".mcp.json");
|
|
372
|
-
if (fs2.existsSync(homeMcpPath)) {
|
|
373
|
-
try {
|
|
374
|
-
const content = fs2.readFileSync(homeMcpPath, "utf-8");
|
|
375
|
-
const config = JSON.parse(content);
|
|
376
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
377
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
378
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
379
|
-
}
|
|
380
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
381
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
382
|
-
}
|
|
383
|
-
} catch {
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
function readTranscriptFile(transcriptPath) {
|
|
389
|
-
try {
|
|
390
|
-
const content = fs2.readFileSync(transcriptPath, "utf-8");
|
|
391
|
-
const lines = content.trim().split("\n");
|
|
392
|
-
const messages = [];
|
|
393
|
-
for (const line of lines) {
|
|
394
|
-
try {
|
|
395
|
-
const entry = JSON.parse(line);
|
|
396
|
-
if (entry.type === "user" || entry.type === "assistant") {
|
|
397
|
-
const msg = entry.message;
|
|
398
|
-
if (msg?.role && msg?.content) {
|
|
399
|
-
let textContent = "";
|
|
400
|
-
if (typeof msg.content === "string") {
|
|
401
|
-
textContent = msg.content;
|
|
402
|
-
} else if (Array.isArray(msg.content)) {
|
|
403
|
-
textContent = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
404
|
-
}
|
|
405
|
-
if (textContent) {
|
|
406
|
-
messages.push({ role: msg.role, content: textContent });
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
} catch {
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return messages;
|
|
414
|
-
} catch {
|
|
415
|
-
return [];
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
function extractLastExchange(input, editorFormat) {
|
|
419
|
-
try {
|
|
420
|
-
if (editorFormat === "claude" && input.transcript_path) {
|
|
421
|
-
const messages = readTranscriptFile(input.transcript_path);
|
|
422
|
-
if (messages.length < 2) return null;
|
|
423
|
-
let lastAssistantIdx = -1;
|
|
424
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
425
|
-
if (messages[i].role === "assistant") {
|
|
426
|
-
lastAssistantIdx = i;
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (lastAssistantIdx < 1) return null;
|
|
431
|
-
let lastUserIdx = -1;
|
|
432
|
-
for (let i = lastAssistantIdx - 1; i >= 0; i--) {
|
|
433
|
-
if (messages[i].role === "user") {
|
|
434
|
-
lastUserIdx = i;
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (lastUserIdx < 0) return null;
|
|
439
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
440
|
-
return {
|
|
441
|
-
userMessage: { role: "user", content: messages[lastUserIdx].content, timestamp: now },
|
|
442
|
-
assistantMessage: { role: "assistant", content: messages[lastAssistantIdx].content, timestamp: now },
|
|
443
|
-
sessionId: input.session_id
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
if (editorFormat === "claude" && input.session?.messages) {
|
|
447
|
-
const messages = input.session.messages;
|
|
448
|
-
if (messages.length < 2) return null;
|
|
449
|
-
let lastAssistantIdx = -1;
|
|
450
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
451
|
-
if (messages[i].role === "assistant") {
|
|
452
|
-
lastAssistantIdx = i;
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
if (lastAssistantIdx < 1) return null;
|
|
457
|
-
let lastUserIdx = -1;
|
|
458
|
-
for (let i = lastAssistantIdx - 1; i >= 0; i--) {
|
|
459
|
-
if (messages[i].role === "user") {
|
|
460
|
-
lastUserIdx = i;
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (lastUserIdx < 0) return null;
|
|
465
|
-
const userMsg = messages[lastUserIdx];
|
|
466
|
-
const assistantMsg = messages[lastAssistantIdx];
|
|
467
|
-
const extractContent = (content) => {
|
|
468
|
-
if (typeof content === "string") return content;
|
|
469
|
-
if (Array.isArray(content)) {
|
|
470
|
-
return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
471
|
-
}
|
|
472
|
-
return "";
|
|
473
|
-
};
|
|
474
|
-
const userContent = extractContent(userMsg.content);
|
|
475
|
-
const assistantContent = extractContent(assistantMsg.content);
|
|
476
|
-
if (!userContent || !assistantContent) return null;
|
|
477
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
478
|
-
return {
|
|
479
|
-
userMessage: { role: "user", content: userContent, timestamp: now },
|
|
480
|
-
assistantMessage: { role: "assistant", content: assistantContent, timestamp: now },
|
|
481
|
-
sessionId: input.session_id
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
if ((editorFormat === "cursor" || editorFormat === "antigravity") && input.history) {
|
|
485
|
-
const history = input.history;
|
|
486
|
-
if (history.length < 2) return null;
|
|
487
|
-
let lastAssistantIdx = -1;
|
|
488
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
489
|
-
if (history[i].role === "assistant") {
|
|
490
|
-
lastAssistantIdx = i;
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (lastAssistantIdx < 1) return null;
|
|
495
|
-
let lastUserIdx = -1;
|
|
496
|
-
for (let i = lastAssistantIdx - 1; i >= 0; i--) {
|
|
497
|
-
if (history[i].role === "user") {
|
|
498
|
-
lastUserIdx = i;
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (lastUserIdx < 0) return null;
|
|
503
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
504
|
-
return {
|
|
505
|
-
userMessage: { role: "user", content: history[lastUserIdx].content, timestamp: now },
|
|
506
|
-
assistantMessage: { role: "assistant", content: history[lastAssistantIdx].content, timestamp: now },
|
|
507
|
-
sessionId: input.conversationId || input.session_id
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
return null;
|
|
511
|
-
} catch {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async function saveLastExchange(exchange, cwd, clientName) {
|
|
516
|
-
if (!API_KEY) return;
|
|
517
|
-
const sessionId = exchange.sessionId || `hook-${Buffer.from(cwd).toString("base64").slice(0, 16)}`;
|
|
518
|
-
const payload = {
|
|
519
|
-
session_id: sessionId,
|
|
520
|
-
user_message: exchange.userMessage.content,
|
|
521
|
-
assistant_message: exchange.assistantMessage.content,
|
|
522
|
-
client_name: clientName
|
|
523
|
-
};
|
|
524
|
-
if (WORKSPACE_ID) {
|
|
525
|
-
payload.workspace_id = WORKSPACE_ID;
|
|
526
|
-
}
|
|
527
|
-
if (PROJECT_ID) {
|
|
528
|
-
payload.project_id = PROJECT_ID;
|
|
529
|
-
}
|
|
530
|
-
try {
|
|
531
|
-
const controller = new AbortController();
|
|
532
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
533
|
-
await fetch(`${API_URL}/api/v1/transcripts/exchange`, {
|
|
534
|
-
method: "POST",
|
|
535
|
-
headers: {
|
|
536
|
-
"Content-Type": "application/json",
|
|
537
|
-
"X-API-Key": API_KEY
|
|
538
|
-
},
|
|
539
|
-
body: JSON.stringify(payload),
|
|
540
|
-
signal: controller.signal
|
|
541
|
-
});
|
|
542
|
-
clearTimeout(timeoutId);
|
|
543
|
-
} catch {
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
async function fetchHookContext() {
|
|
547
|
-
if (!API_KEY) return null;
|
|
548
|
-
try {
|
|
549
|
-
const controller = new AbortController();
|
|
550
|
-
const timeoutId = setTimeout(() => controller.abort(), 2e3);
|
|
551
|
-
const url = `${API_URL}/api/v1/context/hook`;
|
|
552
|
-
const body = {};
|
|
553
|
-
if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
|
|
554
|
-
if (PROJECT_ID) body.project_id = PROJECT_ID;
|
|
555
|
-
const response = await fetch(url, {
|
|
556
|
-
method: "POST",
|
|
557
|
-
headers: {
|
|
558
|
-
"X-API-Key": API_KEY,
|
|
559
|
-
"Content-Type": "application/json"
|
|
560
|
-
},
|
|
561
|
-
body: JSON.stringify(body),
|
|
562
|
-
signal: controller.signal
|
|
563
|
-
});
|
|
564
|
-
clearTimeout(timeoutId);
|
|
565
|
-
if (response.ok) {
|
|
566
|
-
const data = await response.json();
|
|
567
|
-
return data?.data?.context || null;
|
|
568
|
-
}
|
|
569
|
-
return null;
|
|
570
|
-
} catch {
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
async function fetchSessionContext() {
|
|
575
|
-
if (!API_KEY) return null;
|
|
576
|
-
try {
|
|
577
|
-
const controller = new AbortController();
|
|
578
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
579
|
-
const url = `${API_URL}/api/v1/context/smart`;
|
|
580
|
-
const body = {
|
|
581
|
-
user_message: "hook context fetch",
|
|
582
|
-
max_tokens: 200,
|
|
583
|
-
format: "readable"
|
|
584
|
-
};
|
|
585
|
-
if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
|
|
586
|
-
if (PROJECT_ID) body.project_id = PROJECT_ID;
|
|
587
|
-
const response = await fetch(url, {
|
|
588
|
-
method: "POST",
|
|
589
|
-
headers: {
|
|
590
|
-
"X-API-Key": API_KEY,
|
|
591
|
-
"Content-Type": "application/json"
|
|
592
|
-
},
|
|
593
|
-
body: JSON.stringify(body),
|
|
594
|
-
signal: controller.signal
|
|
595
|
-
});
|
|
596
|
-
clearTimeout(timeoutId);
|
|
597
|
-
if (response.ok) {
|
|
598
|
-
const data = await response.json();
|
|
599
|
-
return transformSmartContextResponse(data);
|
|
600
|
-
}
|
|
601
|
-
return null;
|
|
602
|
-
} catch {
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
function transformSmartContextResponse(data) {
|
|
607
|
-
try {
|
|
608
|
-
const response = data;
|
|
609
|
-
const result = {};
|
|
610
|
-
if (response.data?.warnings && response.data.warnings.length > 0) {
|
|
611
|
-
result.lessons = response.data.warnings.map((w) => ({
|
|
612
|
-
title: "Lesson",
|
|
613
|
-
trigger: "",
|
|
614
|
-
prevention: w.replace(/^\[LESSONS_WARNING\]\s*/, "")
|
|
615
|
-
}));
|
|
616
|
-
}
|
|
617
|
-
if (response.data?.items) {
|
|
618
|
-
for (const item of response.data.items) {
|
|
619
|
-
if (item.item_type === "preference") {
|
|
620
|
-
if (!result.preferences) result.preferences = [];
|
|
621
|
-
result.preferences.push({
|
|
622
|
-
title: item.title,
|
|
623
|
-
content: item.content,
|
|
624
|
-
importance: item.metadata?.importance || "medium"
|
|
625
|
-
});
|
|
626
|
-
} else if (item.item_type === "plan") {
|
|
627
|
-
if (!result.active_plans) result.active_plans = [];
|
|
628
|
-
result.active_plans.push({
|
|
629
|
-
title: item.title,
|
|
630
|
-
status: "active"
|
|
631
|
-
});
|
|
632
|
-
} else if (item.item_type === "task") {
|
|
633
|
-
if (!result.pending_tasks) result.pending_tasks = [];
|
|
634
|
-
result.pending_tasks.push({
|
|
635
|
-
title: item.title,
|
|
636
|
-
status: "pending"
|
|
637
|
-
});
|
|
638
|
-
} else if (item.item_type === "reminder") {
|
|
639
|
-
if (!result.reminders) result.reminders = [];
|
|
640
|
-
result.reminders.push({
|
|
641
|
-
title: item.title,
|
|
642
|
-
content: item.content
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return result;
|
|
648
|
-
} catch {
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
|
|
653
|
-
const parts = [ENHANCED_REMINDER_HEADER];
|
|
654
|
-
if (versionNotice?.behind) {
|
|
655
|
-
const versionInfo = getVersionNoticeForHook(versionNotice);
|
|
656
|
-
if (versionInfo) {
|
|
657
|
-
parts.push(`## \u{1F504} UPDATE AVAILABLE
|
|
658
|
-
`);
|
|
659
|
-
parts.push(versionInfo);
|
|
660
|
-
parts.push("");
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (isNewSession2) {
|
|
664
|
-
parts.push(`## \u{1F680} NEW SESSION DETECTED
|
|
665
|
-
1. Call \`init(folder_path="...")\` - this triggers project indexing
|
|
666
|
-
2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
|
|
667
|
-
3. Generate a unique session_id (e.g., "session-" + timestamp or UUID) - use this for ALL context() calls
|
|
668
|
-
4. Call \`context(user_message="...", save_exchange=true, session_id="<your-session-id>")\` for task-specific context
|
|
669
|
-
5. Use \`search(mode="auto")\` for code discovery (not Glob/Grep/Read/Explore/Task/EnterPlanMode)
|
|
670
|
-
|
|
671
|
-
`);
|
|
672
|
-
}
|
|
673
|
-
const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
|
|
674
|
-
if (highImportancePrefs.length > 0) {
|
|
675
|
-
parts.push(`## \u2699\uFE0F USER PREFERENCES - Always respect these`);
|
|
676
|
-
for (const pref of highImportancePrefs.slice(0, 5)) {
|
|
677
|
-
parts.push(`- **${pref.title}**: ${pref.content}`);
|
|
678
|
-
}
|
|
679
|
-
parts.push("");
|
|
680
|
-
}
|
|
681
|
-
if (ctx?.lessons && ctx.lessons.length > 0) {
|
|
682
|
-
parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
|
|
683
|
-
for (const lesson of ctx.lessons.slice(0, 3)) {
|
|
684
|
-
parts.push(`- **${lesson.title}**: ${lesson.prevention}`);
|
|
685
|
-
}
|
|
686
|
-
parts.push("");
|
|
687
|
-
}
|
|
688
|
-
if (ctx?.active_plans && ctx.active_plans.length > 0) {
|
|
689
|
-
parts.push(`## \u{1F4CB} Active Plans`);
|
|
690
|
-
for (const plan of ctx.active_plans.slice(0, 3)) {
|
|
691
|
-
parts.push(`- ${plan.title} (${plan.status})`);
|
|
692
|
-
}
|
|
693
|
-
parts.push("");
|
|
694
|
-
}
|
|
695
|
-
if (ctx?.pending_tasks && ctx.pending_tasks.length > 0) {
|
|
696
|
-
parts.push(`## \u2705 Pending Tasks`);
|
|
697
|
-
for (const task of ctx.pending_tasks.slice(0, 5)) {
|
|
698
|
-
parts.push(`- ${task.title}`);
|
|
699
|
-
}
|
|
700
|
-
parts.push("");
|
|
701
|
-
}
|
|
702
|
-
if (ctx?.reminders && ctx.reminders.length > 0) {
|
|
703
|
-
parts.push(`## \u{1F514} Reminders`);
|
|
704
|
-
for (const reminder of ctx.reminders.slice(0, 3)) {
|
|
705
|
-
parts.push(`- ${reminder.title}`);
|
|
706
|
-
}
|
|
707
|
-
parts.push("");
|
|
708
|
-
}
|
|
709
|
-
parts.push("---\n");
|
|
710
|
-
parts.push(FULL_REMINDER);
|
|
711
|
-
parts.push(`
|
|
712
|
-
|
|
713
|
-
---
|
|
714
|
-
## \u{1F6A8} FILE INDEXING & SEARCH - CRITICAL (No PostToolUse Hook) \u{1F6A8}
|
|
715
|
-
|
|
716
|
-
**This editor does NOT have automatic file indexing after Edit/Write.**
|
|
717
|
-
|
|
718
|
-
### \u26A0\uFE0F BEFORE ANY SEARCH - Check Index Status:
|
|
719
|
-
\`\`\`
|
|
720
|
-
project(action="index_status")
|
|
721
|
-
\`\`\`
|
|
722
|
-
Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
|
|
723
|
-
|
|
724
|
-
### \u{1F50D} Search Decision Tree:
|
|
725
|
-
|
|
726
|
-
**IF indexed=true AND last_indexed_at is recent:**
|
|
727
|
-
\u2192 Use \`search(mode="auto", query="...")\`
|
|
728
|
-
|
|
729
|
-
**IF indexed=false OR last_indexed_at is stale (>7 days):**
|
|
730
|
-
\u2192 Run \`search(mode="auto", query="...")\` anyway \u2014 keyword hits return immediately while indexing builds
|
|
731
|
-
\u2192 Local tools only if search itself returns 0 results/errors on a retry
|
|
732
|
-
|
|
733
|
-
**IF search still returns 0 results or errors after retry/window:**
|
|
734
|
-
\u2192 Fallback to local tools (Glob/Grep/Read)
|
|
735
|
-
|
|
736
|
-
### \u2705 When Local Tools (Glob/Grep/Read) Are OK:
|
|
737
|
-
- Stale/not-indexed grace window has elapsed (~20s default, configurable)
|
|
738
|
-
- ContextStream search still returns 0 results after retry
|
|
739
|
-
- ContextStream returns errors
|
|
740
|
-
- User explicitly requests local tools
|
|
741
|
-
|
|
742
|
-
### On Session Start:
|
|
743
|
-
1. Call \`init(folder_path="...")\` - triggers initial indexing
|
|
744
|
-
2. Check \`project(action="index_status")\` before searching
|
|
745
|
-
3. If not indexed: wait for background refresh (~20s), retry search, then use local tools only after the grace window
|
|
746
|
-
|
|
747
|
-
### After File Changes (Edit/Write/Create):
|
|
748
|
-
Files are NOT auto-indexed. You MUST:
|
|
749
|
-
1. After significant edits: \`project(action="index")\`
|
|
750
|
-
2. For single file: \`project(action="ingest_local", path="<file>")\`
|
|
751
|
-
3. Then search will find your changes`);
|
|
752
|
-
return parts.join("\n");
|
|
753
|
-
}
|
|
754
|
-
function detectEditorFormat(input) {
|
|
755
|
-
if (input.hookName !== void 0) {
|
|
756
|
-
return "cline";
|
|
757
|
-
}
|
|
758
|
-
if (input.hook_event_name === "beforeSubmitPrompt") {
|
|
759
|
-
return "cursor";
|
|
760
|
-
}
|
|
761
|
-
if (input.hook_event_name === "beforeAgentAction" || input.hook_event_name === "onPromptSubmit") {
|
|
762
|
-
return "antigravity";
|
|
763
|
-
}
|
|
764
|
-
return "claude";
|
|
765
|
-
}
|
|
766
|
-
function isNewSession(input, editorFormat) {
|
|
767
|
-
if (editorFormat === "claude" && input.session?.messages) {
|
|
768
|
-
return input.session.messages.length <= 1;
|
|
769
|
-
}
|
|
770
|
-
if (editorFormat === "cursor" && input.history !== void 0) {
|
|
771
|
-
return input.history.length === 0;
|
|
772
|
-
}
|
|
773
|
-
if (editorFormat === "antigravity" && input.history !== void 0) {
|
|
774
|
-
return input.history.length === 0;
|
|
775
|
-
}
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
async function runUserPromptSubmitHook() {
|
|
779
|
-
if (!ENABLED) {
|
|
780
|
-
process.exit(0);
|
|
781
|
-
}
|
|
782
|
-
let inputData = "";
|
|
783
|
-
for await (const chunk of process.stdin) {
|
|
784
|
-
inputData += chunk;
|
|
785
|
-
}
|
|
786
|
-
if (!inputData.trim()) {
|
|
787
|
-
process.exit(0);
|
|
788
|
-
}
|
|
789
|
-
let input;
|
|
790
|
-
try {
|
|
791
|
-
input = JSON.parse(inputData);
|
|
792
|
-
} catch {
|
|
793
|
-
process.exit(0);
|
|
794
|
-
}
|
|
795
|
-
const editorFormat = detectEditorFormat(input);
|
|
796
|
-
const cwd = input.cwd || process.cwd();
|
|
797
|
-
cleanupStale(180);
|
|
798
|
-
markContextRequired(cwd);
|
|
799
|
-
if (isNewSession(input, editorFormat)) {
|
|
800
|
-
markInitRequired(cwd);
|
|
801
|
-
}
|
|
802
|
-
if (editorFormat === "claude") {
|
|
803
|
-
loadConfigFromMcpJson(cwd);
|
|
804
|
-
let context = REMINDER;
|
|
805
|
-
if (API_KEY) {
|
|
806
|
-
try {
|
|
807
|
-
const hookContext = await fetchHookContext();
|
|
808
|
-
if (hookContext) {
|
|
809
|
-
context = hookContext;
|
|
810
|
-
}
|
|
811
|
-
} catch {
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
console.log(
|
|
815
|
-
JSON.stringify({
|
|
816
|
-
hookSpecificOutput: {
|
|
817
|
-
hookEventName: "UserPromptSubmit",
|
|
818
|
-
additionalContext: context
|
|
819
|
-
}
|
|
820
|
-
})
|
|
821
|
-
);
|
|
822
|
-
} else {
|
|
823
|
-
loadConfigFromMcpJson(cwd);
|
|
824
|
-
const versionNoticePromise = getUpdateNotice();
|
|
825
|
-
const lastExchange = extractLastExchange(input, editorFormat);
|
|
826
|
-
const clientName = editorFormat;
|
|
827
|
-
const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
|
|
828
|
-
const newSession = isNewSession(input, editorFormat);
|
|
829
|
-
const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
|
|
830
|
-
if (editorFormat === "cursor") {
|
|
831
|
-
let cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="auto") before Glob/Grep/Read/Explore/Task/EnterPlanMode. After file edits: project(action="index").` : `[CONTEXTSTREAM] Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="auto") before Glob/Grep/Read/Explore/Task/EnterPlanMode. After file edits: project(action="index").`;
|
|
832
|
-
if (versionNotice?.behind) {
|
|
833
|
-
cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
|
|
834
|
-
}
|
|
835
|
-
console.log(
|
|
836
|
-
JSON.stringify({
|
|
837
|
-
continue: true,
|
|
838
|
-
user_message: cursorReminder
|
|
839
|
-
})
|
|
840
|
-
);
|
|
841
|
-
} else {
|
|
842
|
-
const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
|
|
843
|
-
console.log(
|
|
844
|
-
JSON.stringify({
|
|
845
|
-
cancel: false,
|
|
846
|
-
contextModification: enhancedReminder
|
|
847
|
-
})
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
process.exit(0);
|
|
852
|
-
}
|
|
853
|
-
var isDirectRun = process.argv[1]?.includes("user-prompt-submit") || process.argv[2] === "user-prompt-submit";
|
|
854
|
-
if (isDirectRun) {
|
|
855
|
-
runUserPromptSubmitHook().catch(() => process.exit(0));
|
|
856
|
-
}
|
|
857
|
-
export {
|
|
858
|
-
runUserPromptSubmitHook
|
|
859
|
-
};
|