@contextstream/mcp-server 0.4.63 → 0.4.65

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.
@@ -373,7 +373,7 @@ var CONTEXTSTREAM_RULES_BOOTSTRAP = `
373
373
  |---------|----------|
374
374
  | **1st message** | \`init()\` \u2192 \`context(user_message="...")\` |
375
375
  | **Subsequent messages (default)** | \`context(user_message="...")\` FIRST (narrow read-only bypass when context is fresh and no state-changing tool has run) |
376
- | **Before file search** | \`search(mode="auto")\` BEFORE Glob/Grep/Read |
376
+ | **Before file search** | \`search(mode="auto")\` BEFORE Glob/Grep/Read/Explore/Task/EnterPlanMode |
377
377
  </contextstream_rules>
378
378
 
379
379
  **Why?** \`context()\` delivers task-specific rules, lessons from past mistakes, and relevant decisions. Skip it = fly blind.
@@ -394,7 +394,7 @@ var CONTEXTSTREAM_RULES_FULL = `
394
394
  | **First message in session** | \`init()\` \u2192 \`context(user_message="<msg>")\` BEFORE any other tool |
395
395
  | **Subsequent messages (default)** | \`context(user_message="<msg>")\` FIRST, then other tools |
396
396
  | **Narrow bypass** | Immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run |
397
- | **Before Glob/Grep/Read/Search** | \`search(mode="auto", query="...")\` FIRST |
397
+ | **Before Glob/Grep/Read/Search/Explore/Task/EnterPlanMode** | \`search(mode="auto", query="...")\` FIRST |
398
398
  </contextstream_rules>
399
399
 
400
400
  Use \`context()\` by default to get task-specific rules, lessons from past mistakes, and relevant decisions.
@@ -468,7 +468,7 @@ Set environment variable: \`CONTEXTSTREAM_HOOK_ENABLED=false\`
468
468
 
469
469
  ## \u{1F6A8} CRITICAL RULE #1 - CONTEXTSTREAM SEARCH FIRST \u{1F6A8}
470
470
 
471
- **BEFORE using Glob, Grep, Search, Read (for discovery), Explore, or ANY local file scanning:**
471
+ **BEFORE using Glob, Grep, Search, Read (for discovery), Explore, Task(Explore), EnterPlanMode, or ANY local file scanning:**
472
472
  \`\`\`
473
473
  STOP \u2192 Call search(mode="auto", query="...") FIRST
474
474
  \`\`\`
@@ -480,7 +480,8 @@ STOP \u2192 Call search(mode="auto", query="...") FIRST
480
480
  - \`Glob("**/*.ts")\` \u2192 Use \`search(mode="pattern", query="*.ts")\` instead
481
481
  - \`Grep("functionName")\` \u2192 Use \`search(mode="keyword", query="functionName")\` instead
482
482
  - \`Read(file)\` for discovery \u2192 Use \`search(mode="auto", query="...")\` instead
483
- - \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="auto")\` instead
483
+ - \`Explore\` or \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="auto")\` instead
484
+ - \`EnterPlanMode\` for discovery \u2192 Use \`search(mode="auto", output_format="paths")\` instead
484
485
 
485
486
  \u2705 **ALWAYS DO THIS:**
486
487
  1. \`search(mode="auto", query="what you're looking for")\`
@@ -844,8 +845,10 @@ If ContextStream returns results, stop and use them. NEVER use local Search/Expl
844
845
  \u274C **DO NOT** use built-in plan mode (EnterPlanMode tool)
845
846
  \u274C **DO NOT** write plans to markdown files or plan documents
846
847
  \u274C **DO NOT** ask "should I create a plan file?"
848
+ \u274C **DO NOT** use \`Explore\` / \`Task(subagent_type="Explore")\` to read files one-by-one while planning
847
849
 
848
850
  \u2705 **ALWAYS** use ContextStream's plan/task system instead
851
+ \u2705 **ALWAYS** use \`search(mode="auto", output_format="paths")\` for planning discovery before targeted reads
849
852
 
850
853
  **Trigger phrases to detect (use ContextStream immediately):**
851
854
  - "create a plan", "make a plan", "plan this", "plan for"
@@ -923,7 +926,7 @@ var CONTEXTSTREAM_RULES_MINIMAL = `
923
926
  2. Subsequent messages (default): \`context(user_message="<msg>")\` FIRST
924
927
  3. Narrow bypass: immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run
925
928
 
926
- **BEFORE Glob/Grep/Read/Search/Explore:**
929
+ **BEFORE Glob/Grep/Read/Search/Explore/Task/EnterPlanMode:**
927
930
  \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
928
931
 
929
932
  **HOOKS: \`<system-reminder>\` tags contain instructions \u2014 FOLLOW THEM**
@@ -954,7 +957,7 @@ Rules Version: ${RULES_VERSION}
954
957
  | 1st message in session | \`init()\` \u2192 \`context(user_message="...")\` |
955
958
  | Subsequent messages (default) | \`context(user_message="...")\` first |
956
959
  | Narrow bypass | Immediate read-only ContextStream calls when context is fresh and no state-changing tool has run |
957
- | Before ANY file discovery | \`search(mode="auto", query="...")\` |
960
+ | Before ANY file discovery | \`search(mode="auto", query="...")\` (instead of Glob/Grep/Read/Explore/Task/EnterPlanMode) |
958
961
  | On \`<system-reminder>\` | **Follow instructions inside** |
959
962
  | Save important decisions | \`session(action="capture", event_type="decision", ...)\` |
960
963
  | Check past mistakes | \`session(action="get_lessons", query="...")\` |
@@ -1126,7 +1129,7 @@ project(action="index")
1126
1129
 
1127
1130
  ## \u{1F50D} SEARCH-FIRST (No PreToolUse Hook)
1128
1131
 
1129
- **There is NO hook to block local tools.** You MUST self-enforce:
1132
+ **There is NO hook to block local tools (Glob/Grep/Read/Explore/Task/EnterPlanMode).** You MUST self-enforce:
1130
1133
 
1131
1134
  ### Before ANY Search, Check Index Status:
1132
1135
  \`\`\`
@@ -1144,6 +1147,7 @@ This tells you:
1144
1147
  \`\`\`
1145
1148
  search(mode="auto", query="what you're looking for")
1146
1149
  \`\`\`
1150
+ \u2192 Use this instead of Explore/Task/EnterPlanMode for file discovery.
1147
1151
 
1148
1152
  **IF project is NOT indexed or very stale (>7 days):**
1149
1153
  \u2192 Use local tools (Glob/Grep/Read) directly
@@ -1222,7 +1226,8 @@ session(action="capture_plan", title="...", steps=[...])
1222
1226
  memory(action="create_task", title="...", plan_id="...")
1223
1227
  \`\`\`
1224
1228
 
1225
- \u274C DO NOT use built-in plan mode or write plans to markdown files.
1229
+ \u274C DO NOT use built-in plan mode (\`EnterPlanMode\`) or \`Task(subagent_type="Explore")\` for file-by-file scans.
1230
+ \u2705 For planning discovery, use \`search(mode="auto", query="...", output_format="paths")\` then read only narrowed files.
1226
1231
 
1227
1232
  ---
1228
1233
 
@@ -1319,6 +1324,24 @@ ${rules.split("\n").map((line) => " " + line).join("\n")}
1319
1324
  description: "Google Antigravity AI rules",
1320
1325
  build: (rules) => `# Antigravity Agent Rules
