@contextstream/mcp-server 0.4.51 → 0.4.53
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 +5 -1
- package/dist/hooks/pre-compact.js +3 -1
- package/dist/hooks/pre-tool-use.js +29 -4
- package/dist/hooks/runner.js +439 -167
- package/dist/hooks/user-prompt-submit.js +250 -9
- package/dist/index.js +824 -412
- package/package.json +1 -1
package/dist/hooks/runner.js
CHANGED
|
@@ -97,8 +97,17 @@ function extractToolInput(input) {
|
|
|
97
97
|
return input.tool_input || input.parameters || input.toolParameters || {};
|
|
98
98
|
}
|
|
99
99
|
function blockClaudeCode(message) {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const response = {
|
|
101
|
+
hookSpecificOutput: {
|
|
102
|
+
hookEventName: "PreToolUse",
|
|
103
|
+
// Use additionalContext instead of deny - tool runs but Claude sees the message
|
|
104
|
+
additionalContext: `[CONTEXTSTREAM] ${message}`
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] REDIRECT (additionalContext): ${JSON.stringify(response)}
|
|
108
|
+
`);
|
|
109
|
+
console.log(JSON.stringify(response));
|
|
110
|
+
process.exit(0);
|
|
102
111
|
}
|
|
103
112
|
function outputClineBlock(errorMessage, contextMod) {
|
|
104
113
|
const result = {
|
|
@@ -127,13 +136,18 @@ function detectEditorFormat(input) {
|
|
|
127
136
|
if (input.hookName !== void 0 || input.toolName !== void 0) {
|
|
128
137
|
return "cline";
|
|
129
138
|
}
|
|
130
|
-
if (input.hook_event_name !== void 0) {
|
|
131
|
-
return "
|
|
139
|
+
if (input.hook_event_name !== void 0 || input.tool_name !== void 0) {
|
|
140
|
+
return "claude";
|
|
132
141
|
}
|
|
133
142
|
return "claude";
|
|
134
143
|
}
|
|
135
144
|
async function runPreToolUseHook() {
|
|
145
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] Hook invoked at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
146
|
+
`);
|
|
147
|
+
console.error("[PreToolUse] Hook invoked at", (/* @__PURE__ */ new Date()).toISOString());
|
|
136
148
|
if (!ENABLED) {
|
|
149
|
+
fs.appendFileSync(DEBUG_FILE, "[PreToolUse] Hook disabled, exiting\n");
|
|
150
|
+
console.error("[PreToolUse] Hook disabled, exiting");
|
|
137
151
|
process.exit(0);
|
|
138
152
|
}
|
|
139
153
|
let inputData = "";
|
|
@@ -153,8 +167,14 @@ async function runPreToolUseHook() {
|
|
|
153
167
|
const cwd = extractCwd(input);
|
|
154
168
|
const tool = extractToolName(input);
|
|
155
169
|
const toolInput = extractToolInput(input);
|
|
170
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] tool=${tool}, cwd=${cwd}, editorFormat=${editorFormat}
|
|
171
|
+
`);
|
|
156
172
|
const { isIndexed } = isProjectIndexed(cwd);
|
|
173
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
|
|
174
|
+
`);
|
|
157
175
|
if (!isIndexed) {
|
|
176
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
|
|
177
|
+
`);
|
|
158
178
|
if (editorFormat === "cline") {
|
|
159
179
|
outputClineAllow();
|
|
160
180
|
} else if (editorFormat === "cursor") {
|
|
@@ -164,8 +184,12 @@ async function runPreToolUseHook() {
|
|
|
164
184
|
}
|
|
165
185
|
if (tool === "Glob") {
|
|
166
186
|
const pattern = toolInput?.pattern || "";
|
|
187
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
|
|
188
|
+
`);
|
|
167
189
|
if (isDiscoveryGlob(pattern)) {
|
|
168
190
|
const msg = `STOP: Use mcp__contextstream__search(mode="hybrid", query="${pattern}") instead of Glob.`;
|
|
191
|
+
fs.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
|
|
192
|
+
`);
|
|
169
193
|
if (editorFormat === "cline") {
|
|
170
194
|
outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
|
|
171
195
|
} else if (editorFormat === "cursor") {
|
|
@@ -242,12 +266,13 @@ async function runPreToolUseHook() {
|
|
|
242
266
|
}
|
|
243
267
|
process.exit(0);
|
|
244
268
|
}
|
|
245
|
-
var ENABLED, INDEX_STATUS_FILE, STALE_THRESHOLD_DAYS, DISCOVERY_PATTERNS, isDirectRun;
|
|
269
|
+
var ENABLED, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, DISCOVERY_PATTERNS, isDirectRun;
|
|
246
270
|
var init_pre_tool_use = __esm({
|
|
247
271
|
"src/hooks/pre-tool-use.ts"() {
|
|
248
272
|
"use strict";
|
|
249
273
|
ENABLED = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
|
|
250
274
|
INDEX_STATUS_FILE = path.join(homedir(), ".contextstream", "indexed-projects.json");
|
|
275
|
+
DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
|
|
251
276
|
STALE_THRESHOLD_DAYS = 7;
|
|
252
277
|
DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
|
|
253
278
|
isDirectRun = process.argv[1]?.includes("pre-tool-use") || process.argv[2] === "pre-tool-use";
|
|
@@ -262,6 +287,184 @@ var user_prompt_submit_exports = {};
|
|
|
262
287
|
__export(user_prompt_submit_exports, {
|
|
263
288
|
runUserPromptSubmitHook: () => runUserPromptSubmitHook
|
|
264
289
|
});
|
|
290
|
+
import * as fs2 from "node:fs";
|
|
291
|
+
import * as path2 from "node:path";
|
|
292
|
+
import { homedir as homedir2 } from "node:os";
|
|
293
|
+
function loadConfigFromMcpJson(cwd) {
|
|
294
|
+
let searchDir = path2.resolve(cwd);
|
|
295
|
+
for (let i = 0; i < 5; i++) {
|
|
296
|
+
if (!API_KEY) {
|
|
297
|
+
const mcpPath = path2.join(searchDir, ".mcp.json");
|
|
298
|
+
if (fs2.existsSync(mcpPath)) {
|
|
299
|
+
try {
|
|
300
|
+
const content = fs2.readFileSync(mcpPath, "utf-8");
|
|
301
|
+
const config = JSON.parse(content);
|
|
302
|
+
const csEnv = config.mcpServers?.contextstream?.env;
|
|
303
|
+
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
304
|
+
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
305
|
+
}
|
|
306
|
+
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
307
|
+
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
308
|
+
}
|
|
309
|
+
if (csEnv?.CONTEXTSTREAM_WORKSPACE_ID) {
|
|
310
|
+
WORKSPACE_ID = csEnv.CONTEXTSTREAM_WORKSPACE_ID;
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!WORKSPACE_ID || !PROJECT_ID) {
|
|
317
|
+
const csConfigPath = path2.join(searchDir, ".contextstream", "config.json");
|
|
318
|
+
if (fs2.existsSync(csConfigPath)) {
|
|
319
|
+
try {
|
|
320
|
+
const content = fs2.readFileSync(csConfigPath, "utf-8");
|
|
321
|
+
const csConfig = JSON.parse(content);
|
|
322
|
+
if (csConfig.workspace_id && !WORKSPACE_ID) {
|
|
323
|
+
WORKSPACE_ID = csConfig.workspace_id;
|
|
324
|
+
}
|
|
325
|
+
if (csConfig.project_id && !PROJECT_ID) {
|
|
326
|
+
PROJECT_ID = csConfig.project_id;
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const parentDir = path2.dirname(searchDir);
|
|
333
|
+
if (parentDir === searchDir) break;
|
|
334
|
+
searchDir = parentDir;
|
|
335
|
+
}
|
|
336
|
+
if (!API_KEY) {
|
|
337
|
+
const homeMcpPath = path2.join(homedir2(), ".mcp.json");
|
|
338
|
+
if (fs2.existsSync(homeMcpPath)) {
|
|
339
|
+
try {
|
|
340
|
+
const content = fs2.readFileSync(homeMcpPath, "utf-8");
|
|
341
|
+
const config = JSON.parse(content);
|
|
342
|
+
const csEnv = config.mcpServers?.contextstream?.env;
|
|
343
|
+
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
344
|
+
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
345
|
+
}
|
|
346
|
+
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
347
|
+
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function fetchSessionContext() {
|
|
355
|
+
if (!API_KEY) return null;
|
|
356
|
+
try {
|
|
357
|
+
const controller = new AbortController();
|
|
358
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
359
|
+
const url = new URL(`${API_URL}/api/v1/context`);
|
|
360
|
+
if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
|
|
361
|
+
if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
|
|
362
|
+
url.searchParams.set("include_lessons", "true");
|
|
363
|
+
url.searchParams.set("include_decisions", "true");
|
|
364
|
+
url.searchParams.set("include_plans", "true");
|
|
365
|
+
url.searchParams.set("include_reminders", "true");
|
|
366
|
+
url.searchParams.set("limit", "3");
|
|
367
|
+
const response = await fetch(url.toString(), {
|
|
368
|
+
method: "GET",
|
|
369
|
+
headers: {
|
|
370
|
+
"X-API-Key": API_KEY
|
|
371
|
+
},
|
|
372
|
+
signal: controller.signal
|
|
373
|
+
});
|
|
374
|
+
clearTimeout(timeoutId);
|
|
375
|
+
if (response.ok) {
|
|
376
|
+
return await response.json();
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
} catch {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function buildEnhancedReminder(ctx, isNewSession2) {
|
|
384
|
+
const parts = [ENHANCED_REMINDER_HEADER];
|
|
385
|
+
if (isNewSession2) {
|
|
386
|
+
parts.push(`## \u{1F680} NEW SESSION DETECTED
|
|
387
|
+
1. Call \`init(folder_path="...")\` - this triggers project indexing
|
|
388
|
+
2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
|
|
389
|
+
3. Then call \`context(user_message="...")\` for task-specific context
|
|
390
|
+
4. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
|
|
391
|
+
|
|
392
|
+
`);
|
|
393
|
+
}
|
|
394
|
+
if (ctx?.lessons && ctx.lessons.length > 0) {
|
|
395
|
+
parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
|
|
396
|
+
for (const lesson of ctx.lessons.slice(0, 3)) {
|
|
397
|
+
parts.push(`- **${lesson.title}**: ${lesson.prevention}`);
|
|
398
|
+
}
|
|
399
|
+
parts.push("");
|
|
400
|
+
}
|
|
401
|
+
if (ctx?.active_plans && ctx.active_plans.length > 0) {
|
|
402
|
+
parts.push(`## \u{1F4CB} Active Plans`);
|
|
403
|
+
for (const plan of ctx.active_plans.slice(0, 3)) {
|
|
404
|
+
parts.push(`- ${plan.title} (${plan.status})`);
|
|
405
|
+
}
|
|
406
|
+
parts.push("");
|
|
407
|
+
}
|
|
408
|
+
if (ctx?.pending_tasks && ctx.pending_tasks.length > 0) {
|
|
409
|
+
parts.push(`## \u2705 Pending Tasks`);
|
|
410
|
+
for (const task of ctx.pending_tasks.slice(0, 5)) {
|
|
411
|
+
parts.push(`- ${task.title}`);
|
|
412
|
+
}
|
|
413
|
+
parts.push("");
|
|
414
|
+
}
|
|
415
|
+
if (ctx?.reminders && ctx.reminders.length > 0) {
|
|
416
|
+
parts.push(`## \u{1F514} Reminders`);
|
|
417
|
+
for (const reminder of ctx.reminders.slice(0, 3)) {
|
|
418
|
+
parts.push(`- ${reminder.title}`);
|
|
419
|
+
}
|
|
420
|
+
parts.push("");
|
|
421
|
+
}
|
|
422
|
+
parts.push("---\n");
|
|
423
|
+
parts.push(REMINDER);
|
|
424
|
+
parts.push(`
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
## \u{1F6A8} FILE INDEXING & SEARCH - CRITICAL (No PostToolUse Hook) \u{1F6A8}
|
|
428
|
+
|
|
429
|
+
**This editor does NOT have automatic file indexing after Edit/Write.**
|
|
430
|
+
|
|
431
|
+
### \u26A0\uFE0F BEFORE ANY SEARCH - Check Index Status:
|
|
432
|
+
\`\`\`
|
|
433
|
+
project(action="index_status")
|
|
434
|
+
\`\`\`
|
|
435
|
+
Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
|
|
436
|
+
|
|
437
|
+
### \u{1F50D} Search Decision Tree:
|
|
438
|
+
|
|
439
|
+
**IF indexed=true AND last_indexed_at is recent:**
|
|
440
|
+
\u2192 Use \`search(mode="hybrid", query="...")\`
|
|
441
|
+
|
|
442
|
+
**IF indexed=false OR last_indexed_at is stale (>7 days):**
|
|
443
|
+
\u2192 Use local tools (Glob/Grep/Read) directly
|
|
444
|
+
\u2192 OR run \`project(action="index")\` first, then search
|
|
445
|
+
|
|
446
|
+
**IF search returns 0 results or errors:**
|
|
447
|
+
\u2192 Fallback to local tools (Glob/Grep/Read)
|
|
448
|
+
|
|
449
|
+
### \u2705 When Local Tools (Glob/Grep/Read) Are OK:
|
|
450
|
+
- Project is NOT indexed
|
|
451
|
+
- Index is stale/outdated (>7 days)
|
|
452
|
+
- ContextStream search returns 0 results
|
|
453
|
+
- ContextStream returns errors
|
|
454
|
+
- User explicitly requests local tools
|
|
455
|
+
|
|
456
|
+
### On Session Start:
|
|
457
|
+
1. Call \`init(folder_path="...")\` - triggers initial indexing
|
|
458
|
+
2. Check \`project(action="index_status")\` before searching
|
|
459
|
+
3. If not indexed: use local tools OR wait for indexing
|
|
460
|
+
|
|
461
|
+
### After File Changes (Edit/Write/Create):
|
|
462
|
+
Files are NOT auto-indexed. You MUST:
|
|
463
|
+
1. After significant edits: \`project(action="index")\`
|
|
464
|
+
2. For single file: \`project(action="ingest_local", path="<file>")\`
|
|
465
|
+
3. Then search will find your changes`);
|
|
466
|
+
return parts.join("\n");
|
|
467
|
+
}
|
|
265
468
|
function detectEditorFormat2(input) {
|
|
266
469
|
if (input.hookName !== void 0) {
|
|
267
470
|
return "cline";
|
|
@@ -269,8 +472,23 @@ function detectEditorFormat2(input) {
|
|
|
269
472
|
if (input.hook_event_name === "beforeSubmitPrompt") {
|
|
270
473
|
return "cursor";
|
|
271
474
|
}
|
|
475
|
+
if (input.hook_event_name === "beforeAgentAction" || input.hook_event_name === "onPromptSubmit") {
|
|
476
|
+
return "antigravity";
|
|
477
|
+
}
|
|
272
478
|
return "claude";
|
|
273
479
|
}
|
|
480
|
+
function isNewSession(input, editorFormat) {
|
|
481
|
+
if (editorFormat === "claude" && input.session?.messages) {
|
|
482
|
+
return input.session.messages.length <= 1;
|
|
483
|
+
}
|
|
484
|
+
if (editorFormat === "cursor" && input.history !== void 0) {
|
|
485
|
+
return input.history.length === 0;
|
|
486
|
+
}
|
|
487
|
+
if (editorFormat === "antigravity" && input.history !== void 0) {
|
|
488
|
+
return input.history.length === 0;
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
274
492
|
async function runUserPromptSubmitHook() {
|
|
275
493
|
if (!ENABLED2) {
|
|
276
494
|
process.exit(0);
|
|
@@ -289,6 +507,7 @@ async function runUserPromptSubmitHook() {
|
|
|
289
507
|
process.exit(0);
|
|
290
508
|
}
|
|
291
509
|
const editorFormat = detectEditorFormat2(input);
|
|
510
|
+
const cwd = input.cwd || process.cwd();
|
|
292
511
|
if (editorFormat === "claude") {
|
|
293
512
|
console.log(
|
|
294
513
|
JSON.stringify({
|
|
@@ -299,37 +518,84 @@ async function runUserPromptSubmitHook() {
|
|
|
299
518
|
})
|
|
300
519
|
);
|
|
301
520
|
} else if (editorFormat === "cline") {
|
|
521
|
+
loadConfigFromMcpJson(cwd);
|
|
522
|
+
const newSession = isNewSession(input, editorFormat);
|
|
523
|
+
const ctx = await fetchSessionContext();
|
|
524
|
+
const enhancedReminder = buildEnhancedReminder(ctx, newSession);
|
|
302
525
|
console.log(
|
|
303
526
|
JSON.stringify({
|
|
304
527
|
cancel: false,
|
|
305
|
-
contextModification:
|
|
528
|
+
contextModification: enhancedReminder
|
|
306
529
|
})
|
|
307
530
|
);
|
|
308
531
|
} else if (editorFormat === "cursor") {
|
|
532
|
+
loadConfigFromMcpJson(cwd);
|
|
533
|
+
const newSession = isNewSession(input, editorFormat);
|
|
534
|
+
const ctx = await fetchSessionContext();
|
|
535
|
+
const cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Use search(mode="hybrid") before Glob/Grep. Call context() first. After file edits: project(action="index") to re-index.` : `[CONTEXTSTREAM] Use search(mode="hybrid") before Glob/Grep/Read. Call context() first. After file edits: project(action="index") to re-index.`;
|
|
309
536
|
console.log(
|
|
310
537
|
JSON.stringify({
|
|
311
538
|
continue: true,
|
|
312
|
-
user_message:
|
|
539
|
+
user_message: cursorReminder
|
|
540
|
+
})
|
|
541
|
+
);
|
|
542
|
+
} else if (editorFormat === "antigravity") {
|
|
543
|
+
loadConfigFromMcpJson(cwd);
|
|
544
|
+
const newSession = isNewSession(input, editorFormat);
|
|
545
|
+
const ctx = await fetchSessionContext();
|
|
546
|
+
const enhancedReminder = buildEnhancedReminder(ctx, newSession);
|
|
547
|
+
console.log(
|
|
548
|
+
JSON.stringify({
|
|
549
|
+
cancel: false,
|
|
550
|
+
contextModification: enhancedReminder
|
|
313
551
|
})
|
|
314
552
|
);
|
|
315
553
|
}
|
|
316
554
|
process.exit(0);
|
|
317
555
|
}
|
|
318
|
-
var ENABLED2, REMINDER, isDirectRun2;
|
|
556
|
+
var ENABLED2, API_URL, API_KEY, WORKSPACE_ID, PROJECT_ID, REMINDER, ENHANCED_REMINDER_HEADER, isDirectRun2;
|
|
319
557
|
var init_user_prompt_submit = __esm({
|
|
320
558
|
"src/hooks/user-prompt-submit.ts"() {
|
|
321
559
|
"use strict";
|
|
322
560
|
ENABLED2 = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
561
|
+
API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
562
|
+
API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
563
|
+
WORKSPACE_ID = null;
|
|
564
|
+
PROJECT_ID = null;
|
|
565
|
+
REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
|
|
566
|
+
|
|
567
|
+
1. FIRST: Call mcp__contextstream__context(user_message="...") before ANY other tool
|
|
568
|
+
- Returns: dynamic rules, lessons from past mistakes, relevant context
|
|
569
|
+
- Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
|
|
570
|
+
|
|
571
|
+
2. FOR CODE SEARCH: Check index status, then search appropriately
|
|
572
|
+
\u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
|
|
573
|
+
\u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="hybrid", query="...")
|
|
574
|
+
\u2705 IF NOT indexed OR stale: Use local tools (Glob/Grep/Read) directly
|
|
575
|
+
\u2705 IF search returns 0 results: Fallback to local tools (Glob/Grep/Read)
|
|
576
|
+
|
|
577
|
+
3. WHEN LOCAL TOOLS (Glob/Grep/Read) ARE OK:
|
|
578
|
+
\u2705 Project is NOT indexed (index_status.indexed=false)
|
|
579
|
+
\u2705 Index is stale/outdated (>7 days old)
|
|
580
|
+
\u2705 ContextStream search returns 0 results or errors
|
|
581
|
+
\u2705 User explicitly requests local tools
|
|
582
|
+
|
|
583
|
+
4. FOR PLANS & TASKS: Use ContextStream, not file-based plans
|
|
584
|
+
\u2705 Plans: mcp__contextstream__session(action="capture_plan", ...)
|
|
585
|
+
\u2705 Tasks: mcp__contextstream__memory(action="create_task", ...)
|
|
586
|
+
\u274C DO NOT use EnterPlanMode or write plans to markdown files
|
|
587
|
+
|
|
588
|
+
5. CHECK THESE from context() response:
|
|
589
|
+
- Lessons: Past mistakes to avoid (shown as warnings)
|
|
590
|
+
- Reminders: Active reminders for this project
|
|
591
|
+
- Preferences: User's coding style and preferences
|
|
592
|
+
- Rules: Dynamic rules matched to current task
|
|
593
|
+
|
|
594
|
+
6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
|
|
332
595
|
[END]`;
|
|
596
|
+
ENHANCED_REMINDER_HEADER = `[CONTEXTSTREAM - ENHANCED CONTEXT]
|
|
597
|
+
|
|
598
|
+
`;
|
|
333
599
|
isDirectRun2 = process.argv[1]?.includes("user-prompt-submit") || process.argv[2] === "user-prompt-submit";
|
|
334
600
|
if (isDirectRun2) {
|
|
335
601
|
runUserPromptSubmitHook().catch(() => process.exit(0));
|
|
@@ -451,58 +717,58 @@ var pre_compact_exports = {};
|
|
|
451
717
|
__export(pre_compact_exports, {
|
|
452
718
|
runPreCompactHook: () => runPreCompactHook
|
|
453
719
|
});
|
|
454
|
-
import * as
|
|
455
|
-
import * as
|
|
456
|
-
import { homedir as
|
|
457
|
-
function
|
|
458
|
-
let searchDir =
|
|
720
|
+
import * as fs3 from "node:fs";
|
|
721
|
+
import * as path3 from "node:path";
|
|
722
|
+
import { homedir as homedir3 } from "node:os";
|
|
723
|
+
function loadConfigFromMcpJson2(cwd) {
|
|
724
|
+
let searchDir = path3.resolve(cwd);
|
|
459
725
|
for (let i = 0; i < 5; i++) {
|
|
460
|
-
if (!
|
|
461
|
-
const mcpPath =
|
|
462
|
-
if (
|
|
726
|
+
if (!API_KEY2) {
|
|
727
|
+
const mcpPath = path3.join(searchDir, ".mcp.json");
|
|
728
|
+
if (fs3.existsSync(mcpPath)) {
|
|
463
729
|
try {
|
|
464
|
-
const content =
|
|
730
|
+
const content = fs3.readFileSync(mcpPath, "utf-8");
|
|
465
731
|
const config = JSON.parse(content);
|
|
466
732
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
467
733
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
468
|
-
|
|
734
|
+
API_KEY2 = csEnv.CONTEXTSTREAM_API_KEY;
|
|
469
735
|
}
|
|
470
736
|
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
471
|
-
|
|
737
|
+
API_URL2 = csEnv.CONTEXTSTREAM_API_URL;
|
|
472
738
|
}
|
|
473
739
|
} catch {
|
|
474
740
|
}
|
|
475
741
|
}
|
|
476
742
|
}
|
|
477
|
-
if (!
|
|
478
|
-
const csConfigPath =
|
|
479
|
-
if (
|
|
743
|
+
if (!WORKSPACE_ID2) {
|
|
744
|
+
const csConfigPath = path3.join(searchDir, ".contextstream", "config.json");
|
|
745
|
+
if (fs3.existsSync(csConfigPath)) {
|
|
480
746
|
try {
|
|
481
|
-
const content =
|
|
747
|
+
const content = fs3.readFileSync(csConfigPath, "utf-8");
|
|
482
748
|
const csConfig = JSON.parse(content);
|
|
483
749
|
if (csConfig.workspace_id) {
|
|
484
|
-
|
|
750
|
+
WORKSPACE_ID2 = csConfig.workspace_id;
|
|
485
751
|
}
|
|
486
752
|
} catch {
|
|
487
753
|
}
|
|
488
754
|
}
|
|
489
755
|
}
|
|
490
|
-
const parentDir =
|
|
756
|
+
const parentDir = path3.dirname(searchDir);
|
|
491
757
|
if (parentDir === searchDir) break;
|
|
492
758
|
searchDir = parentDir;
|
|
493
759
|
}
|
|
494
|
-
if (!
|
|
495
|
-
const homeMcpPath =
|
|
496
|
-
if (
|
|
760
|
+
if (!API_KEY2) {
|
|
761
|
+
const homeMcpPath = path3.join(homedir3(), ".mcp.json");
|
|
762
|
+
if (fs3.existsSync(homeMcpPath)) {
|
|
497
763
|
try {
|
|
498
|
-
const content =
|
|
764
|
+
const content = fs3.readFileSync(homeMcpPath, "utf-8");
|
|
499
765
|
const config = JSON.parse(content);
|
|
500
766
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
501
767
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
502
|
-
|
|
768
|
+
API_KEY2 = csEnv.CONTEXTSTREAM_API_KEY;
|
|
503
769
|
}
|
|
504
770
|
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
505
|
-
|
|
771
|
+
API_URL2 = csEnv.CONTEXTSTREAM_API_URL;
|
|
506
772
|
}
|
|
507
773
|
} catch {
|
|
508
774
|
}
|
|
@@ -517,7 +783,7 @@ function parseTranscript(transcriptPath) {
|
|
|
517
783
|
let startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
518
784
|
let firstTimestamp = true;
|
|
519
785
|
try {
|
|
520
|
-
const content =
|
|
786
|
+
const content = fs3.readFileSync(transcriptPath, "utf-8");
|
|
521
787
|
const lines = content.split("\n");
|
|
522
788
|
for (const line of lines) {
|
|
523
789
|
if (!line.trim()) continue;
|
|
@@ -598,7 +864,7 @@ function parseTranscript(transcriptPath) {
|
|
|
598
864
|
};
|
|
599
865
|
}
|
|
600
866
|
async function saveFullTranscript(sessionId, transcriptData, trigger) {
|
|
601
|
-
if (!
|
|
867
|
+
if (!API_KEY2) {
|
|
602
868
|
return { success: false, message: "No API key configured" };
|
|
603
869
|
}
|
|
604
870
|
if (transcriptData.messages.length === 0) {
|
|
@@ -617,17 +883,17 @@ async function saveFullTranscript(sessionId, transcriptData, trigger) {
|
|
|
617
883
|
},
|
|
618
884
|
tags: ["pre_compaction", trigger]
|
|
619
885
|
};
|
|
620
|
-
if (
|
|
621
|
-
payload.workspace_id =
|
|
886
|
+
if (WORKSPACE_ID2) {
|
|
887
|
+
payload.workspace_id = WORKSPACE_ID2;
|
|
622
888
|
}
|
|
623
889
|
try {
|
|
624
890
|
const controller = new AbortController();
|
|
625
891
|
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
626
|
-
const response = await fetch(`${
|
|
892
|
+
const response = await fetch(`${API_URL2}/api/v1/transcripts`, {
|
|
627
893
|
method: "POST",
|
|
628
894
|
headers: {
|
|
629
895
|
"Content-Type": "application/json",
|
|
630
|
-
"X-API-Key":
|
|
896
|
+
"X-API-Key": API_KEY2
|
|
631
897
|
},
|
|
632
898
|
body: JSON.stringify(payload),
|
|
633
899
|
signal: controller.signal
|
|
@@ -642,7 +908,7 @@ async function saveFullTranscript(sessionId, transcriptData, trigger) {
|
|
|
642
908
|
}
|
|
643
909
|
}
|
|
644
910
|
async function saveSnapshot(sessionId, transcriptData, trigger) {
|
|
645
|
-
if (!
|
|
911
|
+
if (!API_KEY2) {
|
|
646
912
|
return { success: false, message: "No API key configured" };
|
|
647
913
|
}
|
|
648
914
|
const snapshotContent = {
|
|
@@ -663,17 +929,17 @@ async function saveSnapshot(sessionId, transcriptData, trigger) {
|
|
|
663
929
|
tags: ["session_snapshot", "pre_compaction", "auto_captured"],
|
|
664
930
|
source_type: "hook"
|
|
665
931
|
};
|
|
666
|
-
if (
|
|
667
|
-
payload.workspace_id =
|
|
932
|
+
if (WORKSPACE_ID2) {
|
|
933
|
+
payload.workspace_id = WORKSPACE_ID2;
|
|
668
934
|
}
|
|
669
935
|
try {
|
|
670
936
|
const controller = new AbortController();
|
|
671
937
|
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
672
|
-
const response = await fetch(`${
|
|
938
|
+
const response = await fetch(`${API_URL2}/api/v1/memory/events`, {
|
|
673
939
|
method: "POST",
|
|
674
940
|
headers: {
|
|
675
941
|
"Content-Type": "application/json",
|
|
676
|
-
"X-API-Key":
|
|
942
|
+
"X-API-Key": API_KEY2
|
|
677
943
|
},
|
|
678
944
|
body: JSON.stringify(payload),
|
|
679
945
|
signal: controller.signal
|
|
@@ -705,7 +971,7 @@ async function runPreCompactHook() {
|
|
|
705
971
|
process.exit(0);
|
|
706
972
|
}
|
|
707
973
|
const cwd = input.cwd || process.cwd();
|
|
708
|
-
|
|
974
|
+
loadConfigFromMcpJson2(cwd);
|
|
709
975
|
const sessionId = input.session_id || "unknown";
|
|
710
976
|
const transcriptPath = input.transcript_path || "";
|
|
711
977
|
const trigger = input.trigger || "unknown";
|
|
@@ -714,13 +980,15 @@ async function runPreCompactHook() {
|
|
|
714
980
|
activeFiles: [],
|
|
715
981
|
toolCallCount: 0,
|
|
716
982
|
messageCount: 0,
|
|
717
|
-
lastTools: []
|
|
983
|
+
lastTools: [],
|
|
984
|
+
messages: [],
|
|
985
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
718
986
|
};
|
|
719
|
-
if (transcriptPath &&
|
|
987
|
+
if (transcriptPath && fs3.existsSync(transcriptPath)) {
|
|
720
988
|
transcriptData = parseTranscript(transcriptPath);
|
|
721
989
|
}
|
|
722
990
|
let autoSaveStatus = "";
|
|
723
|
-
if (AUTO_SAVE &&
|
|
991
|
+
if (AUTO_SAVE && API_KEY2) {
|
|
724
992
|
const transcriptResult = await saveFullTranscript(sessionId, transcriptData, trigger);
|
|
725
993
|
if (transcriptResult.success) {
|
|
726
994
|
autoSaveStatus = `
|
|
@@ -754,15 +1022,15 @@ User instructions: ${customInstructions}` : ""}`;
|
|
|
754
1022
|
);
|
|
755
1023
|
process.exit(0);
|
|
756
1024
|
}
|
|
757
|
-
var ENABLED4, AUTO_SAVE,
|
|
1025
|
+
var ENABLED4, AUTO_SAVE, API_URL2, API_KEY2, WORKSPACE_ID2, isDirectRun4;
|
|
758
1026
|
var init_pre_compact = __esm({
|
|
759
1027
|
"src/hooks/pre-compact.ts"() {
|
|
760
1028
|
"use strict";
|
|
761
1029
|
ENABLED4 = process.env.CONTEXTSTREAM_PRECOMPACT_ENABLED !== "false";
|
|
762
1030
|
AUTO_SAVE = process.env.CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE !== "false";
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1031
|
+
API_URL2 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
1032
|
+
API_KEY2 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
1033
|
+
WORKSPACE_ID2 = null;
|
|
766
1034
|
isDirectRun4 = process.argv[1]?.includes("pre-compact") || process.argv[2] === "pre-compact";
|
|
767
1035
|
if (isDirectRun4) {
|
|
768
1036
|
runPreCompactHook().catch(() => process.exit(0));
|
|
@@ -775,9 +1043,9 @@ var post_write_exports = {};
|
|
|
775
1043
|
__export(post_write_exports, {
|
|
776
1044
|
runPostWriteHook: () => runPostWriteHook
|
|
777
1045
|
});
|
|
778
|
-
import * as
|
|
779
|
-
import * as
|
|
780
|
-
import { homedir as
|
|
1046
|
+
import * as fs4 from "node:fs";
|
|
1047
|
+
import * as path4 from "node:path";
|
|
1048
|
+
import { homedir as homedir4 } from "node:os";
|
|
781
1049
|
function extractFilePath(input) {
|
|
782
1050
|
if (input.tool_input) {
|
|
783
1051
|
const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
|
|
@@ -802,34 +1070,34 @@ function extractCwd2(input) {
|
|
|
802
1070
|
return process.cwd();
|
|
803
1071
|
}
|
|
804
1072
|
function findLocalConfig(startDir) {
|
|
805
|
-
let currentDir =
|
|
1073
|
+
let currentDir = path4.resolve(startDir);
|
|
806
1074
|
for (let i = 0; i < 10; i++) {
|
|
807
|
-
const configPath =
|
|
808
|
-
if (
|
|
1075
|
+
const configPath = path4.join(currentDir, ".contextstream", "config.json");
|
|
1076
|
+
if (fs4.existsSync(configPath)) {
|
|
809
1077
|
try {
|
|
810
|
-
const content =
|
|
1078
|
+
const content = fs4.readFileSync(configPath, "utf-8");
|
|
811
1079
|
return JSON.parse(content);
|
|
812
1080
|
} catch {
|
|
813
1081
|
}
|
|
814
1082
|
}
|
|
815
|
-
const parentDir =
|
|
1083
|
+
const parentDir = path4.dirname(currentDir);
|
|
816
1084
|
if (parentDir === currentDir) break;
|
|
817
1085
|
currentDir = parentDir;
|
|
818
1086
|
}
|
|
819
1087
|
return null;
|
|
820
1088
|
}
|
|
821
1089
|
function loadApiConfig(startDir) {
|
|
822
|
-
let apiUrl =
|
|
823
|
-
let apiKey =
|
|
1090
|
+
let apiUrl = API_URL3;
|
|
1091
|
+
let apiKey = API_KEY3;
|
|
824
1092
|
if (apiKey) {
|
|
825
1093
|
return { apiUrl, apiKey };
|
|
826
1094
|
}
|
|
827
|
-
let currentDir =
|
|
1095
|
+
let currentDir = path4.resolve(startDir);
|
|
828
1096
|
for (let i = 0; i < 10; i++) {
|
|
829
|
-
const mcpPath =
|
|
830
|
-
if (
|
|
1097
|
+
const mcpPath = path4.join(currentDir, ".mcp.json");
|
|
1098
|
+
if (fs4.existsSync(mcpPath)) {
|
|
831
1099
|
try {
|
|
832
|
-
const content =
|
|
1100
|
+
const content = fs4.readFileSync(mcpPath, "utf-8");
|
|
833
1101
|
const config = JSON.parse(content);
|
|
834
1102
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
835
1103
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -842,15 +1110,15 @@ function loadApiConfig(startDir) {
|
|
|
842
1110
|
} catch {
|
|
843
1111
|
}
|
|
844
1112
|
}
|
|
845
|
-
const parentDir =
|
|
1113
|
+
const parentDir = path4.dirname(currentDir);
|
|
846
1114
|
if (parentDir === currentDir) break;
|
|
847
1115
|
currentDir = parentDir;
|
|
848
1116
|
}
|
|
849
1117
|
if (!apiKey) {
|
|
850
|
-
const homeMcpPath =
|
|
851
|
-
if (
|
|
1118
|
+
const homeMcpPath = path4.join(homedir4(), ".mcp.json");
|
|
1119
|
+
if (fs4.existsSync(homeMcpPath)) {
|
|
852
1120
|
try {
|
|
853
|
-
const content =
|
|
1121
|
+
const content = fs4.readFileSync(homeMcpPath, "utf-8");
|
|
854
1122
|
const config = JSON.parse(content);
|
|
855
1123
|
const csEnv = config.mcpServers?.contextstream?.env;
|
|
856
1124
|
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
@@ -866,15 +1134,15 @@ function loadApiConfig(startDir) {
|
|
|
866
1134
|
return { apiUrl, apiKey };
|
|
867
1135
|
}
|
|
868
1136
|
function shouldIndexFile(filePath) {
|
|
869
|
-
const ext =
|
|
1137
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
870
1138
|
if (!INDEXABLE_EXTENSIONS.has(ext)) {
|
|
871
|
-
const basename2 =
|
|
1139
|
+
const basename2 = path4.basename(filePath).toLowerCase();
|
|
872
1140
|
if (!["dockerfile", "makefile", "rakefile", "gemfile", "procfile"].includes(basename2)) {
|
|
873
1141
|
return false;
|
|
874
1142
|
}
|
|
875
1143
|
}
|
|
876
1144
|
try {
|
|
877
|
-
const stats =
|
|
1145
|
+
const stats = fs4.statSync(filePath);
|
|
878
1146
|
if (stats.size > MAX_FILE_SIZE) {
|
|
879
1147
|
return false;
|
|
880
1148
|
}
|
|
@@ -884,7 +1152,7 @@ function shouldIndexFile(filePath) {
|
|
|
884
1152
|
return true;
|
|
885
1153
|
}
|
|
886
1154
|
function detectLanguage(filePath) {
|
|
887
|
-
const ext =
|
|
1155
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
888
1156
|
const langMap = {
|
|
889
1157
|
".ts": "typescript",
|
|
890
1158
|
".tsx": "typescript",
|
|
@@ -953,8 +1221,8 @@ function detectLanguage(filePath) {
|
|
|
953
1221
|
return langMap[ext] || "text";
|
|
954
1222
|
}
|
|
955
1223
|
async function indexFile(filePath, projectId, apiUrl, apiKey, projectRoot) {
|
|
956
|
-
const content =
|
|
957
|
-
const relativePath =
|
|
1224
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1225
|
+
const relativePath = path4.relative(projectRoot, filePath);
|
|
958
1226
|
const payload = {
|
|
959
1227
|
files: [
|
|
960
1228
|
{
|
|
@@ -979,13 +1247,13 @@ async function indexFile(filePath, projectId, apiUrl, apiKey, projectRoot) {
|
|
|
979
1247
|
}
|
|
980
1248
|
}
|
|
981
1249
|
function findProjectRoot(filePath) {
|
|
982
|
-
let currentDir =
|
|
1250
|
+
let currentDir = path4.dirname(path4.resolve(filePath));
|
|
983
1251
|
for (let i = 0; i < 10; i++) {
|
|
984
|
-
const configPath =
|
|
985
|
-
if (
|
|
1252
|
+
const configPath = path4.join(currentDir, ".contextstream", "config.json");
|
|
1253
|
+
if (fs4.existsSync(configPath)) {
|
|
986
1254
|
return currentDir;
|
|
987
1255
|
}
|
|
988
|
-
const parentDir =
|
|
1256
|
+
const parentDir = path4.dirname(currentDir);
|
|
989
1257
|
if (parentDir === currentDir) break;
|
|
990
1258
|
currentDir = parentDir;
|
|
991
1259
|
}
|
|
@@ -1013,8 +1281,8 @@ async function runPostWriteHook() {
|
|
|
1013
1281
|
process.exit(0);
|
|
1014
1282
|
}
|
|
1015
1283
|
const cwd = extractCwd2(input);
|
|
1016
|
-
const absolutePath =
|
|
1017
|
-
if (!
|
|
1284
|
+
const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.resolve(cwd, filePath);
|
|
1285
|
+
if (!fs4.existsSync(absolutePath) || !shouldIndexFile(absolutePath)) {
|
|
1018
1286
|
process.exit(0);
|
|
1019
1287
|
}
|
|
1020
1288
|
const projectRoot = findProjectRoot(absolutePath);
|
|
@@ -1035,12 +1303,12 @@ async function runPostWriteHook() {
|
|
|
1035
1303
|
}
|
|
1036
1304
|
process.exit(0);
|
|
1037
1305
|
}
|
|
1038
|
-
var
|
|
1306
|
+
var API_URL3, API_KEY3, ENABLED5, INDEXABLE_EXTENSIONS, MAX_FILE_SIZE, isDirectRun5;
|
|
1039
1307
|
var init_post_write = __esm({
|
|
1040
1308
|
"src/hooks/post-write.ts"() {
|
|
1041
1309
|
"use strict";
|
|
1042
|
-
|
|
1043
|
-
|
|
1310
|
+
API_URL3 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
1311
|
+
API_KEY3 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
1044
1312
|
ENABLED5 = process.env.CONTEXTSTREAM_POSTWRITE_ENABLED !== "false";
|
|
1045
1313
|
INDEXABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1046
1314
|
".ts",
|
|
@@ -1159,16 +1427,20 @@ __export(hooks_config_exports, {
|
|
|
1159
1427
|
writeCursorHooksConfig: () => writeCursorHooksConfig,
|
|
1160
1428
|
writeIndexStatus: () => writeIndexStatus
|
|
1161
1429
|
});
|
|
1162
|
-
import * as
|
|
1163
|
-
import * as
|
|
1164
|
-
import { homedir as
|
|
1430
|
+
import * as fs5 from "node:fs/promises";
|
|
1431
|
+
import * as path5 from "node:path";
|
|
1432
|
+
import { homedir as homedir5 } from "node:os";
|
|
1165
1433
|
import { fileURLToPath } from "node:url";
|
|
1166
1434
|
function getHookCommand(hookName2) {
|
|
1435
|
+
const fs7 = __require("node:fs");
|
|
1436
|
+
const binaryPath = "/usr/local/bin/contextstream-mcp";
|
|
1437
|
+
if (fs7.existsSync(binaryPath)) {
|
|
1438
|
+
return `${binaryPath} hook ${hookName2}`;
|
|
1439
|
+
}
|
|
1167
1440
|
try {
|
|
1168
|
-
const __dirname =
|
|
1169
|
-
const indexPath =
|
|
1170
|
-
|
|
1171
|
-
if (fs6.existsSync(indexPath)) {
|
|
1441
|
+
const __dirname = path5.dirname(fileURLToPath(import.meta.url));
|
|
1442
|
+
const indexPath = path5.join(__dirname, "index.js");
|
|
1443
|
+
if (fs7.existsSync(indexPath)) {
|
|
1172
1444
|
return `node ${indexPath} hook ${hookName2}`;
|
|
1173
1445
|
}
|
|
1174
1446
|
} catch {
|
|
@@ -1177,15 +1449,15 @@ function getHookCommand(hookName2) {
|
|
|
1177
1449
|
}
|
|
1178
1450
|
function getClaudeSettingsPath(scope, projectPath) {
|
|
1179
1451
|
if (scope === "user") {
|
|
1180
|
-
return
|
|
1452
|
+
return path5.join(homedir5(), ".claude", "settings.json");
|
|
1181
1453
|
}
|
|
1182
1454
|
if (!projectPath) {
|
|
1183
1455
|
throw new Error("projectPath required for project scope");
|
|
1184
1456
|
}
|
|
1185
|
-
return
|
|
1457
|
+
return path5.join(projectPath, ".claude", "settings.json");
|
|
1186
1458
|
}
|
|
1187
1459
|
function getHooksDir() {
|
|
1188
|
-
return
|
|
1460
|
+
return path5.join(homedir5(), ".claude", "hooks");
|
|
1189
1461
|
}
|
|
1190
1462
|
function buildHooksConfig(options) {
|
|
1191
1463
|
const userPromptHooks = [
|
|
@@ -1361,7 +1633,7 @@ function buildHooksConfig(options) {
|
|
|
1361
1633
|
}
|
|
1362
1634
|
async function installHookScripts(options) {
|
|
1363
1635
|
const hooksDir = getHooksDir();
|
|
1364
|
-
await
|
|
1636
|
+
await fs5.mkdir(hooksDir, { recursive: true });
|
|
1365
1637
|
const result = {
|
|
1366
1638
|
preToolUse: getHookCommand("pre-tool-use"),
|
|
1367
1639
|
userPrompt: getHookCommand("user-prompt-submit")
|
|
@@ -1380,7 +1652,7 @@ async function installHookScripts(options) {
|
|
|
1380
1652
|
async function readClaudeSettings(scope, projectPath) {
|
|
1381
1653
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
1382
1654
|
try {
|
|
1383
|
-
const content = await
|
|
1655
|
+
const content = await fs5.readFile(settingsPath, "utf-8");
|
|
1384
1656
|
return JSON.parse(content);
|
|
1385
1657
|
} catch {
|
|
1386
1658
|
return {};
|
|
@@ -1388,9 +1660,9 @@ async function readClaudeSettings(scope, projectPath) {
|
|
|
1388
1660
|
}
|
|
1389
1661
|
async function writeClaudeSettings(settings, scope, projectPath) {
|
|
1390
1662
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
1391
|
-
const dir =
|
|
1392
|
-
await
|
|
1393
|
-
await
|
|
1663
|
+
const dir = path5.dirname(settingsPath);
|
|
1664
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1665
|
+
await fs5.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
1394
1666
|
}
|
|
1395
1667
|
function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
1396
1668
|
const settings = { ...existingSettings };
|
|
@@ -1540,12 +1812,12 @@ If you prefer to configure manually, add to \`~/.claude/settings.json\`:
|
|
|
1540
1812
|
`.trim();
|
|
1541
1813
|
}
|
|
1542
1814
|
function getIndexStatusPath() {
|
|
1543
|
-
return
|
|
1815
|
+
return path5.join(homedir5(), ".contextstream", "indexed-projects.json");
|
|
1544
1816
|
}
|
|
1545
1817
|
async function readIndexStatus() {
|
|
1546
1818
|
const statusPath = getIndexStatusPath();
|
|
1547
1819
|
try {
|
|
1548
|
-
const content = await
|
|
1820
|
+
const content = await fs5.readFile(statusPath, "utf-8");
|
|
1549
1821
|
return JSON.parse(content);
|
|
1550
1822
|
} catch {
|
|
1551
1823
|
return { version: 1, projects: {} };
|
|
@@ -1553,13 +1825,13 @@ async function readIndexStatus() {
|
|
|
1553
1825
|
}
|
|
1554
1826
|
async function writeIndexStatus(status) {
|
|
1555
1827
|
const statusPath = getIndexStatusPath();
|
|
1556
|
-
const dir =
|
|
1557
|
-
await
|
|
1558
|
-
await
|
|
1828
|
+
const dir = path5.dirname(statusPath);
|
|
1829
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1830
|
+
await fs5.writeFile(statusPath, JSON.stringify(status, null, 2));
|
|
1559
1831
|
}
|
|
1560
1832
|
async function markProjectIndexed(projectPath, options) {
|
|
1561
1833
|
const status = await readIndexStatus();
|
|
1562
|
-
const resolvedPath =
|
|
1834
|
+
const resolvedPath = path5.resolve(projectPath);
|
|
1563
1835
|
status.projects[resolvedPath] = {
|
|
1564
1836
|
indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1565
1837
|
project_id: options?.project_id,
|
|
@@ -1569,113 +1841,113 @@ async function markProjectIndexed(projectPath, options) {
|
|
|
1569
1841
|
}
|
|
1570
1842
|
async function unmarkProjectIndexed(projectPath) {
|
|
1571
1843
|
const status = await readIndexStatus();
|
|
1572
|
-
const resolvedPath =
|
|
1844
|
+
const resolvedPath = path5.resolve(projectPath);
|
|
1573
1845
|
delete status.projects[resolvedPath];
|
|
1574
1846
|
await writeIndexStatus(status);
|
|
1575
1847
|
}
|
|
1576
1848
|
function getClineHooksDir(scope, projectPath) {
|
|
1577
1849
|
if (scope === "global") {
|
|
1578
|
-
return
|
|
1850
|
+
return path5.join(homedir5(), "Documents", "Cline", "Rules", "Hooks");
|
|
1579
1851
|
}
|
|
1580
1852
|
if (!projectPath) {
|
|
1581
1853
|
throw new Error("projectPath required for project scope");
|
|
1582
1854
|
}
|
|
1583
|
-
return
|
|
1855
|
+
return path5.join(projectPath, ".clinerules", "hooks");
|
|
1584
1856
|
}
|
|
1585
1857
|
async function installClineHookScripts(options) {
|
|
1586
1858
|
const hooksDir = getClineHooksDir(options.scope, options.projectPath);
|
|
1587
|
-
await
|
|
1588
|
-
const preToolUsePath =
|
|
1589
|
-
const userPromptPath =
|
|
1590
|
-
const postToolUsePath =
|
|
1591
|
-
await
|
|
1592
|
-
await
|
|
1859
|
+
await fs5.mkdir(hooksDir, { recursive: true });
|
|
1860
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
1861
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
1862
|
+
const postToolUsePath = path5.join(hooksDir, "PostToolUse");
|
|
1863
|
+
await fs5.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
|
|
1864
|
+
await fs5.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
|
|
1593
1865
|
const result = {
|
|
1594
1866
|
preToolUse: preToolUsePath,
|
|
1595
1867
|
userPromptSubmit: userPromptPath
|
|
1596
1868
|
};
|
|
1597
1869
|
if (options.includePostWrite !== false) {
|
|
1598
|
-
await
|
|
1870
|
+
await fs5.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
|
|
1599
1871
|
result.postToolUse = postToolUsePath;
|
|
1600
1872
|
}
|
|
1601
1873
|
return result;
|
|
1602
1874
|
}
|
|
1603
1875
|
function getRooCodeHooksDir(scope, projectPath) {
|
|
1604
1876
|
if (scope === "global") {
|
|
1605
|
-
return
|
|
1877
|
+
return path5.join(homedir5(), ".roo", "hooks");
|
|
1606
1878
|
}
|
|
1607
1879
|
if (!projectPath) {
|
|
1608
1880
|
throw new Error("projectPath required for project scope");
|
|
1609
1881
|
}
|
|
1610
|
-
return
|
|
1882
|
+
return path5.join(projectPath, ".roo", "hooks");
|
|
1611
1883
|
}
|
|
1612
1884
|
async function installRooCodeHookScripts(options) {
|
|
1613
1885
|
const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
|
|
1614
|
-
await
|
|
1615
|
-
const preToolUsePath =
|
|
1616
|
-
const userPromptPath =
|
|
1617
|
-
const postToolUsePath =
|
|
1618
|
-
await
|
|
1619
|
-
await
|
|
1886
|
+
await fs5.mkdir(hooksDir, { recursive: true });
|
|
1887
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
1888
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
1889
|
+
const postToolUsePath = path5.join(hooksDir, "PostToolUse");
|
|
1890
|
+
await fs5.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
|
|
1891
|
+
await fs5.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
|
|
1620
1892
|
const result = {
|
|
1621
1893
|
preToolUse: preToolUsePath,
|
|
1622
1894
|
userPromptSubmit: userPromptPath
|
|
1623
1895
|
};
|
|
1624
1896
|
if (options.includePostWrite !== false) {
|
|
1625
|
-
await
|
|
1897
|
+
await fs5.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
|
|
1626
1898
|
result.postToolUse = postToolUsePath;
|
|
1627
1899
|
}
|
|
1628
1900
|
return result;
|
|
1629
1901
|
}
|
|
1630
1902
|
function getKiloCodeHooksDir(scope, projectPath) {
|
|
1631
1903
|
if (scope === "global") {
|
|
1632
|
-
return
|
|
1904
|
+
return path5.join(homedir5(), ".kilocode", "hooks");
|
|
1633
1905
|
}
|
|
1634
1906
|
if (!projectPath) {
|
|
1635
1907
|
throw new Error("projectPath required for project scope");
|
|
1636
1908
|
}
|
|
1637
|
-
return
|
|
1909
|
+
return path5.join(projectPath, ".kilocode", "hooks");
|
|
1638
1910
|
}
|
|
1639
1911
|
async function installKiloCodeHookScripts(options) {
|
|
1640
1912
|
const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
|
|
1641
|
-
await
|
|
1642
|
-
const preToolUsePath =
|
|
1643
|
-
const userPromptPath =
|
|
1644
|
-
const postToolUsePath =
|
|
1645
|
-
await
|
|
1646
|
-
await
|
|
1913
|
+
await fs5.mkdir(hooksDir, { recursive: true });
|
|
1914
|
+
const preToolUsePath = path5.join(hooksDir, "PreToolUse");
|
|
1915
|
+
const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
|
|
1916
|
+
const postToolUsePath = path5.join(hooksDir, "PostToolUse");
|
|
1917
|
+
await fs5.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
|
|
1918
|
+
await fs5.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
|
|
1647
1919
|
const result = {
|
|
1648
1920
|
preToolUse: preToolUsePath,
|
|
1649
1921
|
userPromptSubmit: userPromptPath
|
|
1650
1922
|
};
|
|
1651
1923
|
if (options.includePostWrite !== false) {
|
|
1652
|
-
await
|
|
1924
|
+
await fs5.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
|
|
1653
1925
|
result.postToolUse = postToolUsePath;
|
|
1654
1926
|
}
|
|
1655
1927
|
return result;
|
|
1656
1928
|
}
|
|
1657
1929
|
function getCursorHooksConfigPath(scope, projectPath) {
|
|
1658
1930
|
if (scope === "global") {
|
|
1659
|
-
return
|
|
1931
|
+
return path5.join(homedir5(), ".cursor", "hooks.json");
|
|
1660
1932
|
}
|
|
1661
1933
|
if (!projectPath) {
|
|
1662
1934
|
throw new Error("projectPath required for project scope");
|
|
1663
1935
|
}
|
|
1664
|
-
return
|
|
1936
|
+
return path5.join(projectPath, ".cursor", "hooks.json");
|
|
1665
1937
|
}
|
|
1666
1938
|
function getCursorHooksDir(scope, projectPath) {
|
|
1667
1939
|
if (scope === "global") {
|
|
1668
|
-
return
|
|
1940
|
+
return path5.join(homedir5(), ".cursor", "hooks");
|
|
1669
1941
|
}
|
|
1670
1942
|
if (!projectPath) {
|
|
1671
1943
|
throw new Error("projectPath required for project scope");
|
|
1672
1944
|
}
|
|
1673
|
-
return
|
|
1945
|
+
return path5.join(projectPath, ".cursor", "hooks");
|
|
1674
1946
|
}
|
|
1675
1947
|
async function readCursorHooksConfig(scope, projectPath) {
|
|
1676
1948
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
1677
1949
|
try {
|
|
1678
|
-
const content = await
|
|
1950
|
+
const content = await fs5.readFile(configPath, "utf-8");
|
|
1679
1951
|
return JSON.parse(content);
|
|
1680
1952
|
} catch {
|
|
1681
1953
|
return { version: 1, hooks: {} };
|
|
@@ -1683,13 +1955,13 @@ async function readCursorHooksConfig(scope, projectPath) {
|
|
|
1683
1955
|
}
|
|
1684
1956
|
async function writeCursorHooksConfig(config, scope, projectPath) {
|
|
1685
1957
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
1686
|
-
const dir =
|
|
1687
|
-
await
|
|
1688
|
-
await
|
|
1958
|
+
const dir = path5.dirname(configPath);
|
|
1959
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1960
|
+
await fs5.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
1689
1961
|
}
|
|
1690
1962
|
async function installCursorHookScripts(options) {
|
|
1691
1963
|
const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
|
|
1692
|
-
await
|
|
1964
|
+
await fs5.mkdir(hooksDir, { recursive: true });
|
|
1693
1965
|
const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
|
|
1694
1966
|
const filterContextStreamHooks = (hooks2) => {
|
|
1695
1967
|
if (!hooks2) return [];
|
|
@@ -2712,13 +2984,13 @@ var auto_rules_exports = {};
|
|
|
2712
2984
|
__export(auto_rules_exports, {
|
|
2713
2985
|
runAutoRulesHook: () => runAutoRulesHook
|
|
2714
2986
|
});
|
|
2715
|
-
import * as
|
|
2716
|
-
import * as
|
|
2717
|
-
import { homedir as
|
|
2987
|
+
import * as fs6 from "node:fs";
|
|
2988
|
+
import * as path6 from "node:path";
|
|
2989
|
+
import { homedir as homedir6 } from "node:os";
|
|
2718
2990
|
function hasRunRecently() {
|
|
2719
2991
|
try {
|
|
2720
|
-
if (!
|
|
2721
|
-
const stat =
|
|
2992
|
+
if (!fs6.existsSync(MARKER_FILE)) return false;
|
|
2993
|
+
const stat = fs6.statSync(MARKER_FILE);
|
|
2722
2994
|
const age = Date.now() - stat.mtimeMs;
|
|
2723
2995
|
return age < COOLDOWN_MS;
|
|
2724
2996
|
} catch {
|
|
@@ -2727,11 +2999,11 @@ function hasRunRecently() {
|
|
|
2727
2999
|
}
|
|
2728
3000
|
function markAsRan() {
|
|
2729
3001
|
try {
|
|
2730
|
-
const dir =
|
|
2731
|
-
if (!
|
|
2732
|
-
|
|
3002
|
+
const dir = path6.dirname(MARKER_FILE);
|
|
3003
|
+
if (!fs6.existsSync(dir)) {
|
|
3004
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2733
3005
|
}
|
|
2734
|
-
|
|
3006
|
+
fs6.writeFileSync(MARKER_FILE, (/* @__PURE__ */ new Date()).toISOString());
|
|
2735
3007
|
} catch {
|
|
2736
3008
|
}
|
|
2737
3009
|
}
|
|
@@ -2760,8 +3032,8 @@ function extractCwd3(input) {
|
|
|
2760
3032
|
}
|
|
2761
3033
|
function hasPythonHooks(settingsPath) {
|
|
2762
3034
|
try {
|
|
2763
|
-
if (!
|
|
2764
|
-
const content =
|
|
3035
|
+
if (!fs6.existsSync(settingsPath)) return false;
|
|
3036
|
+
const content = fs6.readFileSync(settingsPath, "utf-8");
|
|
2765
3037
|
const settings = JSON.parse(content);
|
|
2766
3038
|
const hooks2 = settings.hooks;
|
|
2767
3039
|
if (!hooks2) return false;
|
|
@@ -2785,8 +3057,8 @@ function hasPythonHooks(settingsPath) {
|
|
|
2785
3057
|
}
|
|
2786
3058
|
}
|
|
2787
3059
|
function detectPythonHooks(cwd) {
|
|
2788
|
-
const globalSettingsPath =
|
|
2789
|
-
const projectSettingsPath =
|
|
3060
|
+
const globalSettingsPath = path6.join(homedir6(), ".claude", "settings.json");
|
|
3061
|
+
const projectSettingsPath = path6.join(cwd, ".claude", "settings.json");
|
|
2790
3062
|
return {
|
|
2791
3063
|
global: hasPythonHooks(globalSettingsPath),
|
|
2792
3064
|
project: hasPythonHooks(projectSettingsPath)
|
|
@@ -2844,14 +3116,14 @@ async function runAutoRulesHook() {
|
|
|
2844
3116
|
}
|
|
2845
3117
|
process.exit(0);
|
|
2846
3118
|
}
|
|
2847
|
-
var
|
|
3119
|
+
var API_URL4, API_KEY4, ENABLED6, MARKER_FILE, COOLDOWN_MS, isDirectRun6;
|
|
2848
3120
|
var init_auto_rules = __esm({
|
|
2849
3121
|
"src/hooks/auto-rules.ts"() {
|
|
2850
3122
|
"use strict";
|
|
2851
|
-
|
|
2852
|
-
|
|
3123
|
+
API_URL4 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
3124
|
+
API_KEY4 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
2853
3125
|
ENABLED6 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
|
|
2854
|
-
MARKER_FILE =
|
|
3126
|
+
MARKER_FILE = path6.join(homedir6(), ".contextstream", ".auto-rules-ran");
|
|
2855
3127
|
COOLDOWN_MS = 4 * 60 * 60 * 1e3;
|
|
2856
3128
|
isDirectRun6 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
|
|
2857
3129
|
if (isDirectRun6) {
|