@contextstream/mcp-server 0.4.61 → 0.4.62
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 +1711 -412
- package/package.json +1 -1
package/dist/hooks/runner.js
CHANGED
|
@@ -38,14 +38,181 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
38
38
|
mod
|
|
39
39
|
));
|
|
40
40
|
|
|
41
|
+
// src/hooks/prompt-state.ts
|
|
42
|
+
import * as fs from "node:fs";
|
|
43
|
+
import * as path from "node:path";
|
|
44
|
+
import { homedir } from "node:os";
|
|
45
|
+
function defaultState() {
|
|
46
|
+
return { workspaces: {} };
|
|
47
|
+
}
|
|
48
|
+
function nowIso() {
|
|
49
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
50
|
+
}
|
|
51
|
+
function ensureStateDir() {
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true });
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function normalizePath(input) {
|
|
58
|
+
try {
|
|
59
|
+
return path.resolve(input);
|
|
60
|
+
} catch {
|
|
61
|
+
return input;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function workspacePathsMatch(a, b) {
|
|
65
|
+
const left = normalizePath(a);
|
|
66
|
+
const right = normalizePath(b);
|
|
67
|
+
return left === right || left.startsWith(`${right}${path.sep}`) || right.startsWith(`${left}${path.sep}`);
|
|
68
|
+
}
|
|
69
|
+
function readState() {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(STATE_PATH, "utf8");
|
|
72
|
+
const parsed = JSON.parse(content);
|
|
73
|
+
if (!parsed || typeof parsed !== "object" || !parsed.workspaces) {
|
|
74
|
+
return defaultState();
|
|
75
|
+
}
|
|
76
|
+
return parsed;
|
|
77
|
+
} catch {
|
|
78
|
+
return defaultState();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function writeState(state) {
|
|
82
|
+
try {
|
|
83
|
+
ensureStateDir();
|
|
84
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8");
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function getOrCreateEntry(state, cwd) {
|
|
89
|
+
if (!cwd.trim()) return null;
|
|
90
|
+
const exact = state.workspaces[cwd];
|
|
91
|
+
if (exact) return { key: cwd, entry: exact };
|
|
92
|
+
for (const [trackedCwd, trackedEntry] of Object.entries(state.workspaces)) {
|
|
93
|
+
if (workspacePathsMatch(trackedCwd, cwd)) {
|
|
94
|
+
return { key: trackedCwd, entry: trackedEntry };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const created = {
|
|
98
|
+
require_context: false,
|
|
99
|
+
require_init: false,
|
|
100
|
+
last_context_at: void 0,
|
|
101
|
+
last_state_change_at: void 0,
|
|
102
|
+
updated_at: nowIso()
|
|
103
|
+
};
|
|
104
|
+
state.workspaces[cwd] = created;
|
|
105
|
+
return { key: cwd, entry: created };
|
|
106
|
+
}
|
|
107
|
+
function cleanupStale(maxAgeSeconds) {
|
|
108
|
+
const state = readState();
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
let changed = false;
|
|
111
|
+
for (const [cwd, entry] of Object.entries(state.workspaces)) {
|
|
112
|
+
const updated = new Date(entry.updated_at);
|
|
113
|
+
if (Number.isNaN(updated.getTime())) continue;
|
|
114
|
+
const ageSeconds = (now - updated.getTime()) / 1e3;
|
|
115
|
+
if (ageSeconds > maxAgeSeconds) {
|
|
116
|
+
delete state.workspaces[cwd];
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (changed) {
|
|
121
|
+
writeState(state);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function markContextRequired(cwd) {
|
|
125
|
+
if (!cwd.trim()) return;
|
|
126
|
+
const state = readState();
|
|
127
|
+
const target = getOrCreateEntry(state, cwd);
|
|
128
|
+
if (!target) return;
|
|
129
|
+
target.entry.require_context = true;
|
|
130
|
+
target.entry.updated_at = nowIso();
|
|
131
|
+
writeState(state);
|
|
132
|
+
}
|
|
133
|
+
function clearContextRequired(cwd) {
|
|
134
|
+
if (!cwd.trim()) return;
|
|
135
|
+
const state = readState();
|
|
136
|
+
const target = getOrCreateEntry(state, cwd);
|
|
137
|
+
if (!target) return;
|
|
138
|
+
target.entry.require_context = false;
|
|
139
|
+
target.entry.last_context_at = nowIso();
|
|
140
|
+
target.entry.updated_at = nowIso();
|
|
141
|
+
writeState(state);
|
|
142
|
+
}
|
|
143
|
+
function isContextRequired(cwd) {
|
|
144
|
+
if (!cwd.trim()) return false;
|
|
145
|
+
const state = readState();
|
|
146
|
+
const target = getOrCreateEntry(state, cwd);
|
|
147
|
+
return Boolean(target?.entry.require_context);
|
|
148
|
+
}
|
|
149
|
+
function markInitRequired(cwd) {
|
|
150
|
+
if (!cwd.trim()) return;
|
|
151
|
+
const state = readState();
|
|
152
|
+
const target = getOrCreateEntry(state, cwd);
|
|
153
|
+
if (!target) return;
|
|
154
|
+
target.entry.require_init = true;
|
|
155
|
+
target.entry.updated_at = nowIso();
|
|
156
|
+
writeState(state);
|
|
157
|
+
}
|
|
158
|
+
function clearInitRequired(cwd) {
|
|
159
|
+
if (!cwd.trim()) return;
|
|
160
|
+
const state = readState();
|
|
161
|
+
const target = getOrCreateEntry(state, cwd);
|
|
162
|
+
if (!target) return;
|
|
163
|
+
target.entry.require_init = false;
|
|
164
|
+
target.entry.updated_at = nowIso();
|
|
165
|
+
writeState(state);
|
|
166
|
+
}
|
|
167
|
+
function isInitRequired(cwd) {
|
|
168
|
+
if (!cwd.trim()) return false;
|
|
169
|
+
const state = readState();
|
|
170
|
+
const target = getOrCreateEntry(state, cwd);
|
|
171
|
+
return Boolean(target?.entry.require_init);
|
|
172
|
+
}
|
|
173
|
+
function markStateChanged(cwd) {
|
|
174
|
+
if (!cwd.trim()) return;
|
|
175
|
+
const state = readState();
|
|
176
|
+
const target = getOrCreateEntry(state, cwd);
|
|
177
|
+
if (!target) return;
|
|
178
|
+
target.entry.last_state_change_at = nowIso();
|
|
179
|
+
target.entry.updated_at = nowIso();
|
|
180
|
+
writeState(state);
|
|
181
|
+
}
|
|
182
|
+
function isContextFreshAndClean(cwd, maxAgeSeconds) {
|
|
183
|
+
if (!cwd.trim()) return false;
|
|
184
|
+
const state = readState();
|
|
185
|
+
const target = getOrCreateEntry(state, cwd);
|
|
186
|
+
const entry = target?.entry;
|
|
187
|
+
if (!entry?.last_context_at) return false;
|
|
188
|
+
const contextAt = new Date(entry.last_context_at);
|
|
189
|
+
if (Number.isNaN(contextAt.getTime())) return false;
|
|
190
|
+
const ageSeconds = (Date.now() - contextAt.getTime()) / 1e3;
|
|
191
|
+
if (ageSeconds < 0 || ageSeconds > maxAgeSeconds) return false;
|
|
192
|
+
if (entry.last_state_change_at) {
|
|
193
|
+
const changedAt = new Date(entry.last_state_change_at);
|
|
194
|
+
if (!Number.isNaN(changedAt.getTime()) && changedAt.getTime() > contextAt.getTime()) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
var STATE_PATH;
|
|
201
|
+
var init_prompt_state = __esm({
|
|
202
|
+
"src/hooks/prompt-state.ts"() {
|
|
203
|
+
"use strict";
|
|
204
|
+
STATE_PATH = path.join(homedir(), ".contextstream", "prompt-state.json");
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
41
208
|
// src/hooks/pre-tool-use.ts
|
|
42
209
|
var pre_tool_use_exports = {};
|
|
43
210
|
__export(pre_tool_use_exports, {
|
|
44
211
|
runPreToolUseHook: () => runPreToolUseHook
|
|
45
212
|
});
|
|
46
|
-
import * as
|
|
47
|
-
import * as
|
|
48
|
-
import { homedir } from "node:os";
|
|
213
|
+
import * as fs2 from "node:fs";
|
|
214
|
+
import * as path2 from "node:path";
|
|
215
|
+
import { homedir as homedir2 } from "node:os";
|
|
49
216
|
function isDiscoveryGlob(pattern) {
|
|
50
217
|
const patternLower = pattern.toLowerCase();
|
|
51
218
|
for (const p of DISCOVERY_PATTERNS) {
|
|
@@ -71,22 +238,22 @@ function isDiscoveryGrep(filePath) {
|
|
|
71
238
|
return false;
|
|
72
239
|
}
|
|
73
240
|
function isProjectIndexed(cwd) {
|
|
74
|
-
if (!
|
|
241
|
+
if (!fs2.existsSync(INDEX_STATUS_FILE)) {
|
|
75
242
|
return { isIndexed: false, isStale: false };
|
|
76
243
|
}
|
|
77
244
|
let data;
|
|
78
245
|
try {
|
|
79
|
-
const content =
|
|
246
|
+
const content = fs2.readFileSync(INDEX_STATUS_FILE, "utf-8");
|
|
80
247
|
data = JSON.parse(content);
|
|
81
248
|
} catch {
|
|
82
249
|
return { isIndexed: false, isStale: false };
|
|
83
250
|
}
|
|
84
251
|
const projects = data.projects || {};
|
|
85
|
-
const cwdPath =
|
|
252
|
+
const cwdPath = path2.resolve(cwd);
|
|
86
253
|
for (const [projectPath, info] of Object.entries(projects)) {
|
|
87
254
|
try {
|
|
88
|
-
const indexedPath =
|
|
89
|
-
if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath +
|
|
255
|
+
const indexedPath = path2.resolve(projectPath);
|
|
256
|
+
if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath + path2.sep)) {
|
|
90
257
|
const indexedAt = info.indexed_at;
|
|
91
258
|
if (indexedAt) {
|
|
92
259
|
try {
|
|
@@ -119,6 +286,97 @@ function extractToolName(input) {
|
|
|
119
286
|
function extractToolInput(input) {
|
|
120
287
|
return input.tool_input || input.parameters || input.toolParameters || {};
|
|
121
288
|
}
|
|
289
|
+
function normalizeContextstreamToolName(toolName) {
|
|
290
|
+
const trimmed = toolName.trim();
|
|
291
|
+
if (!trimmed) return null;
|
|
292
|
+
const lower = trimmed.toLowerCase();
|
|
293
|
+
const prefixed = "mcp__contextstream__";
|
|
294
|
+
if (lower.startsWith(prefixed)) {
|
|
295
|
+
return lower.slice(prefixed.length);
|
|
296
|
+
}
|
|
297
|
+
if (lower.startsWith("contextstream__")) {
|
|
298
|
+
return lower.slice("contextstream__".length);
|
|
299
|
+
}
|
|
300
|
+
if (lower === "init" || lower === "context") {
|
|
301
|
+
return lower;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
function actionFromToolInput(toolInput) {
|
|
306
|
+
const maybeAction = toolInput?.action;
|
|
307
|
+
return typeof maybeAction === "string" ? maybeAction.trim().toLowerCase() : "";
|
|
308
|
+
}
|
|
309
|
+
function isContextstreamReadOnlyOperation(toolName, toolInput) {
|
|
310
|
+
const action = actionFromToolInput(toolInput);
|
|
311
|
+
switch (toolName) {
|
|
312
|
+
case "workspace":
|
|
313
|
+
return action === "list" || action === "get";
|
|
314
|
+
case "memory":
|
|
315
|
+
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";
|
|
316
|
+
case "session":
|
|
317
|
+
return action === "get_lessons" || action === "get_plan" || action === "list_plans" || action === "recall";
|
|
318
|
+
case "help":
|
|
319
|
+
return action === "version" || action === "tools" || action === "auth";
|
|
320
|
+
case "project":
|
|
321
|
+
return action === "list" || action === "get" || action === "index_status";
|
|
322
|
+
case "reminder":
|
|
323
|
+
return action === "list" || action === "active";
|
|
324
|
+
case "context":
|
|
325
|
+
case "init":
|
|
326
|
+
return true;
|
|
327
|
+
default:
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function isLikelyStateChangingTool(toolLower, toolInput, isContextstreamCall, normalizedContextstreamTool) {
|
|
332
|
+
if (isContextstreamCall && normalizedContextstreamTool) {
|
|
333
|
+
return !isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput);
|
|
334
|
+
}
|
|
335
|
+
if ([
|
|
336
|
+
"read",
|
|
337
|
+
"read_file",
|
|
338
|
+
"grep",
|
|
339
|
+
"glob",
|
|
340
|
+
"search",
|
|
341
|
+
"grep_search",
|
|
342
|
+
"code_search",
|
|
343
|
+
"semanticsearch",
|
|
344
|
+
"codebase_search",
|
|
345
|
+
"list_files",
|
|
346
|
+
"search_files",
|
|
347
|
+
"search_files_content",
|
|
348
|
+
"find_files",
|
|
349
|
+
"find_by_name",
|
|
350
|
+
"ls",
|
|
351
|
+
"cat",
|
|
352
|
+
"view"
|
|
353
|
+
].includes(toolLower)) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const writeMarkers = [
|
|
357
|
+
"write",
|
|
358
|
+
"edit",
|
|
359
|
+
"create",
|
|
360
|
+
"delete",
|
|
361
|
+
"remove",
|
|
362
|
+
"rename",
|
|
363
|
+
"move",
|
|
364
|
+
"patch",
|
|
365
|
+
"apply",
|
|
366
|
+
"insert",
|
|
367
|
+
"append",
|
|
368
|
+
"replace",
|
|
369
|
+
"update",
|
|
370
|
+
"commit",
|
|
371
|
+
"push",
|
|
372
|
+
"install",
|
|
373
|
+
"exec",
|
|
374
|
+
"run",
|
|
375
|
+
"bash",
|
|
376
|
+
"shell"
|
|
377
|
+
];
|
|
378
|
+
return writeMarkers.some((marker) => toolLower.includes(marker));
|
|
379
|
+
}
|
|
122
380
|
function blockClaudeCode(message) {
|
|
123
381
|
const response = {
|
|
124
382
|
hookSpecificOutput: {
|
|
@@ -127,7 +385,7 @@ function blockClaudeCode(message) {
|
|
|
127
385
|
additionalContext: `[CONTEXTSTREAM] ${message}`
|
|
128
386
|
}
|
|
129
387
|
};
|
|
130
|
-
|
|
388
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] REDIRECT (additionalContext): ${JSON.stringify(response)}
|
|
131
389
|
`);
|
|
132
390
|
console.log(JSON.stringify(response));
|
|
133
391
|
process.exit(0);
|
|
@@ -155,6 +413,25 @@ function outputCursorAllow() {
|
|
|
155
413
|
console.log(JSON.stringify({ decision: "allow" }));
|
|
156
414
|
process.exit(0);
|
|
157
415
|
}
|
|
416
|
+
function blockWithMessage(editorFormat, message) {
|
|
417
|
+
if (editorFormat === "cline") {
|
|
418
|
+
outputClineBlock(message, "[CONTEXTSTREAM] Follow ContextStream startup requirements.");
|
|
419
|
+
} else if (editorFormat === "cursor") {
|
|
420
|
+
outputCursorBlock(message);
|
|
421
|
+
}
|
|
422
|
+
blockClaudeCode(message);
|
|
423
|
+
}
|
|
424
|
+
function allowTool(editorFormat, cwd, recordStateChange) {
|
|
425
|
+
if (recordStateChange) {
|
|
426
|
+
markStateChanged(cwd);
|
|
427
|
+
}
|
|
428
|
+
if (editorFormat === "cline") {
|
|
429
|
+
outputClineAllow();
|
|
430
|
+
} else if (editorFormat === "cursor") {
|
|
431
|
+
outputCursorAllow();
|
|
432
|
+
}
|
|
433
|
+
process.exit(0);
|
|
434
|
+
}
|
|
158
435
|
function detectEditorFormat(input) {
|
|
159
436
|
if (input.hookName !== void 0 || input.toolName !== void 0) {
|
|
160
437
|
return "cline";
|
|
@@ -165,11 +442,11 @@ function detectEditorFormat(input) {
|
|
|
165
442
|
return "claude";
|
|
166
443
|
}
|
|
167
444
|
async function runPreToolUseHook() {
|
|
168
|
-
|
|
445
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Hook invoked at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
169
446
|
`);
|
|
170
447
|
console.error("[PreToolUse] Hook invoked at", (/* @__PURE__ */ new Date()).toISOString());
|
|
171
448
|
if (!ENABLED) {
|
|
172
|
-
|
|
449
|
+
fs2.appendFileSync(DEBUG_FILE, "[PreToolUse] Hook disabled, exiting\n");
|
|
173
450
|
console.error("[PreToolUse] Hook disabled, exiting");
|
|
174
451
|
process.exit(0);
|
|
175
452
|
}
|
|
@@ -190,28 +467,52 @@ async function runPreToolUseHook() {
|
|
|
190
467
|
const cwd = extractCwd(input);
|
|
191
468
|
const tool = extractToolName(input);
|
|
192
469
|
const toolInput = extractToolInput(input);
|
|
193
|
-
|
|
470
|
+
const toolLower = tool.toLowerCase();
|
|
471
|
+
const normalizedContextstreamTool = normalizeContextstreamToolName(tool);
|
|
472
|
+
const isContextstreamCall = normalizedContextstreamTool !== null;
|
|
473
|
+
const recordStateChange = isLikelyStateChangingTool(
|
|
474
|
+
toolLower,
|
|
475
|
+
toolInput,
|
|
476
|
+
isContextstreamCall,
|
|
477
|
+
normalizedContextstreamTool
|
|
478
|
+
);
|
|
479
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] tool=${tool}, cwd=${cwd}, editorFormat=${editorFormat}
|
|
194
480
|
`);
|
|
481
|
+
cleanupStale(180);
|
|
482
|
+
if (isInitRequired(cwd)) {
|
|
483
|
+
if (isContextstreamCall && normalizedContextstreamTool === "init") {
|
|
484
|
+
clearInitRequired(cwd);
|
|
485
|
+
} else {
|
|
486
|
+
const required = "mcp__contextstream__init(...)";
|
|
487
|
+
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>").`;
|
|
488
|
+
blockWithMessage(editorFormat, msg);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (isContextRequired(cwd)) {
|
|
492
|
+
if (isContextstreamCall && normalizedContextstreamTool === "context") {
|
|
493
|
+
clearContextRequired(cwd);
|
|
494
|
+
} else if (isContextstreamCall && normalizedContextstreamTool === "init") {
|
|
495
|
+
} else if (isContextstreamCall && normalizedContextstreamTool && isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput) && isContextFreshAndClean(cwd, CONTEXT_FRESHNESS_SECONDS)) {
|
|
496
|
+
} else {
|
|
497
|
+
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.';
|
|
498
|
+
blockWithMessage(editorFormat, msg);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
195
501
|
const { isIndexed } = isProjectIndexed(cwd);
|
|
196
|
-
|
|
502
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
|
|
197
503
|
`);
|
|
198
504
|
if (!isIndexed) {
|
|
199
|
-
|
|
505
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
|
|
200
506
|
`);
|
|
201
|
-
|
|
202
|
-
outputClineAllow();
|
|
203
|
-
} else if (editorFormat === "cursor") {
|
|
204
|
-
outputCursorAllow();
|
|
205
|
-
}
|
|
206
|
-
process.exit(0);
|
|
507
|
+
allowTool(editorFormat, cwd, recordStateChange);
|
|
207
508
|
}
|
|
208
509
|
if (tool === "Glob") {
|
|
209
510
|
const pattern = toolInput?.pattern || "";
|
|
210
|
-
|
|
511
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
|
|
211
512
|
`);
|
|
212
513
|
if (isDiscoveryGlob(pattern)) {
|
|
213
|
-
const msg = `
|
|
214
|
-
|
|
514
|
+
const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of Glob for faster, richer code results.`;
|
|
515
|
+
fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
|
|
215
516
|
`);
|
|
216
517
|
if (editorFormat === "cline") {
|
|
217
518
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
@@ -233,7 +534,7 @@ async function runPreToolUseHook() {
|
|
|
233
534
|
}
|
|
234
535
|
blockClaudeCode(msg);
|
|
235
536
|
} else {
|
|
236
|
-
const msg = `
|
|
537
|
+
const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
|
|
237
538
|
if (editorFormat === "cline") {
|
|
238
539
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
239
540
|
} else if (editorFormat === "cursor") {
|
|
@@ -245,7 +546,7 @@ async function runPreToolUseHook() {
|
|
|
245
546
|
} else if (tool === "Task") {
|
|
246
547
|
const subagentType = toolInput?.subagent_type?.toLowerCase() || "";
|
|
247
548
|
if (subagentType === "explore") {
|
|
248
|
-
const msg = '
|
|
549
|
+
const msg = 'Project index is current. Use mcp__contextstream__search(mode="auto") instead of Task(Explore) for broad discovery.';
|
|
249
550
|
if (editorFormat === "cline") {
|
|
250
551
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
251
552
|
} else if (editorFormat === "cursor") {
|
|
@@ -254,7 +555,7 @@ async function runPreToolUseHook() {
|
|
|
254
555
|
blockClaudeCode(msg);
|
|
255
556
|
}
|
|
256
557
|
if (subagentType === "plan") {
|
|
257
|
-
const msg = '
|
|
558
|
+
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="...").';
|
|
258
559
|
if (editorFormat === "cline") {
|
|
259
560
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
|
|
260
561
|
} else if (editorFormat === "cursor") {
|
|
@@ -263,7 +564,7 @@ async function runPreToolUseHook() {
|
|
|
263
564
|
blockClaudeCode(msg);
|
|
264
565
|
}
|
|
265
566
|
} else if (tool === "EnterPlanMode") {
|
|
266
|
-
const msg = '
|
|
567
|
+
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="...").';
|
|
267
568
|
if (editorFormat === "cline") {
|
|
268
569
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
|
|
269
570
|
} else if (editorFormat === "cursor") {
|
|
@@ -274,29 +575,27 @@ async function runPreToolUseHook() {
|
|
|
274
575
|
if (tool === "list_files" || tool === "search_files") {
|
|
275
576
|
const pattern = toolInput?.path || toolInput?.regex || "";
|
|
276
577
|
if (isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern)) {
|
|
277
|
-
const msg = `Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}
|
|
578
|
+
const msg = `Project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
|
|
278
579
|
if (editorFormat === "cline") {
|
|
279
580
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
280
581
|
} else if (editorFormat === "cursor") {
|
|
281
582
|
outputCursorBlock(msg);
|
|
282
583
|
}
|
|
584
|
+
blockClaudeCode(msg);
|
|
283
585
|
}
|
|
284
586
|
}
|
|
285
|
-
|
|
286
|
-
outputClineAllow();
|
|
287
|
-
} else if (editorFormat === "cursor") {
|
|
288
|
-
outputCursorAllow();
|
|
289
|
-
}
|
|
290
|
-
process.exit(0);
|
|
587
|
+
allowTool(editorFormat, cwd, recordStateChange);
|
|
291
588
|
}
|
|
292
|
-
var ENABLED, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, DISCOVERY_PATTERNS, isDirectRun;
|
|
589
|
+
var ENABLED, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, CONTEXT_FRESHNESS_SECONDS, DISCOVERY_PATTERNS, isDirectRun;
|
|
293
590
|
var init_pre_tool_use = __esm({
|
|
294
591
|
"src/hooks/pre-tool-use.ts"() {
|
|
295
592
|
"use strict";
|
|
593
|
+
init_prompt_state();
|
|
296
594
|
ENABLED = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
|
|
297
|
-
INDEX_STATUS_FILE =
|
|
595
|
+
INDEX_STATUS_FILE = path2.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
298
596
|
DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
|
|
299
597
|
STALE_THRESHOLD_DAYS = 7;
|
|
598
|
+
CONTEXT_FRESHNESS_SECONDS = 120;
|
|
300
599
|
DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
|
|
301
600
|
isDirectRun = process.argv[1]?.includes("pre-tool-use") || process.argv[2] === "pre-tool-use";
|
|
302
601
|
if (isDirectRun) {
|
|
@@ -307,9 +606,9 @@ var init_pre_tool_use = __esm({
|
|
|
307
606
|
|
|
308
607
|
// src/version.ts
|
|
309
608
|
import { createRequire } from "module";
|
|
310
|
-
import { existsSync as existsSync2, readFileSync as
|
|
311
|
-
import { homedir as
|
|
312
|
-
import { join as
|
|
609
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
610
|
+
import { homedir as homedir3, platform } from "os";
|
|
611
|
+
import { join as join3 } from "path";
|
|
313
612
|
import { spawn } from "child_process";
|
|
314
613
|
function getVersion() {
|
|
315
614
|
if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
|
|
@@ -336,13 +635,13 @@ function compareVersions(v1, v2) {
|
|
|
336
635
|
return 0;
|
|
337
636
|
}
|
|
338
637
|
function getCacheFilePath() {
|
|
339
|
-
return
|
|
638
|
+
return join3(homedir3(), ".contextstream", "version-cache.json");
|
|
340
639
|
}
|
|
341
640
|
function readCache() {
|
|
342
641
|
try {
|
|
343
642
|
const cacheFile = getCacheFilePath();
|
|
344
643
|
if (!existsSync2(cacheFile)) return null;
|
|
345
|
-
const data = JSON.parse(
|
|
644
|
+
const data = JSON.parse(readFileSync3(cacheFile, "utf-8"));
|
|
346
645
|
if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
|
|
347
646
|
return data;
|
|
348
647
|
} catch {
|
|
@@ -351,12 +650,12 @@ function readCache() {
|
|
|
351
650
|
}
|
|
352
651
|
function writeCache(latestVersion) {
|
|
353
652
|
try {
|
|
354
|
-
const configDir =
|
|
653
|
+
const configDir = join3(homedir3(), ".contextstream");
|
|
355
654
|
if (!existsSync2(configDir)) {
|
|
356
|
-
|
|
655
|
+
mkdirSync2(configDir, { recursive: true });
|
|
357
656
|
}
|
|
358
657
|
const cacheFile = getCacheFilePath();
|
|
359
|
-
|
|
658
|
+
writeFileSync2(
|
|
360
659
|
cacheFile,
|
|
361
660
|
JSON.stringify({
|
|
362
661
|
latestVersion,
|
|
@@ -467,9 +766,9 @@ function isAutoUpdateEnabled() {
|
|
|
467
766
|
return false;
|
|
468
767
|
}
|
|
469
768
|
try {
|
|
470
|
-
const configPath =
|
|
769
|
+
const configPath = join3(homedir3(), ".contextstream", "config.json");
|
|
471
770
|
if (existsSync2(configPath)) {
|
|
472
|
-
const config = JSON.parse(
|
|
771
|
+
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
473
772
|
if (config.auto_update === false) {
|
|
474
773
|
return false;
|
|
475
774
|
}
|
|
@@ -530,7 +829,7 @@ function detectUpdateMethod() {
|
|
|
530
829
|
return "curl";
|
|
531
830
|
}
|
|
532
831
|
async function runUpdate(method) {
|
|
533
|
-
return new Promise((
|
|
832
|
+
return new Promise((resolve15, reject) => {
|
|
534
833
|
let command;
|
|
535
834
|
let args;
|
|
536
835
|
let shell;
|
|
@@ -561,23 +860,23 @@ async function runUpdate(method) {
|
|
|
561
860
|
});
|
|
562
861
|
proc.on("close", (code) => {
|
|
563
862
|
if (code === 0) {
|
|
564
|
-
|
|
863
|
+
resolve15();
|
|
565
864
|
} else {
|
|
566
865
|
reject(new Error(`Update process exited with code ${code}`));
|
|
567
866
|
}
|
|
568
867
|
});
|
|
569
868
|
proc.unref();
|
|
570
|
-
setTimeout(() =>
|
|
869
|
+
setTimeout(() => resolve15(), 1e3);
|
|
571
870
|
});
|
|
572
871
|
}
|
|
573
872
|
function writeUpdateMarker(previousVersion, newVersion) {
|
|
574
873
|
try {
|
|
575
|
-
const markerPath =
|
|
576
|
-
const configDir =
|
|
874
|
+
const markerPath = join3(homedir3(), ".contextstream", "update-pending.json");
|
|
875
|
+
const configDir = join3(homedir3(), ".contextstream");
|
|
577
876
|
if (!existsSync2(configDir)) {
|
|
578
|
-
|
|
877
|
+
mkdirSync2(configDir, { recursive: true });
|
|
579
878
|
}
|
|
580
|
-
|
|
879
|
+
writeFileSync2(markerPath, JSON.stringify({
|
|
581
880
|
previousVersion,
|
|
582
881
|
newVersion,
|
|
583
882
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -587,9 +886,9 @@ function writeUpdateMarker(previousVersion, newVersion) {
|
|
|
587
886
|
}
|
|
588
887
|
function checkUpdateMarker() {
|
|
589
888
|
try {
|
|
590
|
-
const markerPath =
|
|
889
|
+
const markerPath = join3(homedir3(), ".contextstream", "update-pending.json");
|
|
591
890
|
if (!existsSync2(markerPath)) return null;
|
|
592
|
-
const marker = JSON.parse(
|
|
891
|
+
const marker = JSON.parse(readFileSync3(markerPath, "utf-8"));
|
|
593
892
|
const updatedAt = new Date(marker.updatedAt);
|
|
594
893
|
const hourAgo = new Date(Date.now() - 60 * 60 * 1e3);
|
|
595
894
|
if (updatedAt < hourAgo) {
|
|
@@ -606,7 +905,7 @@ function checkUpdateMarker() {
|
|
|
606
905
|
}
|
|
607
906
|
function clearUpdateMarker() {
|
|
608
907
|
try {
|
|
609
|
-
const markerPath =
|
|
908
|
+
const markerPath = join3(homedir3(), ".contextstream", "update-pending.json");
|
|
610
909
|
if (existsSync2(markerPath)) {
|
|
611
910
|
__require("fs").unlinkSync(markerPath);
|
|
612
911
|
}
|
|
@@ -636,17 +935,17 @@ var user_prompt_submit_exports = {};
|
|
|
636
935
|
__export(user_prompt_submit_exports, {
|
|
637
936
|
runUserPromptSubmitHook: () => runUserPromptSubmitHook
|
|
638
937
|
});
|
|
639
|
-
import * as
|
|
640
|
-
import * as
|
|
641
|
-
import { homedir as
|
|
938
|
+
import * as fs3 from "node:fs";
|
|
939
|
+
import * as path3 from "node:path";
|
|
940
|
+
import { homedir as homedir4 } from "node:os";
|
|
642
941
|
function loadConfigFromMcpJson(cwd) {
|
|
643
|
-
let searchDir =
|
|
942
|
+
let searchDir = path3.resolve(cwd);
|
|
644
943
|
for (let i = 0; i < 5; i++) {
|
|
645
944
|
if (!API_KEY) {
|
|
646
|
-
const mcpPath =
|
|
647
|
-
if (
|
|
945
|
+
const mcpPath = path3.join(searchDir, ".mcp.json");
|
|
946
|
+
if (fs3.existsSync(mcpPath)) {
|
|
648
947
|
try {
|
|
649
|
-
const content =
|
|
948
|
+
const content = fs3.readFileSync(mcpPath, "utf-8");
|
|
650
949
|
const config = JSON.parse(content);
|
|
651
950
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
652
951
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -663,10 +962,10 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
663
962
|
}
|
|
664
963
|
}
|
|
665
964
|
if (!WORKSPACE_ID || !PROJECT_ID) {
|
|
666
|
-
const csConfigPath =
|
|
667
|
-
if (
|
|
965
|
+
const csConfigPath = path3.join(searchDir, ".contextstream", "config.json");
|
|
966
|
+
if (fs3.existsSync(csConfigPath)) {
|
|
668
967
|
try {
|
|
669
|
-
const content =
|
|
968
|
+
const content = fs3.readFileSync(csConfigPath, "utf-8");
|
|
670
969
|
const csConfig = JSON.parse(content);
|
|
671
970
|
if (csConfig.workspace_id && !WORKSPACE_ID) {
|
|
672
971
|
WORKSPACE_ID = csConfig.workspace_id;
|
|
@@ -678,15 +977,15 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
678
977
|
}
|
|
679
978
|
}
|
|
680
979
|
}
|
|
681
|
-
const parentDir =
|
|
980
|
+
const parentDir = path3.dirname(searchDir);
|
|
682
981
|
if (parentDir === searchDir) break;
|
|
683
982
|
searchDir = parentDir;
|
|
684
983
|
}
|
|
685
984
|
if (!API_KEY) {
|
|
686
|
-
const homeMcpPath =
|
|
687
|
-
if (
|
|
985
|
+
const homeMcpPath = path3.join(homedir4(), ".mcp.json");
|
|
986
|
+
if (fs3.existsSync(homeMcpPath)) {
|
|
688
987
|
try {
|
|
689
|
-
const content =
|
|
988
|
+
const content = fs3.readFileSync(homeMcpPath, "utf-8");
|
|
690
989
|
const config = JSON.parse(content);
|
|
691
990
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
692
991
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -702,7 +1001,7 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
702
1001
|
}
|
|
703
1002
|
function readTranscriptFile(transcriptPath) {
|
|
704
1003
|
try {
|
|
705
|
-
const content =
|
|
1004
|
+
const content = fs3.readFileSync(transcriptPath, "utf-8");
|
|
706
1005
|
const lines = content.trim().split("\n");
|
|
707
1006
|
const messages = [];
|
|
708
1007
|
for (const line of lines) {
|
|
@@ -1110,6 +1409,11 @@ async function runUserPromptSubmitHook() {
|
|
|
1110
1409
|
}
|
|
1111
1410
|
const editorFormat = detectEditorFormat2(input);
|
|
1112
1411
|
const cwd = input.cwd || process.cwd();
|
|
1412
|
+
cleanupStale(180);
|
|
1413
|
+
markContextRequired(cwd);
|
|
1414
|
+
if (isNewSession(input, editorFormat)) {
|
|
1415
|
+
markInitRequired(cwd);
|
|
1416
|
+
}
|
|
1113
1417
|
if (editorFormat === "claude") {
|
|
1114
1418
|
loadConfigFromMcpJson(cwd);
|
|
1115
1419
|
let context = REMINDER;
|
|
@@ -1166,20 +1470,23 @@ var init_user_prompt_submit = __esm({
|
|
|
1166
1470
|
"src/hooks/user-prompt-submit.ts"() {
|
|
1167
1471
|
"use strict";
|
|
1168
1472
|
init_version();
|
|
1473
|
+
init_prompt_state();
|
|
1169
1474
|
ENABLED2 = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
|
|
1170
1475
|
API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
1171
1476
|
API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
1172
1477
|
WORKSPACE_ID = null;
|
|
1173
1478
|
PROJECT_ID = null;
|
|
1174
|
-
REMINDER = `[CONTEXTSTREAM]
|
|
1479
|
+
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.
|
|
1480
|
+
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").
|
|
1175
1481
|
[END]`;
|
|
1176
1482
|
FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
|
|
1177
1483
|
|
|
1178
|
-
1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>")
|
|
1484
|
+
1. FIRST MESSAGE IN SESSION: Call mcp__contextstream__init(...) then mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>")
|
|
1179
1485
|
- Returns: dynamic rules, lessons from past mistakes, relevant context
|
|
1180
1486
|
- Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
|
|
1181
1487
|
- save_exchange=true saves each conversation turn for later retrieval
|
|
1182
1488
|
- Use a consistent session_id for the entire conversation (generate once on first message)
|
|
1489
|
+
- 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.
|
|
1183
1490
|
|
|
1184
1491
|
2. FOR CODE SEARCH: Check index status, then search appropriately
|
|
1185
1492
|
\u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
|
|
@@ -1330,17 +1637,17 @@ var pre_compact_exports = {};
|
|
|
1330
1637
|
__export(pre_compact_exports, {
|
|
1331
1638
|
runPreCompactHook: () => runPreCompactHook
|
|
1332
1639
|
});
|
|
1333
|
-
import * as
|
|
1334
|
-
import * as
|
|
1335
|
-
import { homedir as
|
|
1640
|
+
import * as fs4 from "node:fs";
|
|
1641
|
+
import * as path4 from "node:path";
|
|
1642
|
+
import { homedir as homedir5 } from "node:os";
|
|
1336
1643
|
function loadConfigFromMcpJson2(cwd) {
|
|
1337
|
-
let searchDir =
|
|
1644
|
+
let searchDir = path4.resolve(cwd);
|
|
1338
1645
|
for (let i = 0; i < 5; i++) {
|
|
1339
1646
|
if (!API_KEY2) {
|
|
1340
|
-
const mcpPath =
|
|
1341
|
-
if (
|
|
1647
|
+
const mcpPath = path4.join(searchDir, ".mcp.json");
|
|
1648
|
+
if (fs4.existsSync(mcpPath)) {
|
|
1342
1649
|
try {
|
|
1343
|
-
const content =
|
|
1650
|
+
const content = fs4.readFileSync(mcpPath, "utf-8");
|
|
1344
1651
|
const config = JSON.parse(content);
|
|
1345
1652
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1346
1653
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1354,10 +1661,10 @@ function loadConfigFromMcpJson2(cwd) {
|
|
|
1354
1661
|
}
|
|
1355
1662
|
}
|
|
1356
1663
|
if (!WORKSPACE_ID2) {
|
|
1357
|
-
const csConfigPath =
|
|
1358
|
-
if (
|
|
1664
|
+
const csConfigPath = path4.join(searchDir, ".contextstream", "config.json");
|
|
1665
|
+
if (fs4.existsSync(csConfigPath)) {
|
|
1359
1666
|
try {
|
|
1360
|
-
const content =
|
|
1667
|
+
const content = fs4.readFileSync(csConfigPath, "utf-8");
|
|
1361
1668
|
const csConfig = JSON.parse(content);
|
|
1362
1669
|
if (csConfig.workspace_id) {
|
|
1363
1670
|
WORKSPACE_ID2 = csConfig.workspace_id;
|
|
@@ -1366,15 +1673,15 @@ function loadConfigFromMcpJson2(cwd) {
|
|
|
1366
1673
|
}
|
|
1367
1674
|
}
|
|
1368
1675
|
}
|
|
1369
|
-
const parentDir =
|
|
1676
|
+
const parentDir = path4.dirname(searchDir);
|
|
1370
1677
|
if (parentDir === searchDir) break;
|
|
1371
1678
|
searchDir = parentDir;
|
|
1372
1679
|
}
|
|
1373
1680
|
if (!API_KEY2) {
|
|
1374
|
-
const homeMcpPath =
|
|
1375
|
-
if (
|
|
1681
|
+
const homeMcpPath = path4.join(homedir5(), ".mcp.json");
|
|
1682
|
+
if (fs4.existsSync(homeMcpPath)) {
|
|
1376
1683
|
try {
|
|
1377
|
-
const content =
|
|
1684
|
+
const content = fs4.readFileSync(homeMcpPath, "utf-8");
|
|
1378
1685
|
const config = JSON.parse(content);
|
|
1379
1686
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1380
1687
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1396,7 +1703,7 @@ function parseTranscript(transcriptPath) {
|
|
|
1396
1703
|
let startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1397
1704
|
let firstTimestamp = true;
|
|
1398
1705
|
try {
|
|
1399
|
-
const content =
|
|
1706
|
+
const content = fs4.readFileSync(transcriptPath, "utf-8");
|
|
1400
1707
|
const lines = content.split("\n");
|
|
1401
1708
|
for (const line of lines) {
|
|
1402
1709
|
if (!line.trim()) continue;
|
|
@@ -1597,7 +1904,7 @@ async function runPreCompactHook() {
|
|
|
1597
1904
|
messages: [],
|
|
1598
1905
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1599
1906
|
};
|
|
1600
|
-
if (transcriptPath &&
|
|
1907
|
+
if (transcriptPath && fs4.existsSync(transcriptPath)) {
|
|
1601
1908
|
transcriptData = parseTranscript(transcriptPath);
|
|
1602
1909
|
}
|
|
1603
1910
|
let autoSaveStatus = "";
|
|
@@ -1656,17 +1963,17 @@ var post_compact_exports = {};
|
|
|
1656
1963
|
__export(post_compact_exports, {
|
|
1657
1964
|
runPostCompactHook: () => runPostCompactHook
|
|
1658
1965
|
});
|
|
1659
|
-
import * as
|
|
1660
|
-
import * as
|
|
1661
|
-
import { homedir as
|
|
1966
|
+
import * as fs5 from "node:fs";
|
|
1967
|
+
import * as path5 from "node:path";
|
|
1968
|
+
import { homedir as homedir6 } from "node:os";
|
|
1662
1969
|
function loadConfigFromMcpJson3(cwd) {
|
|
1663
|
-
let searchDir =
|
|
1970
|
+
let searchDir = path5.resolve(cwd);
|
|
1664
1971
|
for (let i = 0; i < 5; i++) {
|
|
1665
1972
|
if (!API_KEY3) {
|
|
1666
|
-
const mcpPath =
|
|
1667
|
-
if (
|
|
1973
|
+
const mcpPath = path5.join(searchDir, ".mcp.json");
|
|
1974
|
+
if (fs5.existsSync(mcpPath)) {
|
|
1668
1975
|
try {
|
|
1669
|
-
const content =
|
|
1976
|
+
const content = fs5.readFileSync(mcpPath, "utf-8");
|
|
1670
1977
|
const config = JSON.parse(content);
|
|
1671
1978
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1672
1979
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1680,10 +1987,10 @@ function loadConfigFromMcpJson3(cwd) {
|
|
|
1680
1987
|
}
|
|
1681
1988
|
}
|
|
1682
1989
|
if (!WORKSPACE_ID3) {
|
|
1683
|
-
const csConfigPath =
|
|
1684
|
-
if (
|
|
1990
|
+
const csConfigPath = path5.join(searchDir, ".contextstream", "config.json");
|
|
1991
|
+
if (fs5.existsSync(csConfigPath)) {
|
|
1685
1992
|
try {
|
|
1686
|
-
const content =
|
|
1993
|
+
const content = fs5.readFileSync(csConfigPath, "utf-8");
|
|
1687
1994
|
const csConfig = JSON.parse(content);
|
|
1688
1995
|
if (csConfig.workspace_id) {
|
|
1689
1996
|
WORKSPACE_ID3 = csConfig.workspace_id;
|
|
@@ -1692,15 +1999,15 @@ function loadConfigFromMcpJson3(cwd) {
|
|
|
1692
1999
|
}
|
|
1693
2000
|
}
|
|
1694
2001
|
}
|
|
1695
|
-
const parentDir =
|
|
2002
|
+
const parentDir = path5.dirname(searchDir);
|
|
1696
2003
|
if (parentDir === searchDir) break;
|
|
1697
2004
|
searchDir = parentDir;
|
|
1698
2005
|
}
|
|
1699
2006
|
if (!API_KEY3) {
|
|
1700
|
-
const homeMcpPath =
|
|
1701
|
-
if (
|
|
2007
|
+
const homeMcpPath = path5.join(homedir6(), ".mcp.json");
|
|
2008
|
+
if (fs5.existsSync(homeMcpPath)) {
|
|
1702
2009
|
try {
|
|
1703
|
-
const content =
|
|
2010
|
+
const content = fs5.readFileSync(homeMcpPath, "utf-8");
|
|
1704
2011
|
const config = JSON.parse(content);
|
|
1705
2012
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1706
2013
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1834,9 +2141,9 @@ var post_write_exports = {};
|
|
|
1834
2141
|
__export(post_write_exports, {
|
|
1835
2142
|
runPostWriteHook: () => runPostWriteHook
|
|
1836
2143
|
});
|
|
1837
|
-
import * as
|
|
1838
|
-
import * as
|
|
1839
|
-
import { homedir as
|
|
2144
|
+
import * as fs6 from "node:fs";
|
|
2145
|
+
import * as path6 from "node:path";
|
|
2146
|
+
import { homedir as homedir7 } from "node:os";
|
|
1840
2147
|
function extractFilePath(input) {
|
|
1841
2148
|
if (input.tool_input) {
|
|
1842
2149
|
const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
|
|
@@ -1861,17 +2168,17 @@ function extractCwd2(input) {
|
|
|
1861
2168
|
return process.cwd();
|
|
1862
2169
|
}
|
|
1863
2170
|
function findLocalConfig(startDir) {
|
|
1864
|
-
let currentDir =
|
|
2171
|
+
let currentDir = path6.resolve(startDir);
|
|
1865
2172
|
for (let i = 0; i < 10; i++) {
|
|
1866
|
-
const configPath =
|
|
1867
|
-
if (
|
|
2173
|
+
const configPath = path6.join(currentDir, ".contextstream", "config.json");
|
|
2174
|
+
if (fs6.existsSync(configPath)) {
|
|
1868
2175
|
try {
|
|
1869
|
-
const content =
|
|
2176
|
+
const content = fs6.readFileSync(configPath, "utf-8");
|
|
1870
2177
|
return JSON.parse(content);
|
|
1871
2178
|
} catch {
|
|
1872
2179
|
}
|
|
1873
2180
|
}
|
|
1874
|
-
const parentDir =
|
|
2181
|
+
const parentDir = path6.dirname(currentDir);
|
|
1875
2182
|
if (parentDir === currentDir) break;
|
|
1876
2183
|
currentDir = parentDir;
|
|
1877
2184
|
}
|
|
@@ -1883,12 +2190,12 @@ function loadApiConfig(startDir) {
|
|
|
1883
2190
|
if (apiKey) {
|
|
1884
2191
|
return { apiUrl, apiKey };
|
|
1885
2192
|
}
|
|
1886
|
-
let currentDir =
|
|
2193
|
+
let currentDir = path6.resolve(startDir);
|
|
1887
2194
|
for (let i = 0; i < 10; i++) {
|
|
1888
|
-
const mcpPath =
|
|
1889
|
-
if (
|
|
2195
|
+
const mcpPath = path6.join(currentDir, ".mcp.json");
|
|
2196
|
+
if (fs6.existsSync(mcpPath)) {
|
|
1890
2197
|
try {
|
|
1891
|
-
const content =
|
|
2198
|
+
const content = fs6.readFileSync(mcpPath, "utf-8");
|
|
1892
2199
|
const config = JSON.parse(content);
|
|
1893
2200
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1894
2201
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1901,15 +2208,15 @@ function loadApiConfig(startDir) {
|
|
|
1901
2208
|
} catch {
|
|
1902
2209
|
}
|
|
1903
2210
|
}
|
|
1904
|
-
const parentDir =
|
|
2211
|
+
const parentDir = path6.dirname(currentDir);
|
|
1905
2212
|
if (parentDir === currentDir) break;
|
|
1906
2213
|
currentDir = parentDir;
|
|
1907
2214
|
}
|
|
1908
2215
|
if (!apiKey) {
|
|
1909
|
-
const homeMcpPath =
|
|
1910
|
-
if (
|
|
2216
|
+
const homeMcpPath = path6.join(homedir7(), ".mcp.json");
|
|
2217
|
+
if (fs6.existsSync(homeMcpPath)) {
|
|
1911
2218
|
try {
|
|
1912
|
-
const content =
|
|
2219
|
+
const content = fs6.readFileSync(homeMcpPath, "utf-8");
|
|
1913
2220
|
const config = JSON.parse(content);
|
|
1914
2221
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
1915
2222
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -1925,15 +2232,15 @@ function loadApiConfig(startDir) {
|
|
|
1925
2232
|
return { apiUrl, apiKey };
|
|
1926
2233
|
}
|
|
1927
2234
|
function shouldIndexFile(filePath) {
|
|
1928
|
-
const ext =
|
|
2235
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
1929
2236
|
if (!INDEXABLE_EXTENSIONS.has(ext)) {
|
|
1930
|
-
const basename2 =
|
|
2237
|
+
const basename2 = path6.basename(filePath).toLowerCase();
|
|
1931
2238
|
if (!["dockerfile", "makefile", "rakefile", "gemfile", "procfile"].includes(basename2)) {
|
|
1932
2239
|
return false;
|
|
1933
2240
|
}
|
|
1934
2241
|
}
|
|
1935
2242
|
try {
|
|
1936
|
-
const stats =
|
|
2243
|
+
const stats = fs6.statSync(filePath);
|
|
1937
2244
|
if (stats.size > MAX_FILE_SIZE) {
|
|
1938
2245
|
return false;
|
|
1939
2246
|
}
|
|
@@ -1943,7 +2250,7 @@ function shouldIndexFile(filePath) {
|
|
|
1943
2250
|
return true;
|
|
1944
2251
|
}
|
|
1945
2252
|
function detectLanguage(filePath) {
|
|
1946
|
-
const ext =
|
|
2253
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
1947
2254
|
const langMap = {
|
|
1948
2255
|
".ts": "typescript",
|
|
1949
2256
|
".tsx": "typescript",
|
|
@@ -2012,8 +2319,8 @@ function detectLanguage(filePath) {
|
|
|
2012
2319
|
return langMap[ext] || "text";
|
|
2013
2320
|
}
|
|
2014
2321
|
async function indexFile(filePath, projectId, apiUrl, apiKey, projectRoot) {
|
|
2015
|
-
const content =
|
|
2016
|
-
const relativePath =
|
|
2322
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
2323
|
+
const relativePath = path6.relative(projectRoot, filePath);
|
|
2017
2324
|
const payload = {
|
|
2018
2325
|
files: [
|
|
2019
2326
|
{
|
|
@@ -2045,13 +2352,13 @@ async function indexFile(filePath, projectId, apiUrl, apiKey, projectRoot) {
|
|
|
2045
2352
|
}
|
|
2046
2353
|
}
|
|
2047
2354
|
function findProjectRoot(filePath) {
|
|
2048
|
-
let currentDir =
|
|
2355
|
+
let currentDir = path6.dirname(path6.resolve(filePath));
|
|
2049
2356
|
for (let i = 0; i < 10; i++) {
|
|
2050
|
-
const configPath =
|
|
2051
|
-
if (
|
|
2357
|
+
const configPath = path6.join(currentDir, ".contextstream", "config.json");
|
|
2358
|
+
if (fs6.existsSync(configPath)) {
|
|
2052
2359
|
return currentDir;
|
|
2053
2360
|
}
|
|
2054
|
-
const parentDir =
|
|
2361
|
+
const parentDir = path6.dirname(currentDir);
|
|
2055
2362
|
if (parentDir === currentDir) break;
|
|
2056
2363
|
currentDir = parentDir;
|
|
2057
2364
|
}
|
|
@@ -2079,8 +2386,8 @@ async function runPostWriteHook() {
|
|
|
2079
2386
|
process.exit(0);
|
|
2080
2387
|
}
|
|
2081
2388
|
const cwd = extractCwd2(input);
|
|
2082
|
-
const absolutePath =
|
|
2083
|
-
if (!
|
|
2389
|
+
const absolutePath = path6.isAbsolute(filePath) ? filePath : path6.resolve(cwd, filePath);
|
|
2390
|
+
if (!fs6.existsSync(absolutePath) || !shouldIndexFile(absolutePath)) {
|
|
2084
2391
|
process.exit(0);
|
|
2085
2392
|
}
|
|
2086
2393
|
const projectRoot = findProjectRoot(absolutePath);
|
|
@@ -2175,7 +2482,7 @@ var init_post_write = __esm({
|
|
|
2175
2482
|
".prisma",
|
|
2176
2483
|
".proto"
|
|
2177
2484
|
]);
|
|
2178
|
-
MAX_FILE_SIZE = 1024 * 1024;
|
|
2485
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
2179
2486
|
isDirectRun6 = process.argv[1]?.includes("post-write") || process.argv[2] === "post-write";
|
|
2180
2487
|
if (isDirectRun6) {
|
|
2181
2488
|
runPostWriteHook().catch(() => process.exit(0));
|
|
@@ -2480,7 +2787,7 @@ var require_ignore = __commonJS({
|
|
|
2480
2787
|
// path matching.
|
|
2481
2788
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
2482
2789
|
// @returns {TestResult} true if a file is ignored
|
|
2483
|
-
test(
|
|
2790
|
+
test(path17, checkUnignored, mode) {
|
|
2484
2791
|
let ignored = false;
|
|
2485
2792
|
let unignored = false;
|
|
2486
2793
|
let matchedRule;
|
|
@@ -2489,7 +2796,7 @@ var require_ignore = __commonJS({
|
|
|
2489
2796
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
2490
2797
|
return;
|
|
2491
2798
|
}
|
|
2492
|
-
const matched = rule[mode].test(
|
|
2799
|
+
const matched = rule[mode].test(path17);
|
|
2493
2800
|
if (!matched) {
|
|
2494
2801
|
return;
|
|
2495
2802
|
}
|
|
@@ -2510,17 +2817,17 @@ var require_ignore = __commonJS({
|
|
|
2510
2817
|
var throwError = (message, Ctor) => {
|
|
2511
2818
|
throw new Ctor(message);
|
|
2512
2819
|
};
|
|
2513
|
-
var checkPath = (
|
|
2514
|
-
if (!isString(
|
|
2820
|
+
var checkPath = (path17, originalPath, doThrow) => {
|
|
2821
|
+
if (!isString(path17)) {
|
|
2515
2822
|
return doThrow(
|
|
2516
2823
|
`path must be a string, but got \`${originalPath}\``,
|
|
2517
2824
|
TypeError
|
|
2518
2825
|
);
|
|
2519
2826
|
}
|
|
2520
|
-
if (!
|
|
2827
|
+
if (!path17) {
|
|
2521
2828
|
return doThrow(`path must not be empty`, TypeError);
|
|
2522
2829
|
}
|
|
2523
|
-
if (checkPath.isNotRelative(
|
|
2830
|
+
if (checkPath.isNotRelative(path17)) {
|
|
2524
2831
|
const r = "`path.relative()`d";
|
|
2525
2832
|
return doThrow(
|
|
2526
2833
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -2529,7 +2836,7 @@ var require_ignore = __commonJS({
|
|
|
2529
2836
|
}
|
|
2530
2837
|
return true;
|
|
2531
2838
|
};
|
|
2532
|
-
var isNotRelative = (
|
|
2839
|
+
var isNotRelative = (path17) => REGEX_TEST_INVALID_PATH.test(path17);
|
|
2533
2840
|
checkPath.isNotRelative = isNotRelative;
|
|
2534
2841
|
checkPath.convert = (p) => p;
|
|
2535
2842
|
var Ignore2 = class {
|
|
@@ -2559,19 +2866,19 @@ var require_ignore = __commonJS({
|
|
|
2559
2866
|
}
|
|
2560
2867
|
// @returns {TestResult}
|
|
2561
2868
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
2562
|
-
const
|
|
2869
|
+
const path17 = originalPath && checkPath.convert(originalPath);
|
|
2563
2870
|
checkPath(
|
|
2564
|
-
|
|
2871
|
+
path17,
|
|
2565
2872
|
originalPath,
|
|
2566
2873
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
2567
2874
|
);
|
|
2568
|
-
return this._t(
|
|
2875
|
+
return this._t(path17, cache, checkUnignored, slices);
|
|
2569
2876
|
}
|
|
2570
|
-
checkIgnore(
|
|
2571
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
2572
|
-
return this.test(
|
|
2877
|
+
checkIgnore(path17) {
|
|
2878
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path17)) {
|
|
2879
|
+
return this.test(path17);
|
|
2573
2880
|
}
|
|
2574
|
-
const slices =
|
|
2881
|
+
const slices = path17.split(SLASH).filter(Boolean);
|
|
2575
2882
|
slices.pop();
|
|
2576
2883
|
if (slices.length) {
|
|
2577
2884
|
const parent = this._t(
|
|
@@ -2584,18 +2891,18 @@ var require_ignore = __commonJS({
|
|
|
2584
2891
|
return parent;
|
|
2585
2892
|
}
|
|
2586
2893
|
}
|
|
2587
|
-
return this._rules.test(
|
|
2894
|
+
return this._rules.test(path17, false, MODE_CHECK_IGNORE);
|
|
2588
2895
|
}
|
|
2589
|
-
_t(
|
|
2590
|
-
if (
|
|
2591
|
-
return cache[
|
|
2896
|
+
_t(path17, cache, checkUnignored, slices) {
|
|
2897
|
+
if (path17 in cache) {
|
|
2898
|
+
return cache[path17];
|
|
2592
2899
|
}
|
|
2593
2900
|
if (!slices) {
|
|
2594
|
-
slices =
|
|
2901
|
+
slices = path17.split(SLASH).filter(Boolean);
|
|
2595
2902
|
}
|
|
2596
2903
|
slices.pop();
|
|
2597
2904
|
if (!slices.length) {
|
|
2598
|
-
return cache[
|
|
2905
|
+
return cache[path17] = this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
2599
2906
|
}
|
|
2600
2907
|
const parent = this._t(
|
|
2601
2908
|
slices.join(SLASH) + SLASH,
|
|
@@ -2603,29 +2910,29 @@ var require_ignore = __commonJS({
|
|
|
2603
2910
|
checkUnignored,
|
|
2604
2911
|
slices
|
|
2605
2912
|
);
|
|
2606
|
-
return cache[
|
|
2913
|
+
return cache[path17] = parent.ignored ? parent : this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
2607
2914
|
}
|
|
2608
|
-
ignores(
|
|
2609
|
-
return this._test(
|
|
2915
|
+
ignores(path17) {
|
|
2916
|
+
return this._test(path17, this._ignoreCache, false).ignored;
|
|
2610
2917
|
}
|
|
2611
2918
|
createFilter() {
|
|
2612
|
-
return (
|
|
2919
|
+
return (path17) => !this.ignores(path17);
|
|
2613
2920
|
}
|
|
2614
2921
|
filter(paths) {
|
|
2615
2922
|
return makeArray(paths).filter(this.createFilter());
|
|
2616
2923
|
}
|
|
2617
2924
|
// @returns {TestResult}
|
|
2618
|
-
test(
|
|
2619
|
-
return this._test(
|
|
2925
|
+
test(path17) {
|
|
2926
|
+
return this._test(path17, this._testCache, true);
|
|
2620
2927
|
}
|
|
2621
2928
|
};
|
|
2622
2929
|
var factory = (options) => new Ignore2(options);
|
|
2623
|
-
var isPathValid = (
|
|
2930
|
+
var isPathValid = (path17) => checkPath(path17 && checkPath.convert(path17), path17, RETURN_FALSE);
|
|
2624
2931
|
var setupWindows = () => {
|
|
2625
2932
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
2626
2933
|
checkPath.convert = makePosix;
|
|
2627
2934
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
2628
|
-
checkPath.isNotRelative = (
|
|
2935
|
+
checkPath.isNotRelative = (path17) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path17) || isNotRelative(path17);
|
|
2629
2936
|
};
|
|
2630
2937
|
if (
|
|
2631
2938
|
// Detect `process` so that it can run in browsers.
|
|
@@ -2641,16 +2948,16 @@ var require_ignore = __commonJS({
|
|
|
2641
2948
|
});
|
|
2642
2949
|
|
|
2643
2950
|
// src/ignore.ts
|
|
2644
|
-
import * as
|
|
2645
|
-
import * as
|
|
2951
|
+
import * as fs7 from "fs";
|
|
2952
|
+
import * as path7 from "path";
|
|
2646
2953
|
async function loadIgnorePatterns(projectRoot) {
|
|
2647
2954
|
const ig = (0, import_ignore.default)();
|
|
2648
2955
|
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
2649
2956
|
ig.add(DEFAULT_IGNORE_PATTERNS);
|
|
2650
|
-
const ignoreFilePath =
|
|
2957
|
+
const ignoreFilePath = path7.join(projectRoot, IGNORE_FILENAME);
|
|
2651
2958
|
let hasUserPatterns = false;
|
|
2652
2959
|
try {
|
|
2653
|
-
const content = await
|
|
2960
|
+
const content = await fs7.promises.readFile(ignoreFilePath, "utf-8");
|
|
2654
2961
|
const userPatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
2655
2962
|
if (userPatterns.length > 0) {
|
|
2656
2963
|
ig.add(userPatterns);
|
|
@@ -2731,8 +3038,8 @@ __export(files_exports, {
|
|
|
2731
3038
|
sha256Hex: () => sha256Hex,
|
|
2732
3039
|
writeHashManifest: () => writeHashManifest
|
|
2733
3040
|
});
|
|
2734
|
-
import * as
|
|
2735
|
-
import * as
|
|
3041
|
+
import * as fs8 from "fs";
|
|
3042
|
+
import * as path8 from "path";
|
|
2736
3043
|
import { exec } from "child_process";
|
|
2737
3044
|
import { promisify } from "util";
|
|
2738
3045
|
import * as os from "os";
|
|
@@ -2852,14 +3159,14 @@ async function readFilesFromDirectory(rootPath, options = {}) {
|
|
|
2852
3159
|
if (files.length >= maxFiles) return;
|
|
2853
3160
|
let entries;
|
|
2854
3161
|
try {
|
|
2855
|
-
entries = await
|
|
3162
|
+
entries = await fs8.promises.readdir(dir, { withFileTypes: true });
|
|
2856
3163
|
} catch {
|
|
2857
3164
|
return;
|
|
2858
3165
|
}
|
|
2859
3166
|
for (const entry of entries) {
|
|
2860
3167
|
if (files.length >= maxFiles) break;
|
|
2861
|
-
const fullPath =
|
|
2862
|
-
const relPath =
|
|
3168
|
+
const fullPath = path8.join(dir, entry.name);
|
|
3169
|
+
const relPath = path8.join(relativePath, entry.name);
|
|
2863
3170
|
if (entry.isDirectory()) {
|
|
2864
3171
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
2865
3172
|
if (ig.ignores(relPath + "/")) continue;
|
|
@@ -2870,9 +3177,9 @@ async function readFilesFromDirectory(rootPath, options = {}) {
|
|
|
2870
3177
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
2871
3178
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
2872
3179
|
try {
|
|
2873
|
-
const stat = await
|
|
3180
|
+
const stat = await fs8.promises.stat(fullPath);
|
|
2874
3181
|
if (stat.size > maxFileSize) continue;
|
|
2875
|
-
const content = await
|
|
3182
|
+
const content = await fs8.promises.readFile(fullPath, "utf-8");
|
|
2876
3183
|
const file = {
|
|
2877
3184
|
path: relPath,
|
|
2878
3185
|
content,
|
|
@@ -2911,13 +3218,13 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
2911
3218
|
async function* walkDir(dir, relativePath = "") {
|
|
2912
3219
|
let entries;
|
|
2913
3220
|
try {
|
|
2914
|
-
entries = await
|
|
3221
|
+
entries = await fs8.promises.readdir(dir, { withFileTypes: true });
|
|
2915
3222
|
} catch {
|
|
2916
3223
|
return;
|
|
2917
3224
|
}
|
|
2918
3225
|
for (const entry of entries) {
|
|
2919
|
-
const fullPath =
|
|
2920
|
-
const relPath =
|
|
3226
|
+
const fullPath = path8.join(dir, entry.name);
|
|
3227
|
+
const relPath = path8.join(relativePath, entry.name);
|
|
2921
3228
|
if (entry.isDirectory()) {
|
|
2922
3229
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
2923
3230
|
if (ig.ignores(relPath + "/")) continue;
|
|
@@ -2928,9 +3235,9 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
2928
3235
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
2929
3236
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
2930
3237
|
try {
|
|
2931
|
-
const stat = await
|
|
3238
|
+
const stat = await fs8.promises.stat(fullPath);
|
|
2932
3239
|
if (stat.size > maxFileSize) continue;
|
|
2933
|
-
const content = await
|
|
3240
|
+
const content = await fs8.promises.readFile(fullPath, "utf-8");
|
|
2934
3241
|
const file = {
|
|
2935
3242
|
path: relPath,
|
|
2936
3243
|
content,
|
|
@@ -2997,13 +3304,13 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
2997
3304
|
async function* walkDir(dir, relativePath = "") {
|
|
2998
3305
|
let entries;
|
|
2999
3306
|
try {
|
|
3000
|
-
entries = await
|
|
3307
|
+
entries = await fs8.promises.readdir(dir, { withFileTypes: true });
|
|
3001
3308
|
} catch {
|
|
3002
3309
|
return;
|
|
3003
3310
|
}
|
|
3004
3311
|
for (const entry of entries) {
|
|
3005
|
-
const fullPath =
|
|
3006
|
-
const relPath =
|
|
3312
|
+
const fullPath = path8.join(dir, entry.name);
|
|
3313
|
+
const relPath = path8.join(relativePath, entry.name);
|
|
3007
3314
|
if (entry.isDirectory()) {
|
|
3008
3315
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3009
3316
|
if (ig.ignores(relPath + "/")) continue;
|
|
@@ -3014,11 +3321,11 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
3014
3321
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
3015
3322
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
3016
3323
|
try {
|
|
3017
|
-
const stat = await
|
|
3324
|
+
const stat = await fs8.promises.stat(fullPath);
|
|
3018
3325
|
filesScanned++;
|
|
3019
3326
|
if (stat.mtimeMs <= sinceMs) continue;
|
|
3020
3327
|
if (stat.size > maxFileSize) continue;
|
|
3021
|
-
const content = await
|
|
3328
|
+
const content = await fs8.promises.readFile(fullPath, "utf-8");
|
|
3022
3329
|
filesChanged++;
|
|
3023
3330
|
const file = {
|
|
3024
3331
|
path: relPath,
|
|
@@ -3087,7 +3394,7 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
3087
3394
|
}
|
|
3088
3395
|
let entries;
|
|
3089
3396
|
try {
|
|
3090
|
-
entries = await
|
|
3397
|
+
entries = await fs8.promises.readdir(dir, { withFileTypes: true });
|
|
3091
3398
|
} catch {
|
|
3092
3399
|
return;
|
|
3093
3400
|
}
|
|
@@ -3096,8 +3403,8 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
3096
3403
|
stopped = true;
|
|
3097
3404
|
return;
|
|
3098
3405
|
}
|
|
3099
|
-
const fullPath =
|
|
3100
|
-
const relPath =
|
|
3406
|
+
const fullPath = path8.join(dir, entry.name);
|
|
3407
|
+
const relPath = path8.join(relativePath, entry.name);
|
|
3101
3408
|
if (entry.isDirectory()) {
|
|
3102
3409
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3103
3410
|
if (ig.ignores(relPath + "/")) continue;
|
|
@@ -3108,7 +3415,7 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
3108
3415
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
3109
3416
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
3110
3417
|
try {
|
|
3111
|
-
const stat = await
|
|
3418
|
+
const stat = await fs8.promises.stat(fullPath);
|
|
3112
3419
|
if (stat.size > maxFileSize) continue;
|
|
3113
3420
|
count++;
|
|
3114
3421
|
if (count >= maxFiles) {
|
|
@@ -3160,11 +3467,11 @@ function sha256Hex(content) {
|
|
|
3160
3467
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
3161
3468
|
}
|
|
3162
3469
|
function hashManifestPath(projectId) {
|
|
3163
|
-
return
|
|
3470
|
+
return path8.join(os.homedir(), ".contextstream", "file-hashes", `${projectId}.json`);
|
|
3164
3471
|
}
|
|
3165
3472
|
function readHashManifest(projectId) {
|
|
3166
3473
|
try {
|
|
3167
|
-
const content =
|
|
3474
|
+
const content = fs8.readFileSync(hashManifestPath(projectId), "utf-8");
|
|
3168
3475
|
const parsed = JSON.parse(content);
|
|
3169
3476
|
return new Map(Object.entries(parsed));
|
|
3170
3477
|
} catch {
|
|
@@ -3174,16 +3481,16 @@ function readHashManifest(projectId) {
|
|
|
3174
3481
|
function writeHashManifest(projectId, hashes) {
|
|
3175
3482
|
const filePath = hashManifestPath(projectId);
|
|
3176
3483
|
try {
|
|
3177
|
-
const dir =
|
|
3178
|
-
|
|
3484
|
+
const dir = path8.dirname(filePath);
|
|
3485
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3179
3486
|
const obj = Object.fromEntries(hashes);
|
|
3180
|
-
|
|
3487
|
+
fs8.writeFileSync(filePath, JSON.stringify(obj, null, 2));
|
|
3181
3488
|
} catch {
|
|
3182
3489
|
}
|
|
3183
3490
|
}
|
|
3184
3491
|
function deleteHashManifest(projectId) {
|
|
3185
3492
|
try {
|
|
3186
|
-
|
|
3493
|
+
fs8.unlinkSync(hashManifestPath(projectId));
|
|
3187
3494
|
} catch {
|
|
3188
3495
|
}
|
|
3189
3496
|
}
|
|
@@ -3296,7 +3603,7 @@ var init_files = __esm({
|
|
|
3296
3603
|
"Gemfile.lock",
|
|
3297
3604
|
"composer.lock"
|
|
3298
3605
|
]);
|
|
3299
|
-
MAX_FILE_SIZE2 = 1024 * 1024;
|
|
3606
|
+
MAX_FILE_SIZE2 = 5 * 1024 * 1024;
|
|
3300
3607
|
MAX_BATCH_BYTES = 10 * 1024 * 1024;
|
|
3301
3608
|
LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
|
|
3302
3609
|
MAX_FILES_PER_BATCH = 200;
|
|
@@ -3347,17 +3654,17 @@ __export(hooks_config_exports, {
|
|
|
3347
3654
|
writeCursorHooksConfig: () => writeCursorHooksConfig,
|
|
3348
3655
|
writeIndexStatus: () => writeIndexStatus
|
|
3349
3656
|
});
|
|
3350
|
-
import * as
|
|
3657
|
+
import * as fs9 from "node:fs/promises";
|
|
3351
3658
|
import * as fsSync from "node:fs";
|
|
3352
|
-
import * as
|
|
3353
|
-
import { homedir as
|
|
3659
|
+
import * as path9 from "node:path";
|
|
3660
|
+
import { homedir as homedir9 } from "node:os";
|
|
3354
3661
|
import { fileURLToPath } from "node:url";
|
|
3355
3662
|
function getHookCommand(hookName2) {
|
|
3356
3663
|
const isWindows = process.platform === "win32";
|
|
3357
3664
|
if (isWindows) {
|
|
3358
3665
|
const localAppData = process.env.LOCALAPPDATA;
|
|
3359
3666
|
if (localAppData) {
|
|
3360
|
-
const windowsBinaryPath =
|
|
3667
|
+
const windowsBinaryPath = path9.join(localAppData, "ContextStream", "contextstream-mcp.exe");
|
|
3361
3668
|
if (fsSync.existsSync(windowsBinaryPath)) {
|
|
3362
3669
|
return `"${windowsBinaryPath}" hook ${hookName2}`;
|
|
3363
3670
|
}
|
|
@@ -3369,8 +3676,8 @@ function getHookCommand(hookName2) {
|
|
|
3369
3676
|
}
|
|
3370
3677
|
}
|
|
3371
3678
|
try {
|
|
3372
|
-
const __dirname =
|
|
3373
|
-
const indexPath =
|
|
3679
|
+
const __dirname = path9.dirname(fileURLToPath(import.meta.url));
|
|
3680
|
+
const indexPath = path9.join(__dirname, "index.js");
|
|
3374
3681
|
if (fsSync.existsSync(indexPath)) {
|
|
3375
3682
|
return `node "${indexPath}" hook ${hookName2}`;
|
|
3376
3683
|
}
|
|
@@ -3380,15 +3687,15 @@ function getHookCommand(hookName2) {
|
|
|
3380
3687
|
}
|
|
3381
3688
|
function getClaudeSettingsPath(scope, projectPath) {
|
|
3382
3689
|
if (scope === "user") {
|
|
3383
|
-
return
|
|
3690
|
+
return path9.join(homedir9(), ".claude", "settings.json");
|
|
3384
3691
|
}
|
|
3385
3692
|
if (!projectPath) {
|
|
3386
3693
|
throw new Error("projectPath required for project scope");
|
|
3387
3694
|
}
|
|
3388
|
-
return
|
|
3695
|
+
return path9.join(projectPath, ".claude", "settings.json");
|
|
3389
3696
|
}
|
|
3390
3697
|
function getHooksDir() {
|
|
3391
|
-
return
|
|
3698
|
+
return path9.join(homedir9(), ".claude", "hooks");
|
|
3392
3699
|
}
|
|
3393
3700
|
function buildHooksConfig(options) {
|
|
3394
3701
|
const userPromptHooks = [
|
|
@@ -3564,7 +3871,7 @@ function buildHooksConfig(options) {
|
|
|
3564
3871
|
}
|
|
3565
3872
|
async function installHookScripts(options) {
|
|
3566
3873
|
const hooksDir = getHooksDir();
|
|
3567
|
-
await
|
|
3874
|
+
await fs9.mkdir(hooksDir, { recursive: true });
|
|
3568
3875
|
const result = {
|
|
3569
3876
|
preToolUse: getHookCommand("pre-tool-use"),
|
|
3570
3877
|
userPrompt: getHookCommand("user-prompt-submit")
|
|
@@ -3583,7 +3890,7 @@ async function installHookScripts(options) {
|
|
|
3583
3890
|
async function readClaudeSettings(scope, projectPath) {
|
|
3584
3891
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
3585
3892
|
try {
|
|
3586
|
-
const content = await
|
|
3893
|
+
const content = await fs9.readFile(settingsPath, "utf-8");
|
|
3587
3894
|
return JSON.parse(content);
|
|
3588
3895
|
} catch {
|
|
3589
3896
|
return {};
|
|
@@ -3591,9 +3898,9 @@ async function readClaudeSettings(scope, projectPath) {
|
|
|
3591
3898
|
}
|
|
3592
3899
|
async function writeClaudeSettings(settings, scope, projectPath) {
|
|
3593
3900
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
3594
|
-
const dir =
|
|
3595
|
-
await
|
|
3596
|
-
await
|
|
3901
|
+
const dir = path9.dirname(settingsPath);
|
|
3902
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
3903
|
+
await fs9.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
3597
3904
|
}
|
|
3598
3905
|
function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
3599
3906
|
const settings = { ...existingSettings };
|
|
@@ -3743,12 +4050,12 @@ If you prefer to configure manually, add to \`~/.claude/settings.json\`:
|
|
|
3743
4050
|
`.trim();
|
|
3744
4051
|
}
|
|
3745
4052
|
function getIndexStatusPath() {
|
|
3746
|
-
return
|
|
4053
|
+
return path9.join(homedir9(), ".contextstream", "indexed-projects.json");
|
|
3747
4054
|
}
|
|
3748
4055
|
async function readIndexStatus() {
|
|
3749
4056
|
const statusPath = getIndexStatusPath();
|
|
3750
4057
|
try {
|
|
3751
|
-
const content = await
|
|
4058
|
+
const content = await fs9.readFile(statusPath, "utf-8");
|
|
3752
4059
|
return JSON.parse(content);
|
|
3753
4060
|
} catch {
|
|
3754
4061
|
return { version: 1, projects: {} };
|
|
@@ -3756,13 +4063,13 @@ async function readIndexStatus() {
|
|
|
3756
4063
|
}
|
|
3757
4064
|
async function writeIndexStatus(status) {
|
|
3758
4065
|
const statusPath = getIndexStatusPath();
|
|
3759
|
-
const dir =
|
|
3760
|
-
await
|
|
3761
|
-
await
|
|
4066
|
+
const dir = path9.dirname(statusPath);
|
|
4067
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
4068
|
+
await fs9.writeFile(statusPath, JSON.stringify(status, null, 2));
|
|
3762
4069
|
}
|
|
3763
4070
|
async function markProjectIndexed(projectPath, options) {
|
|
3764
4071
|
const status = await readIndexStatus();
|
|
3765
|
-
const resolvedPath =
|
|
4072
|
+
const resolvedPath = path9.resolve(projectPath);
|
|
3766
4073
|
status.projects[resolvedPath] = {
|
|
3767
4074
|
indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3768
4075
|
project_id: options?.project_id,
|
|
@@ -3772,7 +4079,7 @@ async function markProjectIndexed(projectPath, options) {
|
|
|
3772
4079
|
}
|
|
3773
4080
|
async function unmarkProjectIndexed(projectPath) {
|
|
3774
4081
|
const status = await readIndexStatus();
|
|
3775
|
-
const resolvedPath =
|
|
4082
|
+
const resolvedPath = path9.resolve(projectPath);
|
|
3776
4083
|
delete status.projects[resolvedPath];
|
|
3777
4084
|
await writeIndexStatus(status);
|
|
3778
4085
|
}
|
|
@@ -3785,12 +4092,12 @@ async function clearProjectIndex(projectPath, projectId) {
|
|
|
3785
4092
|
}
|
|
3786
4093
|
function getClineHooksDir(scope, projectPath) {
|
|
3787
4094
|
if (scope === "global") {
|
|
3788
|
-
return
|
|
4095
|
+
return path9.join(homedir9(), "Documents", "Cline", "Rules", "Hooks");
|
|
3789
4096
|
}
|
|
3790
4097
|
if (!projectPath) {
|
|
3791
4098
|
throw new Error("projectPath required for project scope");
|
|
3792
4099
|
}
|
|
3793
|
-
return
|
|
4100
|
+
return path9.join(projectPath, ".clinerules", "hooks");
|
|
3794
4101
|
}
|
|
3795
4102
|
function getHookWrapperScript(hookName2) {
|
|
3796
4103
|
const isWindows = process.platform === "win32";
|
|
@@ -3814,107 +4121,107 @@ exec ${command}
|
|
|
3814
4121
|
}
|
|
3815
4122
|
async function installClineHookScripts(options) {
|
|
3816
4123
|
const hooksDir = getClineHooksDir(options.scope, options.projectPath);
|
|
3817
|
-
await
|
|
4124
|
+
await fs9.mkdir(hooksDir, { recursive: true });
|
|
3818
4125
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
3819
4126
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
3820
4127
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
3821
|
-
const preToolUsePath =
|
|
3822
|
-
const userPromptPath =
|
|
3823
|
-
const postToolUsePath =
|
|
3824
|
-
await
|
|
3825
|
-
await
|
|
4128
|
+
const preToolUsePath = path9.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
4129
|
+
const userPromptPath = path9.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
4130
|
+
const postToolUsePath = path9.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
4131
|
+
await fs9.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
4132
|
+
await fs9.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
3826
4133
|
const result = {
|
|
3827
4134
|
preToolUse: preToolUsePath,
|
|
3828
4135
|
userPromptSubmit: userPromptPath
|
|
3829
4136
|
};
|
|
3830
4137
|
if (options.includePostWrite !== false) {
|
|
3831
|
-
await
|
|
4138
|
+
await fs9.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
3832
4139
|
result.postToolUse = postToolUsePath;
|
|
3833
4140
|
}
|
|
3834
4141
|
return result;
|
|
3835
4142
|
}
|
|
3836
4143
|
function getRooCodeHooksDir(scope, projectPath) {
|
|
3837
4144
|
if (scope === "global") {
|
|
3838
|
-
return
|
|
4145
|
+
return path9.join(homedir9(), ".roo", "hooks");
|
|
3839
4146
|
}
|
|
3840
4147
|
if (!projectPath) {
|
|
3841
4148
|
throw new Error("projectPath required for project scope");
|
|
3842
4149
|
}
|
|
3843
|
-
return
|
|
4150
|
+
return path9.join(projectPath, ".roo", "hooks");
|
|
3844
4151
|
}
|
|
3845
4152
|
async function installRooCodeHookScripts(options) {
|
|
3846
4153
|
const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
|
|
3847
|
-
await
|
|
4154
|
+
await fs9.mkdir(hooksDir, { recursive: true });
|
|
3848
4155
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
3849
4156
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
3850
4157
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
3851
|
-
const preToolUsePath =
|
|
3852
|
-
const userPromptPath =
|
|
3853
|
-
const postToolUsePath =
|
|
3854
|
-
await
|
|
3855
|
-
await
|
|
4158
|
+
const preToolUsePath = path9.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
4159
|
+
const userPromptPath = path9.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
4160
|
+
const postToolUsePath = path9.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
4161
|
+
await fs9.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
4162
|
+
await fs9.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
3856
4163
|
const result = {
|
|
3857
4164
|
preToolUse: preToolUsePath,
|
|
3858
4165
|
userPromptSubmit: userPromptPath
|
|
3859
4166
|
};
|
|
3860
4167
|
if (options.includePostWrite !== false) {
|
|
3861
|
-
await
|
|
4168
|
+
await fs9.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
3862
4169
|
result.postToolUse = postToolUsePath;
|
|
3863
4170
|
}
|
|
3864
4171
|
return result;
|
|
3865
4172
|
}
|
|
3866
4173
|
function getKiloCodeHooksDir(scope, projectPath) {
|
|
3867
4174
|
if (scope === "global") {
|
|
3868
|
-
return
|
|
4175
|
+
return path9.join(homedir9(), ".kilocode", "hooks");
|
|
3869
4176
|
}
|
|
3870
4177
|
if (!projectPath) {
|
|
3871
4178
|
throw new Error("projectPath required for project scope");
|
|
3872
4179
|
}
|
|
3873
|
-
return
|
|
4180
|
+
return path9.join(projectPath, ".kilocode", "hooks");
|
|
3874
4181
|
}
|
|
3875
4182
|
async function installKiloCodeHookScripts(options) {
|
|
3876
4183
|
const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
|
|
3877
|
-
await
|
|
4184
|
+
await fs9.mkdir(hooksDir, { recursive: true });
|
|
3878
4185
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
3879
4186
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
3880
4187
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
3881
|
-
const preToolUsePath =
|
|
3882
|
-
const userPromptPath =
|
|
3883
|
-
const postToolUsePath =
|
|
3884
|
-
await
|
|
3885
|
-
await
|
|
4188
|
+
const preToolUsePath = path9.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
4189
|
+
const userPromptPath = path9.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
4190
|
+
const postToolUsePath = path9.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
4191
|
+
await fs9.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
4192
|
+
await fs9.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
3886
4193
|
const result = {
|
|
3887
4194
|
preToolUse: preToolUsePath,
|
|
3888
4195
|
userPromptSubmit: userPromptPath
|
|
3889
4196
|
};
|
|
3890
4197
|
if (options.includePostWrite !== false) {
|
|
3891
|
-
await
|
|
4198
|
+
await fs9.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
3892
4199
|
result.postToolUse = postToolUsePath;
|
|
3893
4200
|
}
|
|
3894
4201
|
return result;
|
|
3895
4202
|
}
|
|
3896
4203
|
function getCursorHooksConfigPath(scope, projectPath) {
|
|
3897
4204
|
if (scope === "global") {
|
|
3898
|
-
return
|
|
4205
|
+
return path9.join(homedir9(), ".cursor", "hooks.json");
|
|
3899
4206
|
}
|
|
3900
4207
|
if (!projectPath) {
|
|
3901
4208
|
throw new Error("projectPath required for project scope");
|
|
3902
4209
|
}
|
|
3903
|
-
return
|
|
4210
|
+
return path9.join(projectPath, ".cursor", "hooks.json");
|
|
3904
4211
|
}
|
|
3905
4212
|
function getCursorHooksDir(scope, projectPath) {
|
|
3906
4213
|
if (scope === "global") {
|
|
3907
|
-
return
|
|
4214
|
+
return path9.join(homedir9(), ".cursor", "hooks");
|
|
3908
4215
|
}
|
|
3909
4216
|
if (!projectPath) {
|
|
3910
4217
|
throw new Error("projectPath required for project scope");
|
|
3911
4218
|
}
|
|
3912
|
-
return
|
|
4219
|
+
return path9.join(projectPath, ".cursor", "hooks");
|
|
3913
4220
|
}
|
|
3914
4221
|
async function readCursorHooksConfig(scope, projectPath) {
|
|
3915
4222
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
3916
4223
|
try {
|
|
3917
|
-
const content = await
|
|
4224
|
+
const content = await fs9.readFile(configPath, "utf-8");
|
|
3918
4225
|
return JSON.parse(content);
|
|
3919
4226
|
} catch {
|
|
3920
4227
|
return { version: 1, hooks: {} };
|
|
@@ -3922,13 +4229,13 @@ async function readCursorHooksConfig(scope, projectPath) {
|
|
|
3922
4229
|
}
|
|
3923
4230
|
async function writeCursorHooksConfig(config, scope, projectPath) {
|
|
3924
4231
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
3925
|
-
const dir =
|
|
3926
|
-
await
|
|
3927
|
-
await
|
|
4232
|
+
const dir = path9.dirname(configPath);
|
|
4233
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
4234
|
+
await fs9.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
3928
4235
|
}
|
|
3929
4236
|
async function installCursorHookScripts(options) {
|
|
3930
4237
|
const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
|
|
3931
|
-
await
|
|
4238
|
+
await fs9.mkdir(hooksDir, { recursive: true });
|
|
3932
4239
|
const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
|
|
3933
4240
|
const filterContextStreamHooks = (hooks2) => {
|
|
3934
4241
|
if (!hooks2) return [];
|
|
@@ -4944,13 +5251,13 @@ var auto_rules_exports = {};
|
|
|
4944
5251
|
__export(auto_rules_exports, {
|
|
4945
5252
|
runAutoRulesHook: () => runAutoRulesHook
|
|
4946
5253
|
});
|
|
4947
|
-
import * as
|
|
4948
|
-
import * as
|
|
4949
|
-
import { homedir as
|
|
5254
|
+
import * as fs10 from "node:fs";
|
|
5255
|
+
import * as path10 from "node:path";
|
|
5256
|
+
import { homedir as homedir10 } from "node:os";
|
|
4950
5257
|
function hasRunRecently() {
|
|
4951
5258
|
try {
|
|
4952
|
-
if (!
|
|
4953
|
-
const stat =
|
|
5259
|
+
if (!fs10.existsSync(MARKER_FILE)) return false;
|
|
5260
|
+
const stat = fs10.statSync(MARKER_FILE);
|
|
4954
5261
|
const age = Date.now() - stat.mtimeMs;
|
|
4955
5262
|
return age < COOLDOWN_MS;
|
|
4956
5263
|
} catch {
|
|
@@ -4959,11 +5266,11 @@ function hasRunRecently() {
|
|
|
4959
5266
|
}
|
|
4960
5267
|
function markAsRan() {
|
|
4961
5268
|
try {
|
|
4962
|
-
const dir =
|
|
4963
|
-
if (!
|
|
4964
|
-
|
|
5269
|
+
const dir = path10.dirname(MARKER_FILE);
|
|
5270
|
+
if (!fs10.existsSync(dir)) {
|
|
5271
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
4965
5272
|
}
|
|
4966
|
-
|
|
5273
|
+
fs10.writeFileSync(MARKER_FILE, (/* @__PURE__ */ new Date()).toISOString());
|
|
4967
5274
|
} catch {
|
|
4968
5275
|
}
|
|
4969
5276
|
}
|
|
@@ -4992,8 +5299,8 @@ function extractCwd3(input) {
|
|
|
4992
5299
|
}
|
|
4993
5300
|
function hasPythonHooks(settingsPath) {
|
|
4994
5301
|
try {
|
|
4995
|
-
if (!
|
|
4996
|
-
const content =
|
|
5302
|
+
if (!fs10.existsSync(settingsPath)) return false;
|
|
5303
|
+
const content = fs10.readFileSync(settingsPath, "utf-8");
|
|
4997
5304
|
const settings = JSON.parse(content);
|
|
4998
5305
|
const hooks2 = settings.hooks;
|
|
4999
5306
|
if (!hooks2) return false;
|
|
@@ -5017,8 +5324,8 @@ function hasPythonHooks(settingsPath) {
|
|
|
5017
5324
|
}
|
|
5018
5325
|
}
|
|
5019
5326
|
function detectPythonHooks(cwd) {
|
|
5020
|
-
const globalSettingsPath =
|
|
5021
|
-
const projectSettingsPath =
|
|
5327
|
+
const globalSettingsPath = path10.join(homedir10(), ".claude", "settings.json");
|
|
5328
|
+
const projectSettingsPath = path10.join(cwd, ".claude", "settings.json");
|
|
5022
5329
|
return {
|
|
5023
5330
|
global: hasPythonHooks(globalSettingsPath),
|
|
5024
5331
|
project: hasPythonHooks(projectSettingsPath)
|
|
@@ -5083,7 +5390,7 @@ var init_auto_rules = __esm({
|
|
|
5083
5390
|
API_URL5 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
5084
5391
|
API_KEY5 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
5085
5392
|
ENABLED7 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
|
|
5086
|
-
MARKER_FILE =
|
|
5393
|
+
MARKER_FILE = path10.join(homedir10(), ".contextstream", ".auto-rules-ran");
|
|
5087
5394
|
COOLDOWN_MS = 4 * 60 * 60 * 1e3;
|
|
5088
5395
|
isDirectRun7 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
|
|
5089
5396
|
if (isDirectRun7) {
|
|
@@ -5097,17 +5404,17 @@ var on_bash_exports = {};
|
|
|
5097
5404
|
__export(on_bash_exports, {
|
|
5098
5405
|
runOnBashHook: () => runOnBashHook
|
|
5099
5406
|
});
|
|
5100
|
-
import * as
|
|
5101
|
-
import * as
|
|
5102
|
-
import { homedir as
|
|
5407
|
+
import * as fs11 from "node:fs";
|
|
5408
|
+
import * as path11 from "node:path";
|
|
5409
|
+
import { homedir as homedir11 } from "node:os";
|
|
5103
5410
|
function loadConfigFromMcpJson4(cwd) {
|
|
5104
|
-
let searchDir =
|
|
5411
|
+
let searchDir = path11.resolve(cwd);
|
|
5105
5412
|
for (let i = 0; i < 5; i++) {
|
|
5106
5413
|
if (!API_KEY6) {
|
|
5107
|
-
const mcpPath =
|
|
5108
|
-
if (
|
|
5414
|
+
const mcpPath = path11.join(searchDir, ".mcp.json");
|
|
5415
|
+
if (fs11.existsSync(mcpPath)) {
|
|
5109
5416
|
try {
|
|
5110
|
-
const content =
|
|
5417
|
+
const content = fs11.readFileSync(mcpPath, "utf-8");
|
|
5111
5418
|
const config = JSON.parse(content);
|
|
5112
5419
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5113
5420
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5121,10 +5428,10 @@ function loadConfigFromMcpJson4(cwd) {
|
|
|
5121
5428
|
}
|
|
5122
5429
|
}
|
|
5123
5430
|
if (!WORKSPACE_ID4) {
|
|
5124
|
-
const csConfigPath =
|
|
5125
|
-
if (
|
|
5431
|
+
const csConfigPath = path11.join(searchDir, ".contextstream", "config.json");
|
|
5432
|
+
if (fs11.existsSync(csConfigPath)) {
|
|
5126
5433
|
try {
|
|
5127
|
-
const content =
|
|
5434
|
+
const content = fs11.readFileSync(csConfigPath, "utf-8");
|
|
5128
5435
|
const csConfig = JSON.parse(content);
|
|
5129
5436
|
if (csConfig.workspace_id) {
|
|
5130
5437
|
WORKSPACE_ID4 = csConfig.workspace_id;
|
|
@@ -5133,15 +5440,15 @@ function loadConfigFromMcpJson4(cwd) {
|
|
|
5133
5440
|
}
|
|
5134
5441
|
}
|
|
5135
5442
|
}
|
|
5136
|
-
const parentDir =
|
|
5443
|
+
const parentDir = path11.dirname(searchDir);
|
|
5137
5444
|
if (parentDir === searchDir) break;
|
|
5138
5445
|
searchDir = parentDir;
|
|
5139
5446
|
}
|
|
5140
5447
|
if (!API_KEY6) {
|
|
5141
|
-
const homeMcpPath =
|
|
5142
|
-
if (
|
|
5448
|
+
const homeMcpPath = path11.join(homedir11(), ".mcp.json");
|
|
5449
|
+
if (fs11.existsSync(homeMcpPath)) {
|
|
5143
5450
|
try {
|
|
5144
|
-
const content =
|
|
5451
|
+
const content = fs11.readFileSync(homeMcpPath, "utf-8");
|
|
5145
5452
|
const config = JSON.parse(content);
|
|
5146
5453
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5147
5454
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5293,17 +5600,17 @@ var on_task_exports = {};
|
|
|
5293
5600
|
__export(on_task_exports, {
|
|
5294
5601
|
runOnTaskHook: () => runOnTaskHook
|
|
5295
5602
|
});
|
|
5296
|
-
import * as
|
|
5297
|
-
import * as
|
|
5298
|
-
import { homedir as
|
|
5603
|
+
import * as fs12 from "node:fs";
|
|
5604
|
+
import * as path12 from "node:path";
|
|
5605
|
+
import { homedir as homedir12 } from "node:os";
|
|
5299
5606
|
function loadConfigFromMcpJson5(cwd) {
|
|
5300
|
-
let searchDir =
|
|
5607
|
+
let searchDir = path12.resolve(cwd);
|
|
5301
5608
|
for (let i = 0; i < 5; i++) {
|
|
5302
5609
|
if (!API_KEY7) {
|
|
5303
|
-
const mcpPath =
|
|
5304
|
-
if (
|
|
5610
|
+
const mcpPath = path12.join(searchDir, ".mcp.json");
|
|
5611
|
+
if (fs12.existsSync(mcpPath)) {
|
|
5305
5612
|
try {
|
|
5306
|
-
const content =
|
|
5613
|
+
const content = fs12.readFileSync(mcpPath, "utf-8");
|
|
5307
5614
|
const config = JSON.parse(content);
|
|
5308
5615
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5309
5616
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5317,10 +5624,10 @@ function loadConfigFromMcpJson5(cwd) {
|
|
|
5317
5624
|
}
|
|
5318
5625
|
}
|
|
5319
5626
|
if (!WORKSPACE_ID5) {
|
|
5320
|
-
const csConfigPath =
|
|
5321
|
-
if (
|
|
5627
|
+
const csConfigPath = path12.join(searchDir, ".contextstream", "config.json");
|
|
5628
|
+
if (fs12.existsSync(csConfigPath)) {
|
|
5322
5629
|
try {
|
|
5323
|
-
const content =
|
|
5630
|
+
const content = fs12.readFileSync(csConfigPath, "utf-8");
|
|
5324
5631
|
const csConfig = JSON.parse(content);
|
|
5325
5632
|
if (csConfig.workspace_id) {
|
|
5326
5633
|
WORKSPACE_ID5 = csConfig.workspace_id;
|
|
@@ -5329,15 +5636,15 @@ function loadConfigFromMcpJson5(cwd) {
|
|
|
5329
5636
|
}
|
|
5330
5637
|
}
|
|
5331
5638
|
}
|
|
5332
|
-
const parentDir =
|
|
5639
|
+
const parentDir = path12.dirname(searchDir);
|
|
5333
5640
|
if (parentDir === searchDir) break;
|
|
5334
5641
|
searchDir = parentDir;
|
|
5335
5642
|
}
|
|
5336
5643
|
if (!API_KEY7) {
|
|
5337
|
-
const homeMcpPath =
|
|
5338
|
-
if (
|
|
5644
|
+
const homeMcpPath = path12.join(homedir12(), ".mcp.json");
|
|
5645
|
+
if (fs12.existsSync(homeMcpPath)) {
|
|
5339
5646
|
try {
|
|
5340
|
-
const content =
|
|
5647
|
+
const content = fs12.readFileSync(homeMcpPath, "utf-8");
|
|
5341
5648
|
const config = JSON.parse(content);
|
|
5342
5649
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5343
5650
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5438,17 +5745,17 @@ var on_read_exports = {};
|
|
|
5438
5745
|
__export(on_read_exports, {
|
|
5439
5746
|
runOnReadHook: () => runOnReadHook
|
|
5440
5747
|
});
|
|
5441
|
-
import * as
|
|
5442
|
-
import * as
|
|
5443
|
-
import { homedir as
|
|
5748
|
+
import * as fs13 from "node:fs";
|
|
5749
|
+
import * as path13 from "node:path";
|
|
5750
|
+
import { homedir as homedir13 } from "node:os";
|
|
5444
5751
|
function loadConfigFromMcpJson6(cwd) {
|
|
5445
|
-
let searchDir =
|
|
5752
|
+
let searchDir = path13.resolve(cwd);
|
|
5446
5753
|
for (let i = 0; i < 5; i++) {
|
|
5447
5754
|
if (!API_KEY8) {
|
|
5448
|
-
const mcpPath =
|
|
5449
|
-
if (
|
|
5755
|
+
const mcpPath = path13.join(searchDir, ".mcp.json");
|
|
5756
|
+
if (fs13.existsSync(mcpPath)) {
|
|
5450
5757
|
try {
|
|
5451
|
-
const content =
|
|
5758
|
+
const content = fs13.readFileSync(mcpPath, "utf-8");
|
|
5452
5759
|
const config = JSON.parse(content);
|
|
5453
5760
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5454
5761
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5462,10 +5769,10 @@ function loadConfigFromMcpJson6(cwd) {
|
|
|
5462
5769
|
}
|
|
5463
5770
|
}
|
|
5464
5771
|
if (!WORKSPACE_ID6) {
|
|
5465
|
-
const csConfigPath =
|
|
5466
|
-
if (
|
|
5772
|
+
const csConfigPath = path13.join(searchDir, ".contextstream", "config.json");
|
|
5773
|
+
if (fs13.existsSync(csConfigPath)) {
|
|
5467
5774
|
try {
|
|
5468
|
-
const content =
|
|
5775
|
+
const content = fs13.readFileSync(csConfigPath, "utf-8");
|
|
5469
5776
|
const csConfig = JSON.parse(content);
|
|
5470
5777
|
if (csConfig.workspace_id) {
|
|
5471
5778
|
WORKSPACE_ID6 = csConfig.workspace_id;
|
|
@@ -5474,15 +5781,15 @@ function loadConfigFromMcpJson6(cwd) {
|
|
|
5474
5781
|
}
|
|
5475
5782
|
}
|
|
5476
5783
|
}
|
|
5477
|
-
const parentDir =
|
|
5784
|
+
const parentDir = path13.dirname(searchDir);
|
|
5478
5785
|
if (parentDir === searchDir) break;
|
|
5479
5786
|
searchDir = parentDir;
|
|
5480
5787
|
}
|
|
5481
5788
|
if (!API_KEY8) {
|
|
5482
|
-
const homeMcpPath =
|
|
5483
|
-
if (
|
|
5789
|
+
const homeMcpPath = path13.join(homedir13(), ".mcp.json");
|
|
5790
|
+
if (fs13.existsSync(homeMcpPath)) {
|
|
5484
5791
|
try {
|
|
5485
|
-
const content =
|
|
5792
|
+
const content = fs13.readFileSync(homeMcpPath, "utf-8");
|
|
5486
5793
|
const config = JSON.parse(content);
|
|
5487
5794
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5488
5795
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5607,17 +5914,17 @@ var on_web_exports = {};
|
|
|
5607
5914
|
__export(on_web_exports, {
|
|
5608
5915
|
runOnWebHook: () => runOnWebHook
|
|
5609
5916
|
});
|
|
5610
|
-
import * as
|
|
5611
|
-
import * as
|
|
5612
|
-
import { homedir as
|
|
5917
|
+
import * as fs14 from "node:fs";
|
|
5918
|
+
import * as path14 from "node:path";
|
|
5919
|
+
import { homedir as homedir14 } from "node:os";
|
|
5613
5920
|
function loadConfigFromMcpJson7(cwd) {
|
|
5614
|
-
let searchDir =
|
|
5921
|
+
let searchDir = path14.resolve(cwd);
|
|
5615
5922
|
for (let i = 0; i < 5; i++) {
|
|
5616
5923
|
if (!API_KEY9) {
|
|
5617
|
-
const mcpPath =
|
|
5618
|
-
if (
|
|
5924
|
+
const mcpPath = path14.join(searchDir, ".mcp.json");
|
|
5925
|
+
if (fs14.existsSync(mcpPath)) {
|
|
5619
5926
|
try {
|
|
5620
|
-
const content =
|
|
5927
|
+
const content = fs14.readFileSync(mcpPath, "utf-8");
|
|
5621
5928
|
const config = JSON.parse(content);
|
|
5622
5929
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5623
5930
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5631,10 +5938,10 @@ function loadConfigFromMcpJson7(cwd) {
|
|
|
5631
5938
|
}
|
|
5632
5939
|
}
|
|
5633
5940
|
if (!WORKSPACE_ID7) {
|
|
5634
|
-
const csConfigPath =
|
|
5635
|
-
if (
|
|
5941
|
+
const csConfigPath = path14.join(searchDir, ".contextstream", "config.json");
|
|
5942
|
+
if (fs14.existsSync(csConfigPath)) {
|
|
5636
5943
|
try {
|
|
5637
|
-
const content =
|
|
5944
|
+
const content = fs14.readFileSync(csConfigPath, "utf-8");
|
|
5638
5945
|
const csConfig = JSON.parse(content);
|
|
5639
5946
|
if (csConfig.workspace_id) {
|
|
5640
5947
|
WORKSPACE_ID7 = csConfig.workspace_id;
|
|
@@ -5643,15 +5950,15 @@ function loadConfigFromMcpJson7(cwd) {
|
|
|
5643
5950
|
}
|
|
5644
5951
|
}
|
|
5645
5952
|
}
|
|
5646
|
-
const parentDir =
|
|
5953
|
+
const parentDir = path14.dirname(searchDir);
|
|
5647
5954
|
if (parentDir === searchDir) break;
|
|
5648
5955
|
searchDir = parentDir;
|
|
5649
5956
|
}
|
|
5650
5957
|
if (!API_KEY9) {
|
|
5651
|
-
const homeMcpPath =
|
|
5652
|
-
if (
|
|
5958
|
+
const homeMcpPath = path14.join(homedir14(), ".mcp.json");
|
|
5959
|
+
if (fs14.existsSync(homeMcpPath)) {
|
|
5653
5960
|
try {
|
|
5654
|
-
const content =
|
|
5961
|
+
const content = fs14.readFileSync(homeMcpPath, "utf-8");
|
|
5655
5962
|
const config = JSON.parse(content);
|
|
5656
5963
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
5657
5964
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -5844,7 +6151,7 @@ var init_rules_templates = __esm({
|
|
|
5844
6151
|
| Message | Required |
|
|
5845
6152
|
|---------|----------|
|
|
5846
6153
|
| **1st message** | \`init()\` \u2192 \`context(user_message="...")\` |
|
|
5847
|
-
| **
|
|
6154
|
+
| **Subsequent messages (default)** | \`context(user_message="...")\` FIRST (narrow read-only bypass when context is fresh and no state-changing tool has run) |
|
|
5848
6155
|
| **Before file search** | \`search(mode="auto")\` BEFORE Glob/Grep/Read |
|
|
5849
6156
|
</contextstream_rules>
|
|
5850
6157
|
|
|
@@ -5858,27 +6165,25 @@ v${RULES_VERSION}
|
|
|
5858
6165
|
`.trim();
|
|
5859
6166
|
CONTEXTSTREAM_RULES_DYNAMIC = CONTEXTSTREAM_RULES_BOOTSTRAP;
|
|
5860
6167
|
CONTEXTSTREAM_RULES_FULL = `
|
|
5861
|
-
## \u{1F6A8} MANDATORY:
|
|
6168
|
+
## \u{1F6A8} MANDATORY STARTUP: CONTEXT-FIRST FLOW \u{1F6A8}
|
|
5862
6169
|
|
|
5863
6170
|
<contextstream_rules>
|
|
5864
|
-
**EVERY response MUST start with \`context(user_message="...")\`**
|
|
5865
|
-
|
|
5866
6171
|
| Message | What to Call |
|
|
5867
6172
|
|---------|--------------|
|
|
5868
|
-
|
|
|
5869
|
-
| **
|
|
5870
|
-
|
|
5871
|
-
**
|
|
6173
|
+
| **First message in session** | \`init()\` \u2192 \`context(user_message="<msg>")\` BEFORE any other tool |
|
|
6174
|
+
| **Subsequent messages (default)** | \`context(user_message="<msg>")\` FIRST, then other tools |
|
|
6175
|
+
| **Narrow bypass** | Immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run |
|
|
6176
|
+
| **Before Glob/Grep/Read/Search** | \`search(mode="auto", query="...")\` FIRST |
|
|
5872
6177
|
</contextstream_rules>
|
|
5873
6178
|
|
|
5874
|
-
|
|
6179
|
+
Use \`context()\` by default to get task-specific rules, lessons from past mistakes, and relevant decisions.
|
|
5875
6180
|
|
|
5876
6181
|
---
|
|
5877
6182
|
|
|
5878
|
-
## Why
|
|
6183
|
+
## Why Default Context-First
|
|
5879
6184
|
|
|
5880
|
-
\u274C **
|
|
5881
|
-
\u2705 **
|
|
6185
|
+
\u274C **Wrong:** "I already called init, so I can skip context for everything"
|
|
6186
|
+
\u2705 **Correct:** \`context()\` is the default first call for subsequent messages, with a narrow read-only bypass when context is still fresh and state is unchanged
|
|
5882
6187
|
|
|
5883
6188
|
**What you lose without \`context()\`:**
|
|
5884
6189
|
- Dynamic rules matched to your current task
|
|
@@ -5886,7 +6191,7 @@ v${RULES_VERSION}
|
|
|
5886
6191
|
- Semantically relevant decisions and context
|
|
5887
6192
|
- Warnings about risky operations
|
|
5888
6193
|
|
|
5889
|
-
**\`init()\`
|
|
6194
|
+
**\`init()\` returns recent items by time. \`context()\` finds items semantically relevant to this message.**
|
|
5890
6195
|
|
|
5891
6196
|
---
|
|
5892
6197
|
|
|
@@ -6392,9 +6697,10 @@ See full documentation: https://contextstream.io/docs/mcp/tools
|
|
|
6392
6697
|
## \u{1F6A8} CRITICAL: These Rules Are Non-Negotiable \u{1F6A8}
|
|
6393
6698
|
|
|
6394
6699
|
<contextstream_protocol>
|
|
6395
|
-
**
|
|
6396
|
-
1. \`init()\`
|
|
6397
|
-
2. \`context(user_message="<msg>")\`
|
|
6700
|
+
**MANDATORY STARTUP:**
|
|
6701
|
+
1. First message in session: \`init()\` \u2192 then \`context(user_message="<msg>")\`
|
|
6702
|
+
2. Subsequent messages (default): \`context(user_message="<msg>")\` FIRST
|
|
6703
|
+
3. Narrow bypass: immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run
|
|
6398
6704
|
|
|
6399
6705
|
**BEFORE Glob/Grep/Read/Search/Explore:**
|
|
6400
6706
|
\u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
|
|
@@ -6424,8 +6730,9 @@ Rules Version: ${RULES_VERSION}
|
|
|
6424
6730
|
|
|
6425
6731
|
| When | Call |
|
|
6426
6732
|
|------|------|
|
|
6427
|
-
| 1st message | \`init()\` \u2192 \`context(user_message="...")\` |
|
|
6428
|
-
|
|
|
6733
|
+
| 1st message in session | \`init()\` \u2192 \`context(user_message="...")\` |
|
|
6734
|
+
| Subsequent messages (default) | \`context(user_message="...")\` first |
|
|
6735
|
+
| Narrow bypass | Immediate read-only ContextStream calls when context is fresh and no state-changing tool has run |
|
|
6429
6736
|
| Before ANY file discovery | \`search(mode="auto", query="...")\` |
|
|
6430
6737
|
| On \`<system-reminder>\` | **Follow instructions inside** |
|
|
6431
6738
|
| Save important decisions | \`session(action="capture", event_type="decision", ...)\` |
|
|
@@ -6448,12 +6755,10 @@ Rules Version: ${RULES_VERSION}
|
|
|
6448
6755
|
- **[RULES_NOTICE]** \u2192 Run \`generate_rules()\`
|
|
6449
6756
|
- **[VERSION_NOTICE]** \u2192 Tell user to update MCP
|
|
6450
6757
|
|
|
6451
|
-
##
|
|
6452
|
-
|
|
6453
|
-
Skip init/context ONLY for: "list workspaces", "show version", "list reminders"
|
|
6454
|
-
\u2192 Just call: \`workspace(action="list")\`, \`help(action="version")\`, etc.
|
|
6758
|
+
## Read-Only Examples
|
|
6455
6759
|
|
|
6456
|
-
|
|
6760
|
+
Default behavior is context-first. Narrow bypass applies only for immediate read-only ContextStream calls when context is fresh and state is unchanged.
|
|
6761
|
+
Examples: \`workspace(action="list"|"get")\`, \`help(action="version"|"tools"|"auth")\`, \`project(action="index_status")\`.
|
|
6457
6762
|
|
|
6458
6763
|
### Lessons (Past Mistakes)
|
|
6459
6764
|
|
|
@@ -6542,7 +6847,7 @@ You MUST follow these rules manually - there is no automatic enforcement.
|
|
|
6542
6847
|
- Check for [RULES_NOTICE] - update rules if needed
|
|
6543
6848
|
- **save_exchange=true** saves each conversation turn for later retrieval
|
|
6544
6849
|
|
|
6545
|
-
4. **
|
|
6850
|
+
4. **Default behavior:** call \`context(...)\` first on each message. Narrow bypass is allowed only for immediate read-only ContextStream calls when previous context is still fresh and no state-changing tool has run.
|
|
6546
6851
|
|
|
6547
6852
|
---
|
|
6548
6853
|
|
|
@@ -6551,7 +6856,7 @@ You MUST follow these rules manually - there is no automatic enforcement.
|
|
|
6551
6856
|
**This editor does NOT have hooks to auto-save transcripts.**
|
|
6552
6857
|
You MUST save each conversation turn manually:
|
|
6553
6858
|
|
|
6554
|
-
### On
|
|
6859
|
+
### On MOST messages (including the first):
|
|
6555
6860
|
\`\`\`
|
|
6556
6861
|
context(user_message="<user's message>", save_exchange=true, session_id="<session-id>")
|
|
6557
6862
|
\`\`\`
|
|
@@ -6626,6 +6931,28 @@ search(mode="auto", query="what you're looking for")
|
|
|
6626
6931
|
**IF ContextStream search returns 0 results or errors:**
|
|
6627
6932
|
\u2192 Use local tools (Glob/Grep/Read) as fallback
|
|
6628
6933
|
|
|
6934
|
+
### Choose Search Mode Intelligently:
|
|
6935
|
+
- \`auto\` (recommended): query-aware mode selection
|
|
6936
|
+
- \`hybrid\`: mixed semantic + keyword retrieval for broad discovery
|
|
6937
|
+
- \`semantic\`: conceptual questions ("how does X work?")
|
|
6938
|
+
- \`keyword\`: exact text / quoted string
|
|
6939
|
+
- \`pattern\`: glob or regex (\`*.ts\`, \`foo\\s+bar\`)
|
|
6940
|
+
- \`refactor\`: symbol usage / rename-safe lookup
|
|
6941
|
+
- \`exhaustive\`: all occurrences / complete match coverage
|
|
6942
|
+
- \`team\`: cross-project team search
|
|
6943
|
+
|
|
6944
|
+
### Output Format Hints:
|
|
6945
|
+
- Use \`output_format="paths"\` for file listings and rename targets
|
|
6946
|
+
- Use \`output_format="count"\` for "how many" queries
|
|
6947
|
+
|
|
6948
|
+
### Two-Phase Search Pattern (for precision):
|
|
6949
|
+
- Pass 1 (discovery): \`search(mode="auto", query="<concept + module>", output_format="paths", limit=10)\`
|
|
6950
|
+
- Pass 2 (precision): use one of:
|
|
6951
|
+
- exact text/symbol: \`search(mode="keyword", query="\\"exact_text\\"", include_content=true)\`
|
|
6952
|
+
- symbol usage: \`search(mode="refactor", query="SymbolName", output_format="paths")\`
|
|
6953
|
+
- all occurrences: \`search(mode="exhaustive", query="symbol_or_text")\`
|
|
6954
|
+
- Then use local Read/Grep only on paths returned by ContextStream.
|
|
6955
|
+
|
|
6629
6956
|
### When Local Tools Are OK:
|
|
6630
6957
|
\u2705 Project is not indexed
|
|
6631
6958
|
\u2705 Index is stale/outdated (>7 days old)
|
|
@@ -6782,17 +7109,17 @@ var session_init_exports = {};
|
|
|
6782
7109
|
__export(session_init_exports, {
|
|
6783
7110
|
runSessionInitHook: () => runSessionInitHook
|
|
6784
7111
|
});
|
|
6785
|
-
import * as
|
|
6786
|
-
import * as
|
|
6787
|
-
import { homedir as
|
|
7112
|
+
import * as fs15 from "node:fs";
|
|
7113
|
+
import * as path15 from "node:path";
|
|
7114
|
+
import { homedir as homedir15 } from "node:os";
|
|
6788
7115
|
function loadConfigFromMcpJson8(cwd) {
|
|
6789
|
-
let searchDir =
|
|
7116
|
+
let searchDir = path15.resolve(cwd);
|
|
6790
7117
|
for (let i = 0; i < 5; i++) {
|
|
6791
7118
|
if (!API_KEY10) {
|
|
6792
|
-
const mcpPath =
|
|
6793
|
-
if (
|
|
7119
|
+
const mcpPath = path15.join(searchDir, ".mcp.json");
|
|
7120
|
+
if (fs15.existsSync(mcpPath)) {
|
|
6794
7121
|
try {
|
|
6795
|
-
const content =
|
|
7122
|
+
const content = fs15.readFileSync(mcpPath, "utf-8");
|
|
6796
7123
|
const config = JSON.parse(content);
|
|
6797
7124
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
6798
7125
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -6809,10 +7136,10 @@ function loadConfigFromMcpJson8(cwd) {
|
|
|
6809
7136
|
}
|
|
6810
7137
|
}
|
|
6811
7138
|
if (!WORKSPACE_ID8 || !PROJECT_ID2) {
|
|
6812
|
-
const csConfigPath =
|
|
6813
|
-
if (
|
|
7139
|
+
const csConfigPath = path15.join(searchDir, ".contextstream", "config.json");
|
|
7140
|
+
if (fs15.existsSync(csConfigPath)) {
|
|
6814
7141
|
try {
|
|
6815
|
-
const content =
|
|
7142
|
+
const content = fs15.readFileSync(csConfigPath, "utf-8");
|
|
6816
7143
|
const csConfig = JSON.parse(content);
|
|
6817
7144
|
if (csConfig.workspace_id && !WORKSPACE_ID8) {
|
|
6818
7145
|
WORKSPACE_ID8 = csConfig.workspace_id;
|
|
@@ -6824,15 +7151,15 @@ function loadConfigFromMcpJson8(cwd) {
|
|
|
6824
7151
|
}
|
|
6825
7152
|
}
|
|
6826
7153
|
}
|
|
6827
|
-
const parentDir =
|
|
7154
|
+
const parentDir = path15.dirname(searchDir);
|
|
6828
7155
|
if (parentDir === searchDir) break;
|
|
6829
7156
|
searchDir = parentDir;
|
|
6830
7157
|
}
|
|
6831
7158
|
if (!API_KEY10) {
|
|
6832
|
-
const homeMcpPath =
|
|
6833
|
-
if (
|
|
7159
|
+
const homeMcpPath = path15.join(homedir15(), ".mcp.json");
|
|
7160
|
+
if (fs15.existsSync(homeMcpPath)) {
|
|
6834
7161
|
try {
|
|
6835
|
-
const content =
|
|
7162
|
+
const content = fs15.readFileSync(homeMcpPath, "utf-8");
|
|
6836
7163
|
const config = JSON.parse(content);
|
|
6837
7164
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
6838
7165
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -6910,7 +7237,9 @@ function formatContext(ctx, options = {}) {
|
|
|
6910
7237
|
}
|
|
6911
7238
|
}
|
|
6912
7239
|
if (!ctx) {
|
|
6913
|
-
parts.push(
|
|
7240
|
+
parts.push(
|
|
7241
|
+
'\nNo saved context found yet. On the first message in this session call `mcp__contextstream__init(...)` then `mcp__contextstream__context(user_message="starting new session")`.'
|
|
7242
|
+
);
|
|
6914
7243
|
return parts.join("\n");
|
|
6915
7244
|
}
|
|
6916
7245
|
if (ctx.lessons && ctx.lessons.length > 0) {
|
|
@@ -6938,7 +7267,9 @@ function formatContext(ctx, options = {}) {
|
|
|
6938
7267
|
}
|
|
6939
7268
|
}
|
|
6940
7269
|
parts.push("\n---");
|
|
6941
|
-
parts.push(
|
|
7270
|
+
parts.push(
|
|
7271
|
+
'On the first message in a new session call `mcp__contextstream__init(...)` then `mcp__contextstream__context(user_message="...")`. After that, call `mcp__contextstream__context(user_message="...")` on every message.'
|
|
7272
|
+
);
|
|
6942
7273
|
return parts.join("\n");
|
|
6943
7274
|
}
|
|
6944
7275
|
function regenerateRuleFiles(folderPath) {
|
|
@@ -6947,10 +7278,10 @@ function regenerateRuleFiles(folderPath) {
|
|
|
6947
7278
|
for (const editor of editors) {
|
|
6948
7279
|
const rule = generateRuleContent(editor, { mode: "bootstrap" });
|
|
6949
7280
|
if (!rule) continue;
|
|
6950
|
-
const filePath =
|
|
6951
|
-
if (!
|
|
7281
|
+
const filePath = path15.join(folderPath, rule.filename);
|
|
7282
|
+
if (!fs15.existsSync(filePath)) continue;
|
|
6952
7283
|
try {
|
|
6953
|
-
const existing =
|
|
7284
|
+
const existing = fs15.readFileSync(filePath, "utf8");
|
|
6954
7285
|
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
|
|
6955
7286
|
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
|
|
6956
7287
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) continue;
|
|
@@ -6960,7 +7291,7 @@ function regenerateRuleFiles(folderPath) {
|
|
|
6960
7291
|
${rule.content.trim()}
|
|
6961
7292
|
${CONTEXTSTREAM_END_MARKER}`;
|
|
6962
7293
|
const merged = [before, newBlock, after].filter((p) => p.length > 0).join("\n\n");
|
|
6963
|
-
|
|
7294
|
+
fs15.writeFileSync(filePath, merged.trim() + "\n", "utf8");
|
|
6964
7295
|
updated++;
|
|
6965
7296
|
} catch {
|
|
6966
7297
|
}
|
|
@@ -6985,6 +7316,8 @@ async function runSessionInitHook() {
|
|
|
6985
7316
|
process.exit(0);
|
|
6986
7317
|
}
|
|
6987
7318
|
const cwd = input.cwd || process.cwd();
|
|
7319
|
+
cleanupStale(360);
|
|
7320
|
+
markInitRequired(cwd);
|
|
6988
7321
|
loadConfigFromMcpJson8(cwd);
|
|
6989
7322
|
const updateMarker = checkUpdateMarker();
|
|
6990
7323
|
if (updateMarker) {
|
|
@@ -7018,6 +7351,7 @@ var init_session_init = __esm({
|
|
|
7018
7351
|
"use strict";
|
|
7019
7352
|
init_version();
|
|
7020
7353
|
init_rules_templates();
|
|
7354
|
+
init_prompt_state();
|
|
7021
7355
|
ENABLED12 = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
|
|
7022
7356
|
API_URL10 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
7023
7357
|
API_KEY10 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
@@ -7037,17 +7371,17 @@ var session_end_exports = {};
|
|
|
7037
7371
|
__export(session_end_exports, {
|
|
7038
7372
|
runSessionEndHook: () => runSessionEndHook
|
|
7039
7373
|
});
|
|
7040
|
-
import * as
|
|
7041
|
-
import * as
|
|
7042
|
-
import { homedir as
|
|
7374
|
+
import * as fs16 from "node:fs";
|
|
7375
|
+
import * as path16 from "node:path";
|
|
7376
|
+
import { homedir as homedir16 } from "node:os";
|
|
7043
7377
|
function loadConfigFromMcpJson9(cwd) {
|
|
7044
|
-
let searchDir =
|
|
7378
|
+
let searchDir = path16.resolve(cwd);
|
|
7045
7379
|
for (let i = 0; i < 5; i++) {
|
|
7046
7380
|
if (!API_KEY11) {
|
|
7047
|
-
const mcpPath =
|
|
7048
|
-
if (
|
|
7381
|
+
const mcpPath = path16.join(searchDir, ".mcp.json");
|
|
7382
|
+
if (fs16.existsSync(mcpPath)) {
|
|
7049
7383
|
try {
|
|
7050
|
-
const content =
|
|
7384
|
+
const content = fs16.readFileSync(mcpPath, "utf-8");
|
|
7051
7385
|
const config = JSON.parse(content);
|
|
7052
7386
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
7053
7387
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -7061,10 +7395,10 @@ function loadConfigFromMcpJson9(cwd) {
|
|
|
7061
7395
|
}
|
|
7062
7396
|
}
|
|
7063
7397
|
if (!WORKSPACE_ID9 || !PROJECT_ID3) {
|
|
7064
|
-
const csConfigPath =
|
|
7065
|
-
if (
|
|
7398
|
+
const csConfigPath = path16.join(searchDir, ".contextstream", "config.json");
|
|
7399
|
+
if (fs16.existsSync(csConfigPath)) {
|
|
7066
7400
|
try {
|
|
7067
|
-
const content =
|
|
7401
|
+
const content = fs16.readFileSync(csConfigPath, "utf-8");
|
|
7068
7402
|
const csConfig = JSON.parse(content);
|
|
7069
7403
|
if (csConfig.workspace_id && !WORKSPACE_ID9) {
|
|
7070
7404
|
WORKSPACE_ID9 = csConfig.workspace_id;
|
|
@@ -7076,15 +7410,15 @@ function loadConfigFromMcpJson9(cwd) {
|
|
|
7076
7410
|
}
|
|
7077
7411
|
}
|
|
7078
7412
|
}
|
|
7079
|
-
const parentDir =
|
|
7413
|
+
const parentDir = path16.dirname(searchDir);
|
|
7080
7414
|
if (parentDir === searchDir) break;
|
|
7081
7415
|
searchDir = parentDir;
|
|
7082
7416
|
}
|
|
7083
7417
|
if (!API_KEY11) {
|
|
7084
|
-
const homeMcpPath =
|
|
7085
|
-
if (
|
|
7418
|
+
const homeMcpPath = path16.join(homedir16(), ".mcp.json");
|
|
7419
|
+
if (fs16.existsSync(homeMcpPath)) {
|
|
7086
7420
|
try {
|
|
7087
|
-
const content =
|
|
7421
|
+
const content = fs16.readFileSync(homeMcpPath, "utf-8");
|
|
7088
7422
|
const config = JSON.parse(content);
|
|
7089
7423
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
7090
7424
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -7107,11 +7441,11 @@ function parseTranscriptStats(transcriptPath) {
|
|
|
7107
7441
|
messages: [],
|
|
7108
7442
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7109
7443
|
};
|
|
7110
|
-
if (!transcriptPath || !
|
|
7444
|
+
if (!transcriptPath || !fs16.existsSync(transcriptPath)) {
|
|
7111
7445
|
return stats;
|
|
7112
7446
|
}
|
|
7113
7447
|
try {
|
|
7114
|
-
const content =
|
|
7448
|
+
const content = fs16.readFileSync(transcriptPath, "utf-8");
|
|
7115
7449
|
const lines = content.split("\n");
|
|
7116
7450
|
let firstTimestamp = null;
|
|
7117
7451
|
let lastTimestamp = null;
|