1321
1326
  ${rules}
1327
+ `
1328
+ },
1329
+ copilot: {
1330
+ filename: ".github/copilot-instructions.md",
1331
+ description: "VS Code GitHub Copilot instructions",
1332
+ build: (rules) => `## ContextStream MCP Integration
1333
+
1334
+ This project uses [ContextStream](https://contextstream.io) for persistent AI memory across sessions. Use the \`contextstream-workflow\` skill for detailed examples and reference material.
1335
+
1336
+ ${rules}
1337
+
1338
+ ### VS Code Copilot Notes
1339
+
1340
+ - Keep this file concise; put detailed workflows in \`.github/skills/contextstream-workflow/SKILL.md\`
1341
+ - Use ContextStream plans/tasks as the persistent record of work
1342
+ - Before code discovery, use \`search(mode="auto", query="...")\`
1343
+
1344
+ Full docs: https://contextstream.io/docs/mcp/tools
1322
1345
  `
1323
1346
  }
1324
1347
  };
@@ -1334,7 +1357,7 @@ function generateRuleContent(editor, options) {
1334
1357
  const mode = options?.mode || "bootstrap";
1335
1358
  const rules = mode === "full" ? CONTEXTSTREAM_RULES_FULL : mode === "minimal" ? CONTEXTSTREAM_RULES_MINIMAL : mode === "bootstrap" ? CONTEXTSTREAM_RULES_BOOTSTRAP : CONTEXTSTREAM_RULES_DYNAMIC;
1336
1359
  let content = template.build(rules);
1337
- if (options?.workspaceName || options?.projectName) {
1360
+ if (editor.toLowerCase() !== "copilot" && (options?.workspaceName || options?.projectName)) {
1338
1361
  const header = `
1339
1362
  # Workspace: ${options.workspaceName || "Unknown"}
1340
1363
  ${options.projectName ? `# Project: ${options.projectName}` : ""}
@@ -1685,10 +1708,7 @@ async function runSessionInitHook() {
1685
1708
  });
1686
1709
  console.log(
1687
1710
  JSON.stringify({
1688
- hookSpecificOutput: {
1689
- hookEventName: "SessionStart",
1690
- additionalContext: formattedContext
1691
- }
1711
+ additionalContext: formattedContext
1692
1712
  })
1693
1713
  );
1694
1714
  process.exit(0);
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/common.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var DEFAULT_API_URL = "https://api.contextstream.io";
8
+ var HOOK_SPECIFIC_OUTPUT_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse"]);
9
+ function readHookInput() {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(0, "utf8"));
12
+ } catch {
13
+ return {};
14
+ }
15
+ }
16
+ function writeHookOutput(output) {
17
+ const eventName = output?.hookEventName || (typeof process.env.HOOK_EVENT_NAME === "string" ? process.env.HOOK_EVENT_NAME.trim() : "");
18
+ const canUseHookSpecificOutput = HOOK_SPECIFIC_OUTPUT_EVENTS.has(eventName);
19
+ const payload = output && (output.additionalContext || output.blocked || output.reason) ? {
20
+ hookSpecificOutput: output.additionalContext && canUseHookSpecificOutput ? {
21
+ hookEventName: eventName,
22
+ additionalContext: output.additionalContext
23
+ } : void 0,
24
+ additionalContext: output.additionalContext,
25
+ blocked: output.blocked,
26
+ reason: output.reason
27
+ } : {};
28
+ console.log(JSON.stringify(payload));
29
+ }
30
+ function extractCwd(input) {
31
+ const cwd = typeof input.cwd === "string" && input.cwd.trim() ? input.cwd.trim() : process.cwd();
32
+ return cwd;
33
+ }
34
+ function loadHookConfig(cwd) {
35
+ let apiUrl = process.env.CONTEXTSTREAM_API_URL || DEFAULT_API_URL;
36
+ let apiKey = process.env.CONTEXTSTREAM_API_KEY || "";
37
+ let jwt = process.env.CONTEXTSTREAM_JWT || "";
38
+ let workspaceId = process.env.CONTEXTSTREAM_WORKSPACE_ID || null;
39
+ let projectId = process.env.CONTEXTSTREAM_PROJECT_ID || null;
40
+ let searchDir = path.resolve(cwd);
41
+ for (let i = 0; i < 6; i++) {
42
+ if (!apiKey && !jwt) {
43
+ const mcpPath = path.join(searchDir, ".mcp.json");
44
+ if (fs.existsSync(mcpPath)) {
45
+ try {
46
+ const config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
47
+ const env = config.mcpServers?.contextstream?.env;
48
+ if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
49
+ if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
50
+ if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
51
+ if (env?.CONTEXTSTREAM_WORKSPACE_ID && !workspaceId) workspaceId = env.CONTEXTSTREAM_WORKSPACE_ID;
52
+ if (env?.CONTEXTSTREAM_PROJECT_ID && !projectId) projectId = env.CONTEXTSTREAM_PROJECT_ID;
53
+ } catch {
54
+ }
55
+ }
56
+ }
57
+ if (!workspaceId || !projectId) {
58
+ const localConfigPath = path.join(searchDir, ".contextstream", "config.json");
59
+ if (fs.existsSync(localConfigPath)) {
60
+ try {
61
+ const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf8"));
62
+ if (localConfig.workspace_id && !workspaceId) workspaceId = localConfig.workspace_id;
63
+ if (localConfig.project_id && !projectId) projectId = localConfig.project_id;
64
+ } catch {
65
+ }
66
+ }
67
+ }
68
+ const parentDir = path.dirname(searchDir);
69
+ if (parentDir === searchDir) break;
70
+ searchDir = parentDir;
71
+ }
72
+ if (!apiKey && !jwt) {
73
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
74
+ if (fs.existsSync(homeMcpPath)) {
75
+ try {
76
+ const config = JSON.parse(fs.readFileSync(homeMcpPath, "utf8"));
77
+ const env = config.mcpServers?.contextstream?.env;
78
+ if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
79
+ if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
80
+ if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
81
+ } catch {
82
+ }
83
+ }
84
+ }
85
+ return { apiUrl, apiKey, jwt, workspaceId, projectId };
86
+ }
87
+ function isConfigured(config) {
88
+ return Boolean(config.apiKey || config.jwt);
89
+ }
90
+ function authHeaders(config) {
91
+ if (config.apiKey) {
92
+ return { "X-API-Key": config.apiKey };
93
+ }
94
+ if (config.jwt) {
95
+ return { Authorization: `Bearer ${config.jwt}` };
96
+ }
97
+ return {};
98
+ }
99
+ async function apiRequest(config, apiPath, init = {}) {
100
+ const response = await fetch(`${config.apiUrl}${apiPath}`, {
101
+ method: init.method || "GET",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ ...authHeaders(config)
105
+ },
106
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
107
+ });
108
+ if (!response.ok) {
109
+ throw new Error(`${response.status} ${response.statusText}`);
110
+ }
111
+ const json = await response.json();
112
+ if (json && typeof json === "object" && "data" in json) {
113
+ return json.data;
114
+ }
115
+ return json;
116
+ }
117
+ async function postMemoryEvent(config, title, content, tags, eventType = "operation") {
118
+ if (!isConfigured(config) || !config.workspaceId) return;
119
+ await apiRequest(config, "/memory/events", {
120
+ method: "POST",
121
+ body: {
122
+ workspace_id: config.workspaceId,
123
+ project_id: config.projectId || void 0,
124
+ event_type: eventType,
125
+ title,
126
+ content: typeof content === "string" ? content : JSON.stringify(content),
127
+ metadata: {
128
+ tags,
129
+ source: "mcp_hook",
130
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
131
+ }
132
+ }
133
+ });
134
+ }
135
+
136
+ // src/hooks/stop.ts
137
+ async function runStopHook() {
138
+ if (process.env.CONTEXTSTREAM_STOP_ENABLED === "false") {
139
+ writeHookOutput();
140
+ return;
141
+ }
142
+ const input = readHookInput();
143
+ const cwd = extractCwd(input);
144
+ const config = loadHookConfig(cwd);
145
+ if (isConfigured(config)) {
146
+ const sessionId = typeof input.session_id === "string" && input.session_id || "unknown";
147
+ const reason = typeof input.reason === "string" && input.reason || typeof input.stop_reason === "string" && input.stop_reason || "response_complete";
148
+ await postMemoryEvent(
149
+ config,
150
+ "Stop checkpoint",
151
+ {
152
+ session_id: sessionId,
153
+ reason,
154
+ hook: "stop",
155
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
156
+ tool_name: typeof input.tool_name === "string" ? input.tool_name : null,
157
+ model: typeof input.model === "string" ? input.model : null
158
+ },
159
+ ["hook", "stop", "checkpoint"]
160
+ ).catch(() => {
161
+ });
162
+ }
163
+ writeHookOutput();
164
+ }
165
+ var isDirectRun = process.argv[1]?.includes("stop") || process.argv[2] === "stop";
166
+ if (isDirectRun) {
167
+ runStopHook().catch(() => process.exit(0));
168
+ }
169
+ export {
170
+ runStopHook
171
+ };
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/common.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var DEFAULT_API_URL = "https://api.contextstream.io";
8
+ var HOOK_SPECIFIC_OUTPUT_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse"]);
9
+ function readHookInput() {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(0, "utf8"));
12
+ } catch {
13
+ return {};
14
+ }
15
+ }
16
+ function writeHookOutput(output) {
17
+ const eventName = output?.hookEventName || (typeof process.env.HOOK_EVENT_NAME === "string" ? process.env.HOOK_EVENT_NAME.trim() : "");
18
+ const canUseHookSpecificOutput = HOOK_SPECIFIC_OUTPUT_EVENTS.has(eventName);
19
+ const payload = output && (output.additionalContext || output.blocked || output.reason) ? {
20
+ hookSpecificOutput: output.additionalContext && canUseHookSpecificOutput ? {
21
+ hookEventName: eventName,
22
+ additionalContext: output.additionalContext
23
+ } : void 0,
24
+ additionalContext: output.additionalContext,
25
+ blocked: output.blocked,
26
+ reason: output.reason
27
+ } : {};
28
+ console.log(JSON.stringify(payload));
29
+ }
30
+ function extractCwd(input) {
31
+ const cwd = typeof input.cwd === "string" && input.cwd.trim() ? input.cwd.trim() : process.cwd();
32
+ return cwd;
33
+ }
34
+ function loadHookConfig(cwd) {
35
+ let apiUrl = process.env.CONTEXTSTREAM_API_URL || DEFAULT_API_URL;
36
+ let apiKey = process.env.CONTEXTSTREAM_API_KEY || "";
37
+ let jwt = process.env.CONTEXTSTREAM_JWT || "";
38
+ let workspaceId = process.env.CONTEXTSTREAM_WORKSPACE_ID || null;
39
+ let projectId = process.env.CONTEXTSTREAM_PROJECT_ID || null;
40
+ let searchDir = path.resolve(cwd);
41
+ for (let i = 0; i < 6; i++) {
42
+ if (!apiKey && !jwt) {
43
+ const mcpPath = path.join(searchDir, ".mcp.json");
44
+ if (fs.existsSync(mcpPath)) {
45
+ try {
46
+ const config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
47
+ const env = config.mcpServers?.contextstream?.env;
48
+ if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
49
+ if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
50
+ if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
51
+ if (env?.CONTEXTSTREAM_WORKSPACE_ID && !workspaceId) workspaceId = env.CONTEXTSTREAM_WORKSPACE_ID;
52
+ if (env?.CONTEXTSTREAM_PROJECT_ID && !projectId) projectId = env.CONTEXTSTREAM_PROJECT_ID;
53
+ } catch {
54
+ }
55
+ }
56
+ }
57
+ if (!workspaceId || !projectId) {
58
+ const localConfigPath = path.join(searchDir, ".contextstream", "config.json");
59
+ if (fs.existsSync(localConfigPath)) {
60
+ try {
61
+ const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf8"));
62
+ if (localConfig.workspace_id && !workspaceId) workspaceId = localConfig.workspace_id;
63
+ if (localConfig.project_id && !projectId) projectId = localConfig.project_id;
64
+ } catch {
65
+ }
66
+ }
67
+ }
68
+ const parentDir = path.dirname(searchDir);
69
+ if (parentDir === searchDir) break;
70
+ searchDir = parentDir;
71
+ }
72
+ if (!apiKey && !jwt) {
73
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
74
+ if (fs.existsSync(homeMcpPath)) {
75
+ try {
76
+ const config = JSON.parse(fs.readFileSync(homeMcpPath, "utf8"));
77
+ const env = config.mcpServers?.contextstream?.env;
78
+ if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
79
+ if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
80
+ if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
81
+ } catch {
82
+ }
83
+ }
84
+ }
85
+ return { apiUrl, apiKey, jwt, workspaceId, projectId };
86
+ }
87
+ function isConfigured(config) {
88
+ return Boolean(config.apiKey || config.jwt);
89
+ }
90
+ function authHeaders(config) {
91
+ if (config.apiKey) {
92
+ return { "X-API-Key": config.apiKey };
93
+ }
94
+ if (config.jwt) {
95
+ return { Authorization: `Bearer ${config.jwt}` };
96
+ }
97
+ return {};
98
+ }
99
+ async function apiRequest(config, apiPath, init = {}) {
100
+ const response = await fetch(`${config.apiUrl}${apiPath}`, {
101
+ method: init.method || "GET",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ ...authHeaders(config)
105
+ },
106
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
107
+ });
108
+ if (!response.ok) {
109
+ throw new Error(`${response.status} ${response.statusText}`);
110
+ }
111
+ const json = await response.json();
112
+ if (json && typeof json === "object" && "data" in json) {
113
+ return json.data;
114
+ }
115
+ return json;
116
+ }
117
+ async function fetchFastContext(config, body) {
118
+ if (!isConfigured(config)) return null;
119
+ try {
120
+ const result = await apiRequest(config, "/context/hook", {
121
+ method: "POST",
122
+ body: {
123
+ workspace_id: config.workspaceId || void 0,
124
+ project_id: config.projectId || void 0,
125
+ ...body
126
+ }
127
+ });
128
+ if (typeof result?.context === "string") return result.context;
129
+ if (typeof result?.data?.context === "string") return result.data.context;
130
+ return null;
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ // src/hooks/subagent-start.ts
137
+ var SEARCH_PROTOCOL = `[CONTEXTSTREAM SEARCH]
138
+ When searching code, prefer ContextStream search tools first:
139
+ - mcp__contextstream__search(mode="auto", query="...")
140
+ - mcp__contextstream__search(mode="keyword", query="...", include_content=true)
141
+ - mcp__contextstream__graph(...) for dependency analysis
142
+ Fall back to local discovery tools only if ContextStream search returns no results.`;
143
+ var EXPLORE_SEARCH_FIRST = `[CRITICAL: SEARCH-FIRST PROTOCOL]
144
+ You MUST call mcp__contextstream__search(mode="auto", query="...") BEFORE reading source files.
145
+ Read only the small set of files and line ranges identified by search.`;
146
+ var PLAN_SEARCH_FIRST = `[PLAN MODE: SEARCH-FIRST]
147
+ Plan mode does NOT justify file-by-file repository scans.
148
+ Start with ContextStream search, then read only the narrowed files and ranges.`;
149
+ function fallbackContext() {
150
+ return `${SEARCH_PROTOCOL}
151
+
152
+ [CONTEXTSTREAM] Call mcp__contextstream__context(user_message="...") for task-specific context.`;
153
+ }
154
+ async function runSubagentStartHook() {
155
+ if (process.env.CONTEXTSTREAM_SUBAGENT_CONTEXT_ENABLED === "false") {
156
+ writeHookOutput();
157
+ return;
158
+ }
159
+ const input = readHookInput();
160
+ const cwd = extractCwd(input);
161
+ const config = loadHookConfig(cwd);
162
+ const agentType = typeof input.agent_type === "string" && input.agent_type || typeof input.subagent_type === "string" && input.subagent_type || "unknown";
163
+ if (!isConfigured(config)) {
164
+ writeHookOutput({ additionalContext: fallbackContext(), hookEventName: "SubagentStart" });
165
+ return;
166
+ }
167
+ const context = await fetchFastContext(config, {
168
+ session_id: typeof input.session_id === "string" && input.session_id || typeof input.sessionId === "string" && input.sessionId || void 0,
169
+ user_message: `SubagentStart:${agentType}`
170
+ });
171
+ const parts = [];
172
+ if (context) parts.push(context);
173
+ if (agentType.toLowerCase() === "explore") {
174
+ parts.push(EXPLORE_SEARCH_FIRST);
175
+ } else if (agentType.toLowerCase() === "plan") {
176
+ parts.push(PLAN_SEARCH_FIRST);
177
+ parts.push(
178
+ `[CONTEXTSTREAM PLAN]
179
+ Save plans to ContextStream with mcp__contextstream__session(action="capture_plan", ...).
180
+ Create tasks with mcp__contextstream__memory(action="create_task", ...).`
181
+ );
182
+ }
183
+ parts.push(SEARCH_PROTOCOL);
184
+ writeHookOutput({
185
+ additionalContext: parts.filter(Boolean).join("\n\n") || fallbackContext(),
186
+ hookEventName: "SubagentStart"
187
+ });
188
+ }
189
+ var isDirectRun = process.argv[1]?.includes("subagent-start") || process.argv[2] === "subagent-start";
190
+ if (isDirectRun) {
191
+ runSubagentStartHook().catch(() => process.exit(0));
192
+ }
193
+ export {
194
+ runSubagentStartHook
195
+ };