@contextstream/mcp-server 0.4.61 → 0.4.63
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/hooks/auto-rules.js +1 -1
- package/dist/hooks/post-write.js +1 -1
- package/dist/hooks/pre-tool-use.js +308 -32
- package/dist/hooks/runner.js +714 -380
- package/dist/hooks/session-init.js +168 -48
- package/dist/hooks/user-prompt-submit.js +127 -17
- package/dist/index.js +1932 -413
- package/package.json +1 -1
package/dist/hooks/auto-rules.js
CHANGED
|
@@ -1145,7 +1145,7 @@ var init_files = __esm({
|
|
|
1145
1145
|
"Gemfile.lock",
|
|
1146
1146
|
"composer.lock"
|
|
1147
1147
|
]);
|
|
1148
|
-
MAX_FILE_SIZE = 1024 * 1024;
|
|
1148
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
1149
1149
|
MAX_BATCH_BYTES = 10 * 1024 * 1024;
|
|
1150
1150
|
LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
|
|
1151
1151
|
MAX_FILES_PER_BATCH = 200;
|
package/dist/hooks/post-write.js
CHANGED
|
@@ -74,7 +74,7 @@ var INDEXABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
74
74
|
".prisma",
|
|
75
75
|
".proto"
|
|
76
76
|
]);
|
|
77
|
-
var MAX_FILE_SIZE = 1024 * 1024;
|
|
77
|
+
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
78
78
|
function extractFilePath(input) {
|
|
79
79
|
if (input.tool_input) {
|
|
80
80
|
const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
|
|
@@ -1,13 +1,159 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/hooks/pre-tool-use.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/hooks/prompt-state.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 STATE_PATH = path.join(homedir(), ".contextstream", "prompt-state.json");
|
|
13
|
+
function defaultState() {
|
|
14
|
+
return { workspaces: {} };
|
|
15
|
+
}
|
|
16
|
+
function nowIso() {
|
|
17
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
18
|
+
}
|
|
19
|
+
function ensureStateDir() {
|
|
20
|
+
try {
|
|
21
|
+
fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true });
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function normalizePath(input) {
|
|
26
|
+
try {
|
|
27
|
+
return path.resolve(input);
|
|
28
|
+
} catch {
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function workspacePathsMatch(a, b) {
|
|
33
|
+
const left = normalizePath(a);
|
|
34
|
+
const right = normalizePath(b);
|
|
35
|
+
return left === right || left.startsWith(`${right}${path.sep}`) || right.startsWith(`${left}${path.sep}`);
|
|
36
|
+
}
|
|
37
|
+
function readState() {
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(STATE_PATH, "utf8");
|
|
40
|
+
const parsed = JSON.parse(content);
|
|
41
|
+
if (!parsed || typeof parsed !== "object" || !parsed.workspaces) {
|
|
42
|
+
return defaultState();
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
} catch {
|
|
46
|
+
return defaultState();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function writeState(state) {
|
|
50
|
+
try {
|
|
51
|
+
ensureStateDir();
|
|
52
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8");
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getOrCreateEntry(state, cwd) {
|
|
57
|
+
if (!cwd.trim()) return null;
|
|
58
|
+
const exact = state.workspaces[cwd];
|
|
59
|
+
if (exact) return { key: cwd, entry: exact };
|
|
60
|
+
for (const [trackedCwd, trackedEntry] of Object.entries(state.workspaces)) {
|
|
61
|
+
if (workspacePathsMatch(trackedCwd, cwd)) {
|
|
62
|
+
return { key: trackedCwd, entry: trackedEntry };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const created = {
|
|
66
|
+
require_context: false,
|
|
67
|
+
require_init: false,
|
|
68
|
+
last_context_at: void 0,
|
|
69
|
+
last_state_change_at: void 0,
|
|
70
|
+
updated_at: nowIso()
|
|
71
|
+
};
|
|
72
|
+
state.workspaces[cwd] = created;
|
|
73
|
+
return { key: cwd, entry: created };
|
|
74
|
+
}
|
|
75
|
+
function cleanupStale(maxAgeSeconds) {
|
|
76
|
+
const state = readState();
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
let changed = false;
|
|
79
|
+
for (const [cwd, entry] of Object.entries(state.workspaces)) {
|
|
80
|
+
const updated = new Date(entry.updated_at);
|
|
81
|
+
if (Number.isNaN(updated.getTime())) continue;
|
|
82
|
+
const ageSeconds = (now - updated.getTime()) / 1e3;
|
|
83
|
+
if (ageSeconds > maxAgeSeconds) {
|
|
84
|
+
delete state.workspaces[cwd];
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (changed) {
|
|
89
|
+
writeState(state);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function clearContextRequired(cwd) {
|
|
93
|
+
if (!cwd.trim()) return;
|
|
94
|
+
const state = readState();
|
|
95
|
+
const target = getOrCreateEntry(state, cwd);
|
|
96
|
+
if (!target) return;
|
|
97
|
+
target.entry.require_context = false;
|
|
98
|
+
target.entry.last_context_at = nowIso();
|
|
99
|
+
target.entry.updated_at = nowIso();
|
|
100
|
+
writeState(state);
|
|
101
|
+
}
|
|
102
|
+
function isContextRequired(cwd) {
|
|
103
|
+
if (!cwd.trim()) return false;
|
|
104
|
+
const state = readState();
|
|
105
|
+
const target = getOrCreateEntry(state, cwd);
|
|
106
|
+
return Boolean(target?.entry.require_context);
|
|
107
|
+
}
|
|
108
|
+
function clearInitRequired(cwd) {
|
|
109
|
+
if (!cwd.trim()) return;
|
|
110
|
+
const state = readState();
|
|
111
|
+
const target = getOrCreateEntry(state, cwd);
|
|
112
|
+
if (!target) return;
|
|
113
|
+
target.entry.require_init = false;
|
|
114
|
+
target.entry.updated_at = nowIso();
|
|
115
|
+
writeState(state);
|
|
116
|
+
}
|
|
117
|
+
function isInitRequired(cwd) {
|
|
118
|
+
if (!cwd.trim()) return false;
|
|
119
|
+
const state = readState();
|
|
120
|
+
const target = getOrCreateEntry(state, cwd);
|
|
121
|
+
return Boolean(target?.entry.require_init);
|
|
122
|
+
}
|
|
123
|
+
function markStateChanged(cwd) {
|
|
124
|
+
if (!cwd.trim()) return;
|
|
125
|
+
const state = readState();
|
|
126
|
+
const target = getOrCreateEntry(state, cwd);
|
|
127
|
+
if (!target) return;
|
|
128
|
+
target.entry.last_state_change_at = nowIso();
|
|
129
|
+
target.entry.updated_at = nowIso();
|
|
130
|
+
writeState(state);
|
|
131
|
+
}
|
|
132
|
+
function isContextFreshAndClean(cwd, maxAgeSeconds) {
|
|
133
|
+
if (!cwd.trim()) return false;
|
|
134
|
+
const state = readState();
|
|
135
|
+
const target = getOrCreateEntry(state, cwd);
|
|
136
|
+
const entry = target?.entry;
|
|
137
|
+
if (!entry?.last_context_at) return false;
|
|
138
|
+
const contextAt = new Date(entry.last_context_at);
|
|
139
|
+
if (Number.isNaN(contextAt.getTime())) return false;
|
|
140
|
+
const ageSeconds = (Date.now() - contextAt.getTime()) / 1e3;
|
|
141
|
+
if (ageSeconds < 0 || ageSeconds > maxAgeSeconds) return false;
|
|
142
|
+
if (entry.last_state_change_at) {
|
|
143
|
+
const changedAt = new Date(entry.last_state_change_at);
|
|
144
|
+
if (!Number.isNaN(changedAt.getTime()) && changedAt.getTime() > contextAt.getTime()) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/hooks/pre-tool-use.ts
|
|
7
152
|
var ENABLED = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
|
|
8
|
-
var INDEX_STATUS_FILE =
|
|
153
|
+
var INDEX_STATUS_FILE = path2.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
9
154
|
var DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
|
|
10
155
|
var STALE_THRESHOLD_DAYS = 7;
|
|
156
|
+
var CONTEXT_FRESHNESS_SECONDS = 120;
|
|
11
157
|
var DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
|
|
12
158
|
function isDiscoveryGlob(pattern) {
|
|
13
159
|
const patternLower = pattern.toLowerCase();
|
|
@@ -34,22 +180,22 @@ function isDiscoveryGrep(filePath) {
|
|
|
34
180
|
return false;
|
|
35
181
|
}
|
|
36
182
|
function isProjectIndexed(cwd) {
|
|
37
|
-
if (!
|
|
183
|
+
if (!fs2.existsSync(INDEX_STATUS_FILE)) {
|
|
38
184
|
return { isIndexed: false, isStale: false };
|
|
39
185
|
}
|
|
40
186
|
let data;
|
|
41
187
|
try {
|
|
42
|
-
const content =
|
|
188
|
+
const content = fs2.readFileSync(INDEX_STATUS_FILE, "utf-8");
|
|
43
189
|
data = JSON.parse(content);
|
|
44
190
|
} catch {
|
|
45
191
|
return { isIndexed: false, isStale: false };
|
|
46
192
|
}
|
|
47
193
|
const projects = data.projects || {};
|
|
48
|
-
const cwdPath =
|
|
194
|
+
const cwdPath = path2.resolve(cwd);
|
|
49
195
|
for (const [projectPath, info] of Object.entries(projects)) {
|
|
50
196
|
try {
|
|
51
|
-
const indexedPath =
|
|
52
|
-
if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath +
|
|
197
|
+
const indexedPath = path2.resolve(projectPath);
|
|
198
|
+
if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath + path2.sep)) {
|
|
53
199
|
const indexedAt = info.indexed_at;
|
|
54
200
|
if (indexedAt) {
|
|
55
201
|
try {
|
|
@@ -82,6 +228,97 @@ function extractToolName(input) {
|
|
|
82
228
|
function extractToolInput(input) {
|
|
83
229
|
return input.tool_input || input.parameters || input.toolParameters || {};
|
|
84
230
|
}
|
|
231
|
+
function normalizeContextstreamToolName(toolName) {
|
|
232
|
+
const trimmed = toolName.trim();
|
|
233
|
+
if (!trimmed) return null;
|
|
234
|
+
const lower = trimmed.toLowerCase();
|
|
235
|
+
const prefixed = "mcp__contextstream__";
|
|
236
|
+
if (lower.startsWith(prefixed)) {
|
|
237
|
+
return lower.slice(prefixed.length);
|
|
238
|
+
}
|
|
239
|
+
if (lower.startsWith("contextstream__")) {
|
|
240
|
+
return lower.slice("contextstream__".length);
|
|
241
|
+
}
|
|
242
|
+
if (lower === "init" || lower === "context") {
|
|
243
|
+
return lower;
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
function actionFromToolInput(toolInput) {
|
|
248
|
+
const maybeAction = toolInput?.action;
|
|
249
|
+
return typeof maybeAction === "string" ? maybeAction.trim().toLowerCase() : "";
|
|
250
|
+
}
|
|
251
|
+
function isContextstreamReadOnlyOperation(toolName, toolInput) {
|
|
252
|
+
const action = actionFromToolInput(toolInput);
|
|
253
|
+
switch (toolName) {
|
|
254
|
+
case "workspace":
|
|
255
|
+
return action === "list" || action === "get";
|
|
256
|
+
case "memory":
|
|
257
|
+
return action === "list_docs" || action === "list_events" || action === "list_todos" || action === "list_tasks" || action === "list_transcripts" || action === "list_nodes" || action === "decisions" || action === "get_doc" || action === "get_event" || action === "get_task" || action === "get_todo" || action === "get_transcript";
|
|
258
|
+
case "session":
|
|
259
|
+
return action === "get_lessons" || action === "get_plan" || action === "list_plans" || action === "recall";
|
|
260
|
+
case "help":
|
|
261
|
+
return action === "version" || action === "tools" || action === "auth";
|
|
262
|
+
case "project":
|
|
263
|
+
return action === "list" || action === "get" || action === "index_status";
|
|
264
|
+
case "reminder":
|
|
265
|
+
return action === "list" || action === "active";
|
|
266
|
+
case "context":
|
|
267
|
+
case "init":
|
|
268
|
+
return true;
|
|
269
|
+
default:
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function isLikelyStateChangingTool(toolLower, toolInput, isContextstreamCall, normalizedContextstreamTool) {
|
|
274
|
+
if (isContextstreamCall && normalizedContextstreamTool) {
|
|
275
|
+
return !isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput);
|
|
276
|
+
}
|
|
277
|
+
if ([
|
|
278
|
+
"read",
|
|
279
|
+
"read_file",
|
|
280
|
+
"grep",
|
|
281
|
+
"glob",
|
|
282
|
+
"search",
|
|
283
|
+
"grep_search",
|
|
284
|
+
"code_search",
|
|
285
|
+
"semanticsearch",
|
|
286
|
+
"codebase_search",
|
|
287
|
+
"list_files",
|
|
288
|
+
"search_files",
|
|
289
|
+
"search_files_content",
|
|
290
|
+
"find_files",
|
|
291
|
+
"find_by_name",
|
|
292
|
+
"ls",
|
|
293
|
+
"cat",
|
|
294
|
+
"view"
|
|
295
|
+
].includes(toolLower)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const writeMarkers = [
|
|
299
|
+
"write",
|
|
300
|
+
"edit",
|
|
301
|
+
"create",
|
|
302
|
+
"delete",
|
|
303
|
+
"remove",
|
|
304
|
+
"rename",
|
|
305
|
+
"move",
|
|
306
|
+
"patch",
|
|
307
|
+
"apply",
|
|
308
|
+
"insert",
|
|
309
|
+
"append",
|
|
310
|
+
"replace",
|
|
311
|
+
"update",
|
|
312
|
+
"commit",
|
|
313
|
+
"push",
|
|
314
|
+
"install",
|
|
315
|
+
"exec",
|
|
316
|
+
"run",
|
|
317
|
+
"bash",
|
|
318
|
+
"shell"
|
|
319
|
+
];
|
|
320
|
+
return writeMarkers.some((marker) => toolLower.includes(marker));
|
|
321
|
+
}
|
|
85
322
|
function blockClaudeCode(message) {
|
|
86
323
|
const response = {
|
|
87
324
|
hookSpecificOutput: {
|
|
@@ -90,7 +327,7 @@ function blockClaudeCode(message) {
|
|
|
90
327
|
additionalContext: `[CONTEXTSTREAM] ${message}`
|
|
91
328
|
}
|
|
92
329
|
};
|
|
93
|
-
|
|
330
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] REDIRECT (additionalContext): ${JSON.stringify(response)}
|
|
94
331
|
`);
|
|
95
332
|
console.log(JSON.stringify(response));
|
|
96
333
|
process.exit(0);
|
|
@@ -118,6 +355,25 @@ function outputCursorAllow() {
|
|
|
118
355
|
console.log(JSON.stringify({ decision: "allow" }));
|
|
119
356
|
process.exit(0);
|
|
120
357
|
}
|
|
358
|
+
function blockWithMessage(editorFormat, message) {
|
|
359
|
+
if (editorFormat === "cline") {
|
|
360
|
+
outputClineBlock(message, "[CONTEXTSTREAM] Follow ContextStream startup requirements.");
|
|
361
|
+
} else if (editorFormat === "cursor") {
|
|
362
|
+
outputCursorBlock(message);
|
|
363
|
+
}
|
|
364
|
+
blockClaudeCode(message);
|
|
365
|
+
}
|
|
366
|
+
function allowTool(editorFormat, cwd, recordStateChange) {
|
|
367
|
+
if (recordStateChange) {
|
|
368
|
+
markStateChanged(cwd);
|
|
369
|
+
}
|
|
370
|
+
if (editorFormat === "cline") {
|
|
371
|
+
outputClineAllow();
|
|
372
|
+
} else if (editorFormat === "cursor") {
|
|
373
|
+
outputCursorAllow();
|
|
374
|
+
}
|
|
375
|
+
process.exit(0);
|
|
376
|
+
}
|
|
121
377
|
function detectEditorFormat(input) {
|
|
122
378
|
if (input.hookName !== void 0 || input.toolName !== void 0) {
|
|
123
379
|
return "cline";
|
|
@@ -128,11 +384,11 @@ function detectEditorFormat(input) {
|
|
|
128
384
|
return "claude";
|
|
129
385
|
}
|
|
130
386
|
async function runPreToolUseHook() {
|
|
131
|
-
|
|
387
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Hook invoked at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
132
388
|
`);
|
|
133
389
|
console.error("[PreToolUse] Hook invoked at", (/* @__PURE__ */ new Date()).toISOString());
|
|
134
390
|
if (!ENABLED) {
|
|
135
|
-
|
|
391
|
+
fs2.appendFileSync(DEBUG_FILE, "[PreToolUse] Hook disabled, exiting\n");
|
|
136
392
|
console.error("[PreToolUse] Hook disabled, exiting");
|
|
137
393
|
process.exit(0);
|
|
138
394
|
}
|
|
@@ -153,28 +409,52 @@ async function runPreToolUseHook() {
|
|
|
153
409
|
const cwd = extractCwd(input);
|
|
154
410
|
const tool = extractToolName(input);
|
|
155
411
|
const toolInput = extractToolInput(input);
|
|
156
|
-
|
|
412
|
+
const toolLower = tool.toLowerCase();
|
|
413
|
+
const normalizedContextstreamTool = normalizeContextstreamToolName(tool);
|
|
414
|
+
const isContextstreamCall = normalizedContextstreamTool !== null;
|
|
415
|
+
const recordStateChange = isLikelyStateChangingTool(
|
|
416
|
+
toolLower,
|
|
417
|
+
toolInput,
|
|
418
|
+
isContextstreamCall,
|
|
419
|
+
normalizedContextstreamTool
|
|
420
|
+
);
|
|
421
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] tool=${tool}, cwd=${cwd}, editorFormat=${editorFormat}
|
|
157
422
|
`);
|
|
423
|
+
cleanupStale(180);
|
|
424
|
+
if (isInitRequired(cwd)) {
|
|
425
|
+
if (isContextstreamCall && normalizedContextstreamTool === "init") {
|
|
426
|
+
clearInitRequired(cwd);
|
|
427
|
+
} else {
|
|
428
|
+
const required = "mcp__contextstream__init(...)";
|
|
429
|
+
const msg = `First call required for this session: ${required}. Run it before any other MCP tool. Then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>").`;
|
|
430
|
+
blockWithMessage(editorFormat, msg);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (isContextRequired(cwd)) {
|
|
434
|
+
if (isContextstreamCall && normalizedContextstreamTool === "context") {
|
|
435
|
+
clearContextRequired(cwd);
|
|
436
|
+
} else if (isContextstreamCall && normalizedContextstreamTool === "init") {
|
|
437
|
+
} else if (isContextstreamCall && normalizedContextstreamTool && isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput) && isContextFreshAndClean(cwd, CONTEXT_FRESHNESS_SECONDS)) {
|
|
438
|
+
} else {
|
|
439
|
+
const msg = 'First call required for this prompt: mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>"). Run it before any other MCP tool.';
|
|
440
|
+
blockWithMessage(editorFormat, msg);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
158
443
|
const { isIndexed } = isProjectIndexed(cwd);
|
|
159
|
-
|
|
444
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
|
|
160
445
|
`);
|
|
161
446
|
if (!isIndexed) {
|
|
162
|
-
|
|
447
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
|
|
163
448
|
`);
|
|
164
|
-
|
|
165
|
-
outputClineAllow();
|
|
166
|
-
} else if (editorFormat === "cursor") {
|
|
167
|
-
outputCursorAllow();
|
|
168
|
-
}
|
|
169
|
-
process.exit(0);
|
|
449
|
+
allowTool(editorFormat, cwd, recordStateChange);
|
|
170
450
|
}
|
|
171
451
|
if (tool === "Glob") {
|
|
172
452
|
const pattern = toolInput?.pattern || "";
|
|
173
|
-
|
|
453
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
|
|
174
454
|
`);
|
|
175
455
|
if (isDiscoveryGlob(pattern)) {
|
|
176
|
-
const msg = `
|
|
177
|
-
|
|
456
|
+
const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of Glob for faster, richer code results.`;
|
|
457
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
|
|
178
458
|
`);
|
|
179
459
|
if (editorFormat === "cline") {
|
|
180
460
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
@@ -196,7 +476,7 @@ async function runPreToolUseHook() {
|
|
|
196
476
|
}
|
|
197
477
|
blockClaudeCode(msg);
|
|
198
478
|
} else {
|
|
199
|
-
const msg = `
|
|
479
|
+
const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
|
|
200
480
|
if (editorFormat === "cline") {
|
|
201
481
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
202
482
|
} else if (editorFormat === "cursor") {
|
|
@@ -208,7 +488,7 @@ async function runPreToolUseHook() {
|
|
|
208
488
|
} else if (tool === "Task") {
|
|
209
489
|
const subagentType = toolInput?.subagent_type?.toLowerCase() || "";
|
|
210
490
|
if (subagentType === "explore") {
|
|
211
|
-
const msg = '
|
|
491
|
+
const msg = 'Project index is current. Use mcp__contextstream__search(mode="auto") instead of Task(Explore) for broad discovery.';
|
|
212
492
|
if (editorFormat === "cline") {
|
|
213
493
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
214
494
|
} else if (editorFormat === "cursor") {
|
|
@@ -217,7 +497,7 @@ async function runPreToolUseHook() {
|
|
|
217
497
|
blockClaudeCode(msg);
|
|
218
498
|
}
|
|
219
499
|
if (subagentType === "plan") {
|
|
220
|
-
const msg = '
|
|
500
|
+
const msg = 'After your plan is ready, save it with mcp__contextstream__session(action="capture_plan"). Then create tasks with mcp__contextstream__memory(action="create_task", title="...", plan_id="...").';
|
|
221
501
|
if (editorFormat === "cline") {
|
|
222
502
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
|
|
223
503
|
} else if (editorFormat === "cursor") {
|
|
@@ -226,7 +506,7 @@ async function runPreToolUseHook() {
|
|
|
226
506
|
blockClaudeCode(msg);
|
|
227
507
|
}
|
|
228
508
|
} else if (tool === "EnterPlanMode") {
|
|
229
|
-
const msg = '
|
|
509
|
+
const msg = 'After finalizing your plan, save it to ContextStream (not a local markdown file): mcp__contextstream__session(action="capture_plan", title="...", steps=[...]). Then create tasks with mcp__contextstream__memory(action="create_task", title="...", plan_id="...").';
|
|
230
510
|
if (editorFormat === "cline") {
|
|
231
511
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
|
|
232
512
|
} else if (editorFormat === "cursor") {
|
|
@@ -237,20 +517,16 @@ async function runPreToolUseHook() {
|
|
|
237
517
|
if (tool === "list_files" || tool === "search_files") {
|
|
238
518
|
const pattern = toolInput?.path || toolInput?.regex || "";
|
|
239
519
|
if (isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern)) {
|
|
240
|
-
const msg = `Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}
|
|
520
|
+
const msg = `Project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
|
|
241
521
|
if (editorFormat === "cline") {
|
|
242
522
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
243
523
|
} else if (editorFormat === "cursor") {
|
|
244
524
|
outputCursorBlock(msg);
|
|
245
525
|
}
|
|
526
|
+
blockClaudeCode(msg);
|
|
246
527
|
}
|
|
247
528
|
}
|
|
248
|
-
|
|
249
|
-
outputClineAllow();
|
|
250
|
-
} else if (editorFormat === "cursor") {
|
|
251
|
-
outputCursorAllow();
|
|
252
|
-
}
|
|
253
|
-
process.exit(0);
|
|
529
|
+
allowTool(editorFormat, cwd, recordStateChange);
|
|
254
530
|
}
|
|
255
531
|
var isDirectRun = process.argv[1]?.includes("pre-tool-use") || process.argv[2] === "pre-tool-use";
|
|
256
532
|
if (isDirectRun) {
|