@contextstream/mcp-server 0.4.50 → 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.
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/session-end.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var ENABLED = process.env.CONTEXTSTREAM_SESSION_END_ENABLED !== "false";
8
+ var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
+ var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
+ var WORKSPACE_ID = null;
11
+ function loadConfigFromMcpJson(cwd) {
12
+ let searchDir = path.resolve(cwd);
13
+ for (let i = 0; i < 5; i++) {
14
+ if (!API_KEY) {
15
+ const mcpPath = path.join(searchDir, ".mcp.json");
16
+ if (fs.existsSync(mcpPath)) {
17
+ try {
18
+ const content = fs.readFileSync(mcpPath, "utf-8");
19
+ const config = JSON.parse(content);
20
+ const csEnv = config.mcpServers?.contextstream?.env;
21
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
22
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
23
+ }
24
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
25
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
26
+ }
27
+ } catch {
28
+ }
29
+ }
30
+ }
31
+ if (!WORKSPACE_ID) {
32
+ const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
33
+ if (fs.existsSync(csConfigPath)) {
34
+ try {
35
+ const content = fs.readFileSync(csConfigPath, "utf-8");
36
+ const csConfig = JSON.parse(content);
37
+ if (csConfig.workspace_id) {
38
+ WORKSPACE_ID = csConfig.workspace_id;
39
+ }
40
+ } catch {
41
+ }
42
+ }
43
+ }
44
+ const parentDir = path.dirname(searchDir);
45
+ if (parentDir === searchDir) break;
46
+ searchDir = parentDir;
47
+ }
48
+ if (!API_KEY) {
49
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
50
+ if (fs.existsSync(homeMcpPath)) {
51
+ try {
52
+ const content = fs.readFileSync(homeMcpPath, "utf-8");
53
+ const config = JSON.parse(content);
54
+ const csEnv = config.mcpServers?.contextstream?.env;
55
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
56
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
57
+ }
58
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
59
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
60
+ }
61
+ } catch {
62
+ }
63
+ }
64
+ }
65
+ }
66
+ function parseTranscriptStats(transcriptPath) {
67
+ const stats = {
68
+ messageCount: 0,
69
+ toolCallCount: 0,
70
+ duration: 0,
71
+ filesModified: []
72
+ };
73
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
74
+ return stats;
75
+ }
76
+ try {
77
+ const content = fs.readFileSync(transcriptPath, "utf-8");
78
+ const lines = content.split("\n");
79
+ let firstTimestamp = null;
80
+ let lastTimestamp = null;
81
+ const modifiedFiles = /* @__PURE__ */ new Set();
82
+ for (const line of lines) {
83
+ if (!line.trim()) continue;
84
+ try {
85
+ const entry = JSON.parse(line);
86
+ if (entry.type === "user" || entry.type === "assistant") {
87
+ stats.messageCount++;
88
+ } else if (entry.type === "tool_use") {
89
+ stats.toolCallCount++;
90
+ if (["Write", "Edit", "NotebookEdit"].includes(entry.name || "")) {
91
+ const filePath = entry.input?.file_path;
92
+ if (filePath) {
93
+ modifiedFiles.add(filePath);
94
+ }
95
+ }
96
+ }
97
+ if (entry.timestamp) {
98
+ const ts = new Date(entry.timestamp);
99
+ if (!firstTimestamp || ts < firstTimestamp) {
100
+ firstTimestamp = ts;
101
+ }
102
+ if (!lastTimestamp || ts > lastTimestamp) {
103
+ lastTimestamp = ts;
104
+ }
105
+ }
106
+ } catch {
107
+ continue;
108
+ }
109
+ }
110
+ if (firstTimestamp && lastTimestamp) {
111
+ stats.duration = Math.round((lastTimestamp.getTime() - firstTimestamp.getTime()) / 1e3);
112
+ }
113
+ stats.filesModified = Array.from(modifiedFiles);
114
+ } catch {
115
+ }
116
+ return stats;
117
+ }
118
+ async function finalizeSession(sessionId, stats, reason) {
119
+ if (!API_KEY) return;
120
+ const payload = {
121
+ event_type: "session_end",
122
+ title: `Session Ended: ${reason}`,
123
+ content: JSON.stringify({
124
+ session_id: sessionId,
125
+ reason,
126
+ stats: {
127
+ messages: stats.messageCount,
128
+ tool_calls: stats.toolCallCount,
129
+ duration_seconds: stats.duration,
130
+ files_modified: stats.filesModified.length
131
+ },
132
+ files_modified: stats.filesModified.slice(0, 20),
133
+ ended_at: (/* @__PURE__ */ new Date()).toISOString()
134
+ }),
135
+ importance: "low",
136
+ tags: ["session", "end", reason],
137
+ source_type: "hook",
138
+ session_id: sessionId
139
+ };
140
+ if (WORKSPACE_ID) {
141
+ payload.workspace_id = WORKSPACE_ID;
142
+ }
143
+ try {
144
+ const controller = new AbortController();
145
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
146
+ await fetch(`${API_URL}/api/v1/memory/events`, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ "X-API-Key": API_KEY
151
+ },
152
+ body: JSON.stringify(payload),
153
+ signal: controller.signal
154
+ });
155
+ clearTimeout(timeoutId);
156
+ } catch {
157
+ }
158
+ }
159
+ async function runSessionEndHook() {
160
+ if (!ENABLED) {
161
+ process.exit(0);
162
+ }
163
+ let inputData = "";
164
+ for await (const chunk of process.stdin) {
165
+ inputData += chunk;
166
+ }
167
+ if (!inputData.trim()) {
168
+ process.exit(0);
169
+ }
170
+ let input;
171
+ try {
172
+ input = JSON.parse(inputData);
173
+ } catch {
174
+ process.exit(0);
175
+ }
176
+ const cwd = input.cwd || process.cwd();
177
+ loadConfigFromMcpJson(cwd);
178
+ const sessionId = input.session_id || "unknown";
179
+ const transcriptPath = input.transcript_path || "";
180
+ const reason = input.reason || "user_exit";
181
+ const stats = parseTranscriptStats(transcriptPath);
182
+ await finalizeSession(sessionId, stats, reason);
183
+ process.exit(0);
184
+ }
185
+ var isDirectRun = process.argv[1]?.includes("session-end") || process.argv[2] === "session-end";
186
+ if (isDirectRun) {
187
+ runSessionEndHook().catch(() => process.exit(0));
188
+ }
189
+ export {
190
+ runSessionEndHook
191
+ };
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/session-init.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var ENABLED = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
8
+ var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
+ var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
+ var WORKSPACE_ID = null;
11
+ var PROJECT_ID = null;
12
+ function loadConfigFromMcpJson(cwd) {
13
+ let searchDir = path.resolve(cwd);
14
+ for (let i = 0; i < 5; i++) {
15
+ if (!API_KEY) {
16
+ const mcpPath = path.join(searchDir, ".mcp.json");
17
+ if (fs.existsSync(mcpPath)) {
18
+ try {
19
+ const content = fs.readFileSync(mcpPath, "utf-8");
20
+ const config = JSON.parse(content);
21
+ const csEnv = config.mcpServers?.contextstream?.env;
22
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
23
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
24
+ }
25
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
26
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
27
+ }
28
+ if (csEnv?.CONTEXTSTREAM_WORKSPACE_ID) {
29
+ WORKSPACE_ID = csEnv.CONTEXTSTREAM_WORKSPACE_ID;
30
+ }
31
+ } catch {
32
+ }
33
+ }
34
+ }
35
+ if (!WORKSPACE_ID || !PROJECT_ID) {
36
+ const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
37
+ if (fs.existsSync(csConfigPath)) {
38
+ try {
39
+ const content = fs.readFileSync(csConfigPath, "utf-8");
40
+ const csConfig = JSON.parse(content);
41
+ if (csConfig.workspace_id && !WORKSPACE_ID) {
42
+ WORKSPACE_ID = csConfig.workspace_id;
43
+ }
44
+ if (csConfig.project_id && !PROJECT_ID) {
45
+ PROJECT_ID = csConfig.project_id;
46
+ }
47
+ } catch {
48
+ }
49
+ }
50
+ }
51
+ const parentDir = path.dirname(searchDir);
52
+ if (parentDir === searchDir) break;
53
+ searchDir = parentDir;
54
+ }
55
+ if (!API_KEY) {
56
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
57
+ if (fs.existsSync(homeMcpPath)) {
58
+ try {
59
+ const content = fs.readFileSync(homeMcpPath, "utf-8");
60
+ const config = JSON.parse(content);
61
+ const csEnv = config.mcpServers?.contextstream?.env;
62
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
63
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
64
+ }
65
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
66
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
67
+ }
68
+ } catch {
69
+ }
70
+ }
71
+ }
72
+ }
73
+ async function fetchSessionContext() {
74
+ if (!API_KEY) return null;
75
+ try {
76
+ const controller = new AbortController();
77
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
78
+ const url = new URL(`${API_URL}/api/v1/context`);
79
+ if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
80
+ if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
81
+ url.searchParams.set("include_rules", "true");
82
+ url.searchParams.set("include_lessons", "true");
83
+ url.searchParams.set("include_decisions", "true");
84
+ url.searchParams.set("include_plans", "true");
85
+ url.searchParams.set("limit", "5");
86
+ const response = await fetch(url.toString(), {
87
+ method: "GET",
88
+ headers: {
89
+ "X-API-Key": API_KEY
90
+ },
91
+ signal: controller.signal
92
+ });
93
+ clearTimeout(timeoutId);
94
+ if (response.ok) {
95
+ return await response.json();
96
+ }
97
+ return null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function formatContext(ctx) {
103
+ if (!ctx) {
104
+ return `[ContextStream Session Start]
105
+
106
+ No stored context found. Call \`mcp__contextstream__context(user_message="starting new session")\` to initialize.`;
107
+ }
108
+ const parts = ["[ContextStream Session Start]"];
109
+ if (ctx.lessons && ctx.lessons.length > 0) {
110
+ parts.push("\n## \u26A0\uFE0F Lessons from Past Mistakes");
111
+ for (const lesson of ctx.lessons.slice(0, 3)) {
112
+ parts.push(`- **${lesson.title}**: ${lesson.prevention}`);
113
+ }
114
+ }
115
+ if (ctx.active_plans && ctx.active_plans.length > 0) {
116
+ parts.push("\n## \u{1F4CB} Active Plans");
117
+ for (const plan of ctx.active_plans.slice(0, 3)) {
118
+ parts.push(`- ${plan.title} (${plan.status})`);
119
+ }
120
+ }
121
+ if (ctx.pending_tasks && ctx.pending_tasks.length > 0) {
122
+ parts.push("\n## \u2705 Pending Tasks");
123
+ for (const task of ctx.pending_tasks.slice(0, 5)) {
124
+ parts.push(`- ${task.title}`);
125
+ }
126
+ }
127
+ if (ctx.recent_decisions && ctx.recent_decisions.length > 0) {
128
+ parts.push("\n## \u{1F4DD} Recent Decisions");
129
+ for (const decision of ctx.recent_decisions.slice(0, 3)) {
130
+ parts.push(`- **${decision.title}**`);
131
+ }
132
+ }
133
+ parts.push("\n---");
134
+ parts.push('Call `mcp__contextstream__context(user_message="...")` for task-specific context.');
135
+ return parts.join("\n");
136
+ }
137
+ async function runSessionInitHook() {
138
+ if (!ENABLED) {
139
+ process.exit(0);
140
+ }
141
+ let inputData = "";
142
+ for await (const chunk of process.stdin) {
143
+ inputData += chunk;
144
+ }
145
+ if (!inputData.trim()) {
146
+ process.exit(0);
147
+ }
148
+ let input;
149
+ try {
150
+ input = JSON.parse(inputData);
151
+ } catch {
152
+ process.exit(0);
153
+ }
154
+ const cwd = input.cwd || process.cwd();
155
+ loadConfigFromMcpJson(cwd);
156
+ const context = await fetchSessionContext();
157
+ const formattedContext = formatContext(context);
158
+ console.log(
159
+ JSON.stringify({
160
+ hookSpecificOutput: {
161
+ hookEventName: "SessionStart",
162
+ additionalContext: formattedContext
163
+ }
164
+ })
165
+ );
166
+ process.exit(0);
167
+ }
168
+ var isDirectRun = process.argv[1]?.includes("session-init") || process.argv[2] === "session-init";
169
+ if (isDirectRun) {
170
+ runSessionInitHook().catch(() => process.exit(0));
171
+ }
172
+ export {
173
+ runSessionInitHook
174
+ };
@@ -1,17 +1,223 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hooks/user-prompt-submit.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
4
7
  var ENABLED = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
5
- var REMINDER = `[CONTEXTSTREAM - DO THIS FIRST]
6
- mcp__contextstream__context(user_message="<user's message>") - CALL THIS BEFORE ANY OTHER TOOL
8
+ var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
+ var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
+ var WORKSPACE_ID = null;
11
+ var PROJECT_ID = null;
12
+ var REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
7
13
 
8
- Why: Delivers task-specific rules, lessons from past mistakes, and relevant context.
9
- Skip it = repeat past mistakes, miss relevant decisions, fly blind.
14
+ 1. FIRST: Call mcp__contextstream__context(user_message="...") before ANY other tool
15
+ - Returns: dynamic rules, lessons from past mistakes, relevant context
16
+ - Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
10
17
 
11
- BEFORE Glob/Grep/Read/Search/Explore:
12
- mcp__contextstream__search(mode="hybrid", query="...") FIRST
13
- Local tools ONLY if search returns 0 results.
18
+ 2. FOR CODE SEARCH: Check index status, then search appropriately
19
+ \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
20
+ \u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="hybrid", query="...")
21
+ \u2705 IF NOT indexed OR stale: Use local tools (Glob/Grep/Read) directly
22
+ \u2705 IF search returns 0 results: Fallback to local tools (Glob/Grep/Read)
23
+
24
+ 3. WHEN LOCAL TOOLS (Glob/Grep/Read) ARE OK:
25
+ \u2705 Project is NOT indexed (index_status.indexed=false)
26
+ \u2705 Index is stale/outdated (>7 days old)
27
+ \u2705 ContextStream search returns 0 results or errors
28
+ \u2705 User explicitly requests local tools
29
+
30
+ 4. FOR PLANS & TASKS: Use ContextStream, not file-based plans
31
+ \u2705 Plans: mcp__contextstream__session(action="capture_plan", ...)
32
+ \u2705 Tasks: mcp__contextstream__memory(action="create_task", ...)
33
+ \u274C DO NOT use EnterPlanMode or write plans to markdown files
34
+
35
+ 5. CHECK THESE from context() response:
36
+ - Lessons: Past mistakes to avoid (shown as warnings)
37
+ - Reminders: Active reminders for this project
38
+ - Preferences: User's coding style and preferences
39
+ - Rules: Dynamic rules matched to current task
40
+
41
+ 6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
14
42
  [END]`;
43
+ var ENHANCED_REMINDER_HEADER = `[CONTEXTSTREAM - ENHANCED CONTEXT]
44
+
45
+ `;
46
+ function loadConfigFromMcpJson(cwd) {
47
+ let searchDir = path.resolve(cwd);
48
+ for (let i = 0; i < 5; i++) {
49
+ if (!API_KEY) {
50
+ const mcpPath = path.join(searchDir, ".mcp.json");
51
+ if (fs.existsSync(mcpPath)) {
52
+ try {
53
+ const content = fs.readFileSync(mcpPath, "utf-8");
54
+ const config = JSON.parse(content);
55
+ const csEnv = config.mcpServers?.contextstream?.env;
56
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
57
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
58
+ }
59
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
60
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
61
+ }
62
+ if (csEnv?.CONTEXTSTREAM_WORKSPACE_ID) {
63
+ WORKSPACE_ID = csEnv.CONTEXTSTREAM_WORKSPACE_ID;
64
+ }
65
+ } catch {
66
+ }
67
+ }
68
+ }
69
+ if (!WORKSPACE_ID || !PROJECT_ID) {
70
+ const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
71
+ if (fs.existsSync(csConfigPath)) {
72
+ try {
73
+ const content = fs.readFileSync(csConfigPath, "utf-8");
74
+ const csConfig = JSON.parse(content);
75
+ if (csConfig.workspace_id && !WORKSPACE_ID) {
76
+ WORKSPACE_ID = csConfig.workspace_id;
77
+ }
78
+ if (csConfig.project_id && !PROJECT_ID) {
79
+ PROJECT_ID = csConfig.project_id;
80
+ }
81
+ } catch {
82
+ }
83
+ }
84
+ }
85
+ const parentDir = path.dirname(searchDir);
86
+ if (parentDir === searchDir) break;
87
+ searchDir = parentDir;
88
+ }
89
+ if (!API_KEY) {
90
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
91
+ if (fs.existsSync(homeMcpPath)) {
92
+ try {
93
+ const content = fs.readFileSync(homeMcpPath, "utf-8");
94
+ const config = JSON.parse(content);
95
+ const csEnv = config.mcpServers?.contextstream?.env;
96
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
97
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
98
+ }
99
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
100
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ }
107
+ async function fetchSessionContext() {
108
+ if (!API_KEY) return null;
109
+ try {
110
+ const controller = new AbortController();
111
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
112
+ const url = new URL(`${API_URL}/api/v1/context`);
113
+ if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
114
+ if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
115
+ url.searchParams.set("include_lessons", "true");
116
+ url.searchParams.set("include_decisions", "true");
117
+ url.searchParams.set("include_plans", "true");
118
+ url.searchParams.set("include_reminders", "true");
119
+ url.searchParams.set("limit", "3");
120
+ const response = await fetch(url.toString(), {
121
+ method: "GET",
122
+ headers: {
123
+ "X-API-Key": API_KEY
124
+ },
125
+ signal: controller.signal
126
+ });
127
+ clearTimeout(timeoutId);
128
+ if (response.ok) {
129
+ return await response.json();
130
+ }
131
+ return null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ function buildEnhancedReminder(ctx, isNewSession2) {
137
+ const parts = [ENHANCED_REMINDER_HEADER];
138
+ if (isNewSession2) {
139
+ parts.push(`## \u{1F680} NEW SESSION DETECTED
140
+ 1. Call \`init(folder_path="...")\` - this triggers project indexing
141
+ 2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
142
+ 3. Then call \`context(user_message="...")\` for task-specific context
143
+ 4. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
144
+
145
+ `);
146
+ }
147
+ if (ctx?.lessons && ctx.lessons.length > 0) {
148
+ parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
149
+ for (const lesson of ctx.lessons.slice(0, 3)) {
150
+ parts.push(`- **${lesson.title}**: ${lesson.prevention}`);
151
+ }
152
+ parts.push("");
153
+ }
154
+ if (ctx?.active_plans && ctx.active_plans.length > 0) {
155
+ parts.push(`## \u{1F4CB} Active Plans`);
156
+ for (const plan of ctx.active_plans.slice(0, 3)) {
157
+ parts.push(`- ${plan.title} (${plan.status})`);
158
+ }
159
+ parts.push("");
160
+ }
161
+ if (ctx?.pending_tasks && ctx.pending_tasks.length > 0) {
162
+ parts.push(`## \u2705 Pending Tasks`);
163
+ for (const task of ctx.pending_tasks.slice(0, 5)) {
164
+ parts.push(`- ${task.title}`);
165
+ }
166
+ parts.push("");
167
+ }
168
+ if (ctx?.reminders && ctx.reminders.length > 0) {
169
+ parts.push(`## \u{1F514} Reminders`);
170
+ for (const reminder of ctx.reminders.slice(0, 3)) {
171
+ parts.push(`- ${reminder.title}`);
172
+ }
173
+ parts.push("");
174
+ }
175
+ parts.push("---\n");
176
+ parts.push(REMINDER);
177
+ parts.push(`
178
+
179
+ ---
180
+ ## \u{1F6A8} FILE INDEXING & SEARCH - CRITICAL (No PostToolUse Hook) \u{1F6A8}
181
+
182
+ **This editor does NOT have automatic file indexing after Edit/Write.**
183
+
184
+ ### \u26A0\uFE0F BEFORE ANY SEARCH - Check Index Status:
185
+ \`\`\`
186
+ project(action="index_status")
187
+ \`\`\`
188
+ Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
189
+
190
+ ### \u{1F50D} Search Decision Tree:
191
+
192
+ **IF indexed=true AND last_indexed_at is recent:**
193
+ \u2192 Use \`search(mode="hybrid", query="...")\`
194
+
195
+ **IF indexed=false OR last_indexed_at is stale (>7 days):**
196
+ \u2192 Use local tools (Glob/Grep/Read) directly
197
+ \u2192 OR run \`project(action="index")\` first, then search
198
+
199
+ **IF search returns 0 results or errors:**
200
+ \u2192 Fallback to local tools (Glob/Grep/Read)
201
+
202
+ ### \u2705 When Local Tools (Glob/Grep/Read) Are OK:
203
+ - Project is NOT indexed
204
+ - Index is stale/outdated (>7 days)
205
+ - ContextStream search returns 0 results
206
+ - ContextStream returns errors
207
+ - User explicitly requests local tools
208
+
209
+ ### On Session Start:
210
+ 1. Call \`init(folder_path="...")\` - triggers initial indexing
211
+ 2. Check \`project(action="index_status")\` before searching
212
+ 3. If not indexed: use local tools OR wait for indexing
213
+
214
+ ### After File Changes (Edit/Write/Create):
215
+ Files are NOT auto-indexed. You MUST:
216
+ 1. After significant edits: \`project(action="index")\`
217
+ 2. For single file: \`project(action="ingest_local", path="<file>")\`
218
+ 3. Then search will find your changes`);
219
+ return parts.join("\n");
220
+ }
15
221
  function detectEditorFormat(input) {
16
222
  if (input.hookName !== void 0) {
17
223
  return "cline";
@@ -19,8 +225,23 @@ function detectEditorFormat(input) {
19
225
  if (input.hook_event_name === "beforeSubmitPrompt") {
20
226
  return "cursor";
21
227
  }
228
+ if (input.hook_event_name === "beforeAgentAction" || input.hook_event_name === "onPromptSubmit") {
229
+ return "antigravity";
230
+ }
22
231
  return "claude";
23
232
  }
233
+ function isNewSession(input, editorFormat) {
234
+ if (editorFormat === "claude" && input.session?.messages) {
235
+ return input.session.messages.length <= 1;
236
+ }
237
+ if (editorFormat === "cursor" && input.history !== void 0) {
238
+ return input.history.length === 0;
239
+ }
240
+ if (editorFormat === "antigravity" && input.history !== void 0) {
241
+ return input.history.length === 0;
242
+ }
243
+ return false;
244
+ }
24
245
  async function runUserPromptSubmitHook() {
25
246
  if (!ENABLED) {
26
247
  process.exit(0);
@@ -39,6 +260,7 @@ async function runUserPromptSubmitHook() {
39
260
  process.exit(0);
40
261
  }
41
262
  const editorFormat = detectEditorFormat(input);
263
+ const cwd = input.cwd || process.cwd();
42
264
  if (editorFormat === "claude") {
43
265
  console.log(
44
266
  JSON.stringify({
@@ -49,17 +271,36 @@ async function runUserPromptSubmitHook() {
49
271
  })
50
272
  );
51
273
  } else if (editorFormat === "cline") {
274
+ loadConfigFromMcpJson(cwd);
275
+ const newSession = isNewSession(input, editorFormat);
276
+ const ctx = await fetchSessionContext();
277
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession);
52
278
  console.log(
53
279
  JSON.stringify({
54
280
  cancel: false,
55
- contextModification: REMINDER
281
+ contextModification: enhancedReminder
56
282
  })
57
283
  );
58
284
  } else if (editorFormat === "cursor") {
285
+ loadConfigFromMcpJson(cwd);
286
+ const newSession = isNewSession(input, editorFormat);
287
+ const ctx = await fetchSessionContext();
288
+ 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.`;
59
289
  console.log(
60
290
  JSON.stringify({
61
291
  continue: true,
62
- user_message: "[CONTEXTSTREAM] Search with mcp__contextstream__search before using Glob/Grep/Read"
292
+ user_message: cursorReminder
293
+ })
294
+ );
295
+ } else if (editorFormat === "antigravity") {
296
+ loadConfigFromMcpJson(cwd);
297
+ const newSession = isNewSession(input, editorFormat);
298
+ const ctx = await fetchSessionContext();
299
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession);
300
+ console.log(
301
+ JSON.stringify({
302
+ cancel: false,
303
+ contextModification: enhancedReminder
63
304
  })
64
305
  );
65
306
  }