@contextstream/mcp-server 0.4.46 → 0.4.47

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/index.js CHANGED
@@ -38,181 +38,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  mod
39
39
  ));
40
40
 
41
- // src/version.ts
42
- import { createRequire } from "module";
43
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
44
- import { homedir } from "os";
45
- import { join } from "path";
46
- function getVersion() {
47
- try {
48
- const require2 = createRequire(import.meta.url);
49
- const pkg = require2("../package.json");
50
- const version = pkg?.version;
51
- if (typeof version === "string" && version.trim()) return version.trim();
52
- } catch {
53
- }
54
- return "unknown";
55
- }
56
- function compareVersions(v1, v2) {
57
- const parts1 = v1.split(".").map(Number);
58
- const parts2 = v2.split(".").map(Number);
59
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
60
- const p1 = parts1[i] ?? 0;
61
- const p2 = parts2[i] ?? 0;
62
- if (p1 < p2) return -1;
63
- if (p1 > p2) return 1;
64
- }
65
- return 0;
66
- }
67
- function getCacheFilePath() {
68
- return join(homedir(), ".contextstream", "version-cache.json");
69
- }
70
- function readCache() {
71
- try {
72
- const cacheFile = getCacheFilePath();
73
- if (!existsSync(cacheFile)) return null;
74
- const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
75
- if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
76
- return data;
77
- } catch {
78
- return null;
79
- }
80
- }
81
- function writeCache(latestVersion) {
82
- try {
83
- const configDir = join(homedir(), ".contextstream");
84
- if (!existsSync(configDir)) {
85
- mkdirSync(configDir, { recursive: true });
86
- }
87
- const cacheFile = getCacheFilePath();
88
- writeFileSync(
89
- cacheFile,
90
- JSON.stringify({
91
- latestVersion,
92
- checkedAt: Date.now()
93
- })
94
- );
95
- } catch {
96
- }
97
- }
98
- async function fetchLatestVersion() {
99
- try {
100
- const controller = new AbortController();
101
- const timeout = setTimeout(() => controller.abort(), 5e3);
102
- const response = await fetch(NPM_LATEST_URL, {
103
- signal: controller.signal,
104
- headers: { Accept: "application/json" }
105
- });
106
- clearTimeout(timeout);
107
- if (!response.ok) return null;
108
- const data = await response.json();
109
- return typeof data.version === "string" ? data.version : null;
110
- } catch {
111
- return null;
112
- }
113
- }
114
- async function resolveLatestVersion() {
115
- const cached = readCache();
116
- if (cached) return cached.latestVersion;
117
- if (!latestVersionPromise) {
118
- latestVersionPromise = fetchLatestVersion().finally(() => {
119
- latestVersionPromise = null;
120
- });
121
- }
122
- const latestVersion = await latestVersionPromise;
123
- if (latestVersion) {
124
- writeCache(latestVersion);
125
- }
126
- return latestVersion;
127
- }
128
- async function checkForUpdates() {
129
- const notice = await getUpdateNotice();
130
- if (notice?.behind) {
131
- showUpdateWarning(notice.current, notice.latest);
132
- }
133
- }
134
- function showUpdateWarning(currentVersion, latestVersion) {
135
- console.error("");
136
- console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
137
- console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
138
- console.error("");
139
- console.error(` Run: ${UPGRADE_COMMAND}`);
140
- console.error("");
141
- console.error(" Then restart your AI tool to use the new version.");
142
- console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
143
- console.error("");
144
- }
145
- async function getUpdateNotice() {
146
- const currentVersion = VERSION;
147
- if (currentVersion === "unknown") return null;
148
- try {
149
- const latestVersion = await resolveLatestVersion();
150
- if (!latestVersion) return null;
151
- if (compareVersions(currentVersion, latestVersion) < 0) {
152
- return {
153
- current: currentVersion,
154
- latest: latestVersion,
155
- behind: true,
156
- upgrade_command: UPGRADE_COMMAND
157
- };
158
- }
159
- } catch {
160
- }
161
- return null;
162
- }
163
- function getVersionsBehind(current, latest) {
164
- try {
165
- const currentParts = current.split(".").map(Number);
166
- const latestParts = latest.split(".").map(Number);
167
- if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
168
- return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
169
- }
170
- const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
171
- const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
172
- if (minorDiff > 0) {
173
- return minorDiff;
174
- } else if (minorDiff === 0 && patchDiff > 0) {
175
- return 1;
176
- }
177
- return 0;
178
- } catch {
179
- return 0;
180
- }
181
- }
182
- function getVersionWarning(notice) {
183
- if (!notice?.behind) return null;
184
- const versionsBehind = getVersionsBehind(notice.current, notice.latest);
185
- if (versionsBehind >= 3) {
186
- return `[VERSION_CRITICAL] MCP server is ${versionsBehind} versions behind (${notice.current} \u2192 ${notice.latest}). Update immediately: ${notice.upgrade_command}`;
187
- } else if (versionsBehind >= 1) {
188
- return `[VERSION_NOTICE] MCP server update available (${notice.current} \u2192 ${notice.latest}). Run: ${notice.upgrade_command}`;
189
- }
190
- return null;
191
- }
192
- function getVersionInstructions(notice) {
193
- if (!notice?.behind) return null;
194
- const versionsBehind = getVersionsBehind(notice.current, notice.latest);
195
- if (versionsBehind >= 5) {
196
- return `\u26A0\uFE0F CRITICAL: User's MCP is ${versionsBehind} versions behind. Mention update in EVERY response until updated.`;
197
- } else if (versionsBehind >= 3) {
198
- return `\u26A0\uFE0F User's MCP is ${versionsBehind} versions behind. Remind about update every 2-3 messages.`;
199
- } else if (versionsBehind >= 1) {
200
- return `Note: MCP update available (${notice.current}\u2192${notice.latest}). Mention once at session start.`;
201
- }
202
- return null;
203
- }
204
- var UPGRADE_COMMAND, NPM_LATEST_URL, VERSION, CACHE_TTL_MS, latestVersionPromise;
205
- var init_version = __esm({
206
- "src/version.ts"() {
207
- "use strict";
208
- UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
209
- NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
210
- VERSION = getVersion();
211
- CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
212
- latestVersionPromise = null;
213
- }
214
- });
215
-
216
41
  // node_modules/ignore/index.js
217
42
  var require_ignore = __commonJS({
218
43
  "node_modules/ignore/index.js"(exports, module) {
@@ -670,776 +495,1489 @@ var require_ignore = __commonJS({
670
495
  }
671
496
  });
672
497
 
673
- // src/rules-templates.ts
674
- var rules_templates_exports = {};
675
- __export(rules_templates_exports, {
676
- RULES_VERSION: () => RULES_VERSION,
677
- TEMPLATES: () => TEMPLATES,
678
- generateAllRuleFiles: () => generateAllRuleFiles,
679
- generateRuleContent: () => generateRuleContent,
680
- getAvailableEditors: () => getAvailableEditors,
681
- getTemplate: () => getTemplate
498
+ // src/hooks-config.ts
499
+ var hooks_config_exports = {};
500
+ __export(hooks_config_exports, {
501
+ CLINE_POSTTOOLUSE_HOOK_SCRIPT: () => CLINE_POSTTOOLUSE_HOOK_SCRIPT,
502
+ CLINE_PRETOOLUSE_HOOK_SCRIPT: () => CLINE_PRETOOLUSE_HOOK_SCRIPT,
503
+ CLINE_USER_PROMPT_HOOK_SCRIPT: () => CLINE_USER_PROMPT_HOOK_SCRIPT,
504
+ CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT: () => CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT,
505
+ CURSOR_PRETOOLUSE_HOOK_SCRIPT: () => CURSOR_PRETOOLUSE_HOOK_SCRIPT,
506
+ MEDIA_AWARE_HOOK_SCRIPT: () => MEDIA_AWARE_HOOK_SCRIPT,
507
+ PRECOMPACT_HOOK_SCRIPT: () => PRECOMPACT_HOOK_SCRIPT,
508
+ PRETOOLUSE_HOOK_SCRIPT: () => PRETOOLUSE_HOOK_SCRIPT,
509
+ USER_PROMPT_HOOK_SCRIPT: () => USER_PROMPT_HOOK_SCRIPT,
510
+ buildHooksConfig: () => buildHooksConfig,
511
+ generateAllHooksDocumentation: () => generateAllHooksDocumentation,
512
+ generateHooksDocumentation: () => generateHooksDocumentation,
513
+ getClaudeSettingsPath: () => getClaudeSettingsPath,
514
+ getClineHooksDir: () => getClineHooksDir,
515
+ getCursorHooksConfigPath: () => getCursorHooksConfigPath,
516
+ getCursorHooksDir: () => getCursorHooksDir,
517
+ getHooksDir: () => getHooksDir,
518
+ getIndexStatusPath: () => getIndexStatusPath,
519
+ getKiloCodeHooksDir: () => getKiloCodeHooksDir,
520
+ getRooCodeHooksDir: () => getRooCodeHooksDir,
521
+ installAllEditorHooks: () => installAllEditorHooks,
522
+ installClaudeCodeHooks: () => installClaudeCodeHooks,
523
+ installClineHookScripts: () => installClineHookScripts,
524
+ installCursorHookScripts: () => installCursorHookScripts,
525
+ installEditorHooks: () => installEditorHooks,
526
+ installHookScripts: () => installHookScripts,
527
+ installKiloCodeHookScripts: () => installKiloCodeHookScripts,
528
+ installRooCodeHookScripts: () => installRooCodeHookScripts,
529
+ markProjectIndexed: () => markProjectIndexed,
530
+ mergeHooksIntoSettings: () => mergeHooksIntoSettings,
531
+ readClaudeSettings: () => readClaudeSettings,
532
+ readCursorHooksConfig: () => readCursorHooksConfig,
533
+ readIndexStatus: () => readIndexStatus,
534
+ unmarkProjectIndexed: () => unmarkProjectIndexed,
535
+ writeClaudeSettings: () => writeClaudeSettings,
536
+ writeCursorHooksConfig: () => writeCursorHooksConfig,
537
+ writeIndexStatus: () => writeIndexStatus
682
538
  });
683
- function applyMcpToolPrefix(markdown, toolPrefix) {
684
- const toolPattern = CONTEXTSTREAM_TOOL_NAMES.join("|");
685
- const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b(?=\\s*\\()`, "g");
686
- return markdown.replace(toolRegex, `${toolPrefix}$1`);
687
- }
688
- function getAvailableEditors() {
689
- return Object.keys(TEMPLATES);
690
- }
691
- function getTemplate(editor) {
692
- return TEMPLATES[editor.toLowerCase()] || null;
693
- }
694
- function generateRuleContent(editor, options) {
695
- const template = getTemplate(editor);
696
- if (!template) return null;
697
- const mode = options?.mode || "dynamic";
698
- const rules = mode === "full" ? CONTEXTSTREAM_RULES_FULL : mode === "minimal" ? CONTEXTSTREAM_RULES_MINIMAL : CONTEXTSTREAM_RULES_DYNAMIC;
699
- let content = template.build(rules);
700
- if (options?.workspaceName || options?.projectName) {
701
- const header = `
702
- # Workspace: ${options.workspaceName || "Unknown"}
703
- ${options.projectName ? `# Project: ${options.projectName}` : ""}
704
- ${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
705
-
706
- `;
707
- content = header + content;
708
- }
709
- if (options?.additionalRules) {
710
- content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
539
+ import * as fs4 from "node:fs/promises";
540
+ import * as path5 from "node:path";
541
+ import { homedir as homedir2 } from "node:os";
542
+ function getClaudeSettingsPath(scope, projectPath) {
543
+ if (scope === "user") {
544
+ return path5.join(homedir2(), ".claude", "settings.json");
711
545
  }
712
- if (editor.toLowerCase() === "claude") {
713
- content = applyMcpToolPrefix(content, `mcp__${DEFAULT_CLAUDE_MCP_SERVER_NAME}__`);
546
+ if (!projectPath) {
547
+ throw new Error("projectPath required for project scope");
714
548
  }
715
- return {
716
- filename: template.filename,
717
- content: content.trim() + "\n"
718
- };
549
+ return path5.join(projectPath, ".claude", "settings.json");
719
550
  }
720
- function generateAllRuleFiles(options) {
721
- return getAvailableEditors().map((editor) => {
722
- const result = generateRuleContent(editor, options);
723
- if (!result) return null;
724
- return { editor, ...result };
725
- }).filter((r) => r !== null);
551
+ function getHooksDir() {
552
+ return path5.join(homedir2(), ".claude", "hooks");
553
+ }
554
+ function buildHooksConfig(options) {
555
+ const userPromptHooks = [
556
+ {
557
+ matcher: "*",
558
+ hooks: [
559
+ {
560
+ type: "command",
561
+ command: "npx @contextstream/mcp-server hook user-prompt-submit",
562
+ timeout: 5
563
+ }
564
+ ]
565
+ }
566
+ ];
567
+ if (options?.includeMediaAware !== false) {
568
+ userPromptHooks.push({
569
+ matcher: "*",
570
+ hooks: [
571
+ {
572
+ type: "command",
573
+ command: "npx @contextstream/mcp-server hook media-aware",
574
+ timeout: 5
575
+ }
576
+ ]
577
+ });
578
+ }
579
+ const config = {
580
+ PreToolUse: [
581
+ {
582
+ matcher: "Glob|Grep|Search|Task|EnterPlanMode",
583
+ hooks: [
584
+ {
585
+ type: "command",
586
+ command: "npx @contextstream/mcp-server hook pre-tool-use",
587
+ timeout: 5
588
+ }
589
+ ]
590
+ }
591
+ ],
592
+ UserPromptSubmit: userPromptHooks
593
+ };
594
+ if (options?.includePreCompact) {
595
+ config.PreCompact = [
596
+ {
597
+ // Match both manual (/compact) and automatic compaction
598
+ matcher: "*",
599
+ hooks: [
600
+ {
601
+ type: "command",
602
+ command: "npx @contextstream/mcp-server hook pre-compact",
603
+ timeout: 10
604
+ }
605
+ ]
606
+ }
607
+ ];
608
+ }
609
+ const postToolUseHooks = [];
610
+ if (options?.includePostWrite !== false) {
611
+ postToolUseHooks.push({
612
+ matcher: "Edit|Write|NotebookEdit",
613
+ hooks: [
614
+ {
615
+ type: "command",
616
+ command: "npx @contextstream/mcp-server hook post-write",
617
+ timeout: 10
618
+ }
619
+ ]
620
+ });
621
+ }
622
+ if (options?.includeAutoRules !== false) {
623
+ postToolUseHooks.push({
624
+ matcher: "mcp__contextstream__init|mcp__contextstream__context",
625
+ hooks: [
626
+ {
627
+ type: "command",
628
+ command: "npx @contextstream/mcp-server hook auto-rules",
629
+ timeout: 15
630
+ }
631
+ ]
632
+ });
633
+ }
634
+ if (postToolUseHooks.length > 0) {
635
+ config.PostToolUse = postToolUseHooks;
636
+ }
637
+ return config;
638
+ }
639
+ async function installHookScripts(options) {
640
+ const hooksDir = getHooksDir();
641
+ await fs4.mkdir(hooksDir, { recursive: true });
642
+ const result = {
643
+ preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
644
+ userPrompt: "npx @contextstream/mcp-server hook user-prompt-submit"
645
+ };
646
+ if (options?.includePreCompact) {
647
+ result.preCompact = "npx @contextstream/mcp-server hook pre-compact";
648
+ }
649
+ if (options?.includeMediaAware !== false) {
650
+ result.mediaAware = "npx @contextstream/mcp-server hook media-aware";
651
+ }
652
+ if (options?.includeAutoRules !== false) {
653
+ result.autoRules = "npx @contextstream/mcp-server hook auto-rules";
654
+ }
655
+ return result;
656
+ }
657
+ async function readClaudeSettings(scope, projectPath) {
658
+ const settingsPath = getClaudeSettingsPath(scope, projectPath);
659
+ try {
660
+ const content = await fs4.readFile(settingsPath, "utf-8");
661
+ return JSON.parse(content);
662
+ } catch {
663
+ return {};
664
+ }
665
+ }
666
+ async function writeClaudeSettings(settings, scope, projectPath) {
667
+ const settingsPath = getClaudeSettingsPath(scope, projectPath);
668
+ const dir = path5.dirname(settingsPath);
669
+ await fs4.mkdir(dir, { recursive: true });
670
+ await fs4.writeFile(settingsPath, JSON.stringify(settings, null, 2));
671
+ }
672
+ function mergeHooksIntoSettings(existingSettings, newHooks) {
673
+ const settings = { ...existingSettings };
674
+ const existingHooks = settings.hooks || {};
675
+ for (const [hookType, matchers] of Object.entries(newHooks || {})) {
676
+ if (!matchers) continue;
677
+ const existing = existingHooks?.[hookType] || [];
678
+ const filtered = existing.filter((m) => {
679
+ return !m.hooks?.some((h) => h.command?.includes("contextstream"));
680
+ });
681
+ existingHooks[hookType] = [...filtered, ...matchers];
682
+ }
683
+ settings.hooks = existingHooks;
684
+ return settings;
685
+ }
686
+ async function installClaudeCodeHooks(options) {
687
+ const result = { scripts: [], settings: [] };
688
+ result.scripts.push(
689
+ "npx @contextstream/mcp-server hook pre-tool-use",
690
+ "npx @contextstream/mcp-server hook user-prompt-submit"
691
+ );
692
+ if (options.includePreCompact) {
693
+ result.scripts.push("npx @contextstream/mcp-server hook pre-compact");
694
+ }
695
+ if (options.includeMediaAware !== false) {
696
+ result.scripts.push("npx @contextstream/mcp-server hook media-aware");
697
+ }
698
+ if (options.includePostWrite !== false) {
699
+ result.scripts.push("npx @contextstream/mcp-server hook post-write");
700
+ }
701
+ if (options.includeAutoRules !== false) {
702
+ result.scripts.push("npx @contextstream/mcp-server hook auto-rules");
703
+ }
704
+ const hooksConfig = buildHooksConfig({
705
+ includePreCompact: options.includePreCompact,
706
+ includeMediaAware: options.includeMediaAware,
707
+ includePostWrite: options.includePostWrite,
708
+ includeAutoRules: options.includeAutoRules
709
+ });
710
+ if (options.scope === "user" || options.scope === "both") {
711
+ const settingsPath = getClaudeSettingsPath("user");
712
+ if (!options.dryRun) {
713
+ const existing = await readClaudeSettings("user");
714
+ const merged = mergeHooksIntoSettings(existing, hooksConfig);
715
+ await writeClaudeSettings(merged, "user");
716
+ }
717
+ result.settings.push(settingsPath);
718
+ }
719
+ if ((options.scope === "project" || options.scope === "both") && options.projectPath) {
720
+ const settingsPath = getClaudeSettingsPath("project", options.projectPath);
721
+ if (!options.dryRun) {
722
+ const existing = await readClaudeSettings("project", options.projectPath);
723
+ const merged = mergeHooksIntoSettings(existing, hooksConfig);
724
+ await writeClaudeSettings(merged, "project", options.projectPath);
725
+ }
726
+ result.settings.push(settingsPath);
727
+ }
728
+ return result;
729
+ }
730
+ function generateHooksDocumentation() {
731
+ return `
732
+ ## Claude Code Hooks (ContextStream)
733
+
734
+ ContextStream installs hooks to enforce ContextStream-first behavior.
735
+ All hooks run via Node.js - no Python dependency required.
736
+
737
+ ### PreToolUse Hook
738
+ - **Command:** \`npx @contextstream/mcp-server hook pre-tool-use\`
739
+ - **Purpose:** Blocks Glob/Grep/Search/EnterPlanMode and redirects to ContextStream
740
+ - **Blocked tools:** Glob, Grep, Search, Task(Explore), Task(Plan), EnterPlanMode
741
+ - **Disable:** Set \`CONTEXTSTREAM_HOOK_ENABLED=false\` environment variable
742
+
743
+ ### UserPromptSubmit Hook
744
+ - **Command:** \`npx @contextstream/mcp-server hook user-prompt-submit\`
745
+ - **Purpose:** Injects a reminder about ContextStream rules on every message
746
+ - **Disable:** Set \`CONTEXTSTREAM_REMINDER_ENABLED=false\` environment variable
747
+
748
+ ### Media-Aware Hook
749
+ - **Command:** \`npx @contextstream/mcp-server hook media-aware\`
750
+ - **Purpose:** Detects media-related prompts and injects media tool guidance
751
+ - **Triggers:** Patterns like video, clips, Remotion, image, audio, creative assets
752
+ - **Disable:** Set \`CONTEXTSTREAM_MEDIA_HOOK_ENABLED=false\` environment variable
753
+
754
+ When Media-Aware hook detects media patterns, it injects context about:
755
+ - How to search indexed media assets
756
+ - How to get clips for Remotion (with frame-based props)
757
+ - How to index new media files
758
+
759
+ ### PreCompact Hook (Optional)
760
+ - **Command:** \`npx @contextstream/mcp-server hook pre-compact\`
761
+ - **Purpose:** Saves conversation state before context compaction
762
+ - **Triggers:** Both manual (/compact) and automatic compaction
763
+ - **Disable:** Set \`CONTEXTSTREAM_PRECOMPACT_ENABLED=false\` environment variable
764
+ - **Note:** Enable with \`generate_rules(include_pre_compact=true)\` to activate
765
+
766
+ When PreCompact runs, it:
767
+ 1. Parses the transcript for active files and tool calls
768
+ 2. Saves a session_snapshot to ContextStream API
769
+ 3. Injects context about using \`session_init(is_post_compact=true)\` after compaction
770
+
771
+ ### PostToolUse Hook (Real-time Indexing)
772
+ - **Command:** \`npx @contextstream/mcp-server hook post-write\`
773
+ - **Purpose:** Indexes files immediately after Edit/Write/NotebookEdit operations
774
+ - **Matcher:** Edit|Write|NotebookEdit
775
+ - **Disable:** Set \`CONTEXTSTREAM_POSTWRITE_ENABLED=false\` environment variable
776
+
777
+ ### Why Hooks?
778
+ Claude Code has strong built-in behaviors to use its default tools (Grep, Glob, Read)
779
+ and its built-in plan mode. CLAUDE.md instructions decay over long conversations.
780
+ Hooks provide:
781
+ 1. **Physical enforcement** - Blocked tools can't be used
782
+ 2. **Continuous reminders** - Rules stay in recent context
783
+ 3. **Better UX** - Faster searches via indexed ContextStream
784
+ 4. **Persistent plans** - ContextStream plans survive across sessions
785
+ 5. **Compaction awareness** - Save state before context is compacted
786
+ 6. **Real-time indexing** - Files indexed immediately after writes
787
+
788
+ ### Manual Configuration
789
+ If you prefer to configure manually, add to \`~/.claude/settings.json\`:
790
+ \`\`\`json
791
+ {
792
+ "hooks": {
793
+ "PreToolUse": [{
794
+ "matcher": "Glob|Grep|Search|Task|EnterPlanMode",
795
+ "hooks": [{"type": "command", "command": "npx @contextstream/mcp-server hook pre-tool-use"}]
796
+ }],
797
+ "UserPromptSubmit": [
798
+ {
799
+ "matcher": "*",
800
+ "hooks": [{"type": "command", "command": "npx @contextstream/mcp-server hook user-prompt-submit"}]
801
+ },
802
+ {
803
+ "matcher": "*",
804
+ "hooks": [{"type": "command", "command": "npx @contextstream/mcp-server hook media-aware"}]
805
+ }
806
+ ],
807
+ "PreCompact": [{
808
+ "matcher": "*",
809
+ "hooks": [{"type": "command", "command": "npx @contextstream/mcp-server hook pre-compact", "timeout": 10}]
810
+ }],
811
+ "PostToolUse": [{
812
+ "matcher": "Edit|Write|NotebookEdit",
813
+ "hooks": [{"type": "command", "command": "npx @contextstream/mcp-server hook post-write", "timeout": 10}]
814
+ }]
815
+ }
726
816
  }
727
- var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, TEMPLATES;
728
- var init_rules_templates = __esm({
729
- "src/rules-templates.ts"() {
730
- "use strict";
731
- init_version();
732
- DEFAULT_CLAUDE_MCP_SERVER_NAME = "contextstream";
733
- RULES_VERSION = VERSION === "unknown" ? "0.0.0" : VERSION;
734
- CONTEXTSTREAM_TOOL_NAMES = [
735
- // Standalone tools (always present)
736
- "init",
737
- // Renamed from session_init - initialize conversation session
738
- "context",
739
- // Renamed from context_smart - get relevant context every message
740
- "context_feedback",
741
- "generate_rules",
742
- // Consolidated domain tools (v0.4.x default)
743
- "search",
744
- // Modes: semantic, hybrid, keyword, pattern
745
- "session",
746
- // Actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace
747
- "memory",
748
- // Actions: create_event, get_event, update_event, delete_event, list_events, distill_event, create_node, get_node, update_node, delete_node, list_nodes, supersede_node, search, decisions, timeline, summary
749
- "graph",
750
- // Actions: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions
751
- "project",
752
- // Actions: list, get, create, update, index, overview, statistics, files, index_status, ingest_local
753
- "workspace",
754
- // Actions: list, get, associate, bootstrap
755
- "reminder",
756
- // Actions: list, active, create, snooze, complete, dismiss
757
- "integration",
758
- // Provider: slack, github, all; Actions: status, search, stats, activity, contributors, knowledge, summary, channels, discussions, sync_users, repos, issues
759
- "help"
760
- // Actions: tools, auth, version, editor_rules, enable_bundle
761
- ];
762
- CONTEXTSTREAM_RULES_DYNAMIC = `
763
- ## ContextStream Dynamic Rules (Powered by our SmartRouter)
764
-
765
- All rules are served dynamically via \`context\`. This file is minimal by design.
766
-
767
- ### Every Message Protocol
768
-
769
- | Message | What to Call |
770
- |---------|--------------|
771
- | **1st message** | \`init()\` then \`context(user_message="<msg>")\` |
772
- | **2nd+ messages** | \`context(user_message="<msg>")\` |
773
-
774
- Note: \`init()\` auto-detects folder from IDE. Only pass \`folder_path\` if auto-detection fails.
775
-
776
- ### Follow the Instructions Field
777
-
778
- The \`context\` response includes an \`instructions\` field with context-aware guidance.
779
- **Follow these instructions.** They are dynamically matched to your query and include:
780
- - Search guidance (when/how to search)
781
- - Git workflow rules (commit, PR, safety)
782
- - Planning rules (use ContextStream plans, not file-based)
783
- - Media/code analysis guidance
784
- - Lessons from past mistakes
785
- - And more...
786
-
787
- ### Notices
788
-
789
- Handle notices from \`context\` response:
790
- - **[VERSION_NOTICE]**: Tell user to update MCP
791
- - **[RULES_NOTICE]**: Run \`generate_rules()\`
792
- - **[LESSONS_WARNING]**: Apply lessons immediately
793
-
794
- Rules Version: ${RULES_VERSION}
795
- `.trim();
796
- CONTEXTSTREAM_RULES_FULL = `
797
- ## ContextStream Rules
798
-
799
- <contextstream_rules>
800
- 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
801
- 2. **CODING TASK?** \u2192 init \u2192 context \u2192 work \u2192 capture
802
- 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
803
- </contextstream_rules>
804
-
805
- **Display this block at the start of responses to keep rules in context.**
806
-
807
- ---
808
-
809
- ## \u{1F6A8} CRITICAL RULE #1 - CONTEXTSTREAM SEARCH FIRST \u{1F6A8}
810
-
811
- **BEFORE using Glob, Grep, Search, Read (for discovery), Explore, or ANY local file scanning:**
812
- \`\`\`
813
- STOP \u2192 Call search(mode="hybrid", query="...") FIRST
814
817
  \`\`\`
818
+ `.trim();
819
+ }
820
+ function getIndexStatusPath() {
821
+ return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
822
+ }
823
+ async function readIndexStatus() {
824
+ const statusPath = getIndexStatusPath();
825
+ try {
826
+ const content = await fs4.readFile(statusPath, "utf-8");
827
+ return JSON.parse(content);
828
+ } catch {
829
+ return { version: 1, projects: {} };
830
+ }
831
+ }
832
+ async function writeIndexStatus(status) {
833
+ const statusPath = getIndexStatusPath();
834
+ const dir = path5.dirname(statusPath);
835
+ await fs4.mkdir(dir, { recursive: true });
836
+ await fs4.writeFile(statusPath, JSON.stringify(status, null, 2));
837
+ }
838
+ async function markProjectIndexed(projectPath, options) {
839
+ const status = await readIndexStatus();
840
+ const resolvedPath = path5.resolve(projectPath);
841
+ status.projects[resolvedPath] = {
842
+ indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
843
+ project_id: options?.project_id,
844
+ project_name: options?.project_name
845
+ };
846
+ await writeIndexStatus(status);
847
+ }
848
+ async function unmarkProjectIndexed(projectPath) {
849
+ const status = await readIndexStatus();
850
+ const resolvedPath = path5.resolve(projectPath);
851
+ delete status.projects[resolvedPath];
852
+ await writeIndexStatus(status);
853
+ }
854
+ function getClineHooksDir(scope, projectPath) {
855
+ if (scope === "global") {
856
+ return path5.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
857
+ }
858
+ if (!projectPath) {
859
+ throw new Error("projectPath required for project scope");
860
+ }
861
+ return path5.join(projectPath, ".clinerules", "hooks");
862
+ }
863
+ async function installClineHookScripts(options) {
864
+ const hooksDir = getClineHooksDir(options.scope, options.projectPath);
865
+ await fs4.mkdir(hooksDir, { recursive: true });
866
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
867
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
868
+ const postToolUsePath = path5.join(hooksDir, "PostToolUse");
869
+ await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
870
+ await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
871
+ const result = {
872
+ preToolUse: preToolUsePath,
873
+ userPromptSubmit: userPromptPath
874
+ };
875
+ if (options.includePostWrite !== false) {
876
+ await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
877
+ result.postToolUse = postToolUsePath;
878
+ }
879
+ return result;
880
+ }
881
+ function getRooCodeHooksDir(scope, projectPath) {
882
+ if (scope === "global") {
883
+ return path5.join(homedir2(), ".roo", "hooks");
884
+ }
885
+ if (!projectPath) {
886
+ throw new Error("projectPath required for project scope");
887
+ }
888
+ return path5.join(projectPath, ".roo", "hooks");
889
+ }
890
+ async function installRooCodeHookScripts(options) {
891
+ const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
892
+ await fs4.mkdir(hooksDir, { recursive: true });
893
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
894
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
895
+ const postToolUsePath = path5.join(hooksDir, "PostToolUse");
896
+ await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
897
+ await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
898
+ const result = {
899
+ preToolUse: preToolUsePath,
900
+ userPromptSubmit: userPromptPath
901
+ };
902
+ if (options.includePostWrite !== false) {
903
+ await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
904
+ result.postToolUse = postToolUsePath;
905
+ }
906
+ return result;
907
+ }
908
+ function getKiloCodeHooksDir(scope, projectPath) {
909
+ if (scope === "global") {
910
+ return path5.join(homedir2(), ".kilocode", "hooks");
911
+ }
912
+ if (!projectPath) {
913
+ throw new Error("projectPath required for project scope");
914
+ }
915
+ return path5.join(projectPath, ".kilocode", "hooks");
916
+ }
917
+ async function installKiloCodeHookScripts(options) {
918
+ const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
919
+ await fs4.mkdir(hooksDir, { recursive: true });
920
+ const preToolUsePath = path5.join(hooksDir, "PreToolUse");
921
+ const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
922
+ const postToolUsePath = path5.join(hooksDir, "PostToolUse");
923
+ await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
924
+ await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
925
+ const result = {
926
+ preToolUse: preToolUsePath,
927
+ userPromptSubmit: userPromptPath
928
+ };
929
+ if (options.includePostWrite !== false) {
930
+ await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
931
+ result.postToolUse = postToolUsePath;
932
+ }
933
+ return result;
934
+ }
935
+ function getCursorHooksConfigPath(scope, projectPath) {
936
+ if (scope === "global") {
937
+ return path5.join(homedir2(), ".cursor", "hooks.json");
938
+ }
939
+ if (!projectPath) {
940
+ throw new Error("projectPath required for project scope");
941
+ }
942
+ return path5.join(projectPath, ".cursor", "hooks.json");
943
+ }
944
+ function getCursorHooksDir(scope, projectPath) {
945
+ if (scope === "global") {
946
+ return path5.join(homedir2(), ".cursor", "hooks");
947
+ }
948
+ if (!projectPath) {
949
+ throw new Error("projectPath required for project scope");
950
+ }
951
+ return path5.join(projectPath, ".cursor", "hooks");
952
+ }
953
+ async function readCursorHooksConfig(scope, projectPath) {
954
+ const configPath = getCursorHooksConfigPath(scope, projectPath);
955
+ try {
956
+ const content = await fs4.readFile(configPath, "utf-8");
957
+ return JSON.parse(content);
958
+ } catch {
959
+ return { version: 1, hooks: {} };
960
+ }
961
+ }
962
+ async function writeCursorHooksConfig(config, scope, projectPath) {
963
+ const configPath = getCursorHooksConfigPath(scope, projectPath);
964
+ const dir = path5.dirname(configPath);
965
+ await fs4.mkdir(dir, { recursive: true });
966
+ await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
967
+ }
968
+ async function installCursorHookScripts(options) {
969
+ const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
970
+ await fs4.mkdir(hooksDir, { recursive: true });
971
+ const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
972
+ const filterContextStreamHooks = (hooks) => {
973
+ if (!hooks) return [];
974
+ return hooks.filter((h) => {
975
+ const hook = h;
976
+ return !hook.command?.includes("contextstream");
977
+ });
978
+ };
979
+ const filteredPreToolUse = filterContextStreamHooks(existingConfig.hooks.preToolUse);
980
+ const filteredBeforeSubmit = filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt);
981
+ const config = {
982
+ version: 1,
983
+ hooks: {
984
+ ...existingConfig.hooks,
985
+ preToolUse: [
986
+ ...filteredPreToolUse,
987
+ {
988
+ command: "npx @contextstream/mcp-server hook pre-tool-use",
989
+ type: "command",
990
+ timeout: 5,
991
+ matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
992
+ }
993
+ ],
994
+ beforeSubmitPrompt: [
995
+ ...filteredBeforeSubmit,
996
+ {
997
+ command: "npx @contextstream/mcp-server hook user-prompt-submit",
998
+ type: "command",
999
+ timeout: 5
1000
+ }
1001
+ ]
1002
+ }
1003
+ };
1004
+ await writeCursorHooksConfig(config, options.scope, options.projectPath);
1005
+ const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
1006
+ return {
1007
+ preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
1008
+ beforeSubmitPrompt: "npx @contextstream/mcp-server hook user-prompt-submit",
1009
+ config: configPath
1010
+ };
1011
+ }
1012
+ async function installEditorHooks(options) {
1013
+ const { editor, scope, projectPath, includePreCompact, includePostWrite } = options;
1014
+ switch (editor) {
1015
+ case "claude": {
1016
+ if (scope === "project" && !projectPath) {
1017
+ throw new Error("projectPath required for project scope");
1018
+ }
1019
+ const scripts = await installHookScripts({ includePreCompact });
1020
+ const hooksConfig = buildHooksConfig({ includePreCompact, includePostWrite });
1021
+ const settingsScope = scope === "global" ? "user" : "project";
1022
+ const existing = await readClaudeSettings(settingsScope, projectPath);
1023
+ const merged = mergeHooksIntoSettings(existing, hooksConfig);
1024
+ await writeClaudeSettings(merged, settingsScope, projectPath);
1025
+ const installed = [scripts.preToolUse, scripts.userPrompt];
1026
+ if (scripts.preCompact) installed.push(scripts.preCompact);
1027
+ return {
1028
+ editor: "claude",
1029
+ installed,
1030
+ hooksDir: getHooksDir()
1031
+ };
1032
+ }
1033
+ case "cline": {
1034
+ const scripts = await installClineHookScripts({ scope, projectPath, includePostWrite });
1035
+ const installed = [scripts.preToolUse, scripts.userPromptSubmit];
1036
+ if (scripts.postToolUse) installed.push(scripts.postToolUse);
1037
+ return {
1038
+ editor: "cline",
1039
+ installed,
1040
+ hooksDir: getClineHooksDir(scope, projectPath)
1041
+ };
1042
+ }
1043
+ case "roo": {
1044
+ const scripts = await installRooCodeHookScripts({ scope, projectPath, includePostWrite });
1045
+ const installed = [scripts.preToolUse, scripts.userPromptSubmit];
1046
+ if (scripts.postToolUse) installed.push(scripts.postToolUse);
1047
+ return {
1048
+ editor: "roo",
1049
+ installed,
1050
+ hooksDir: getRooCodeHooksDir(scope, projectPath)
1051
+ };
1052
+ }
1053
+ case "kilo": {
1054
+ const scripts = await installKiloCodeHookScripts({ scope, projectPath, includePostWrite });
1055
+ const installed = [scripts.preToolUse, scripts.userPromptSubmit];
1056
+ if (scripts.postToolUse) installed.push(scripts.postToolUse);
1057
+ return {
1058
+ editor: "kilo",
1059
+ installed,
1060
+ hooksDir: getKiloCodeHooksDir(scope, projectPath)
1061
+ };
1062
+ }
1063
+ case "cursor": {
1064
+ const scripts = await installCursorHookScripts({ scope, projectPath });
1065
+ return {
1066
+ editor: "cursor",
1067
+ installed: [scripts.preToolUse, scripts.beforeSubmitPrompt],
1068
+ hooksDir: getCursorHooksDir(scope, projectPath)
1069
+ };
1070
+ }
1071
+ default:
1072
+ throw new Error(`Unsupported editor: ${editor}`);
1073
+ }
1074
+ }
1075
+ async function installAllEditorHooks(options) {
1076
+ const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
1077
+ const results = [];
1078
+ for (const editor of editors) {
1079
+ try {
1080
+ const result = await installEditorHooks({
1081
+ editor,
1082
+ scope: options.scope,
1083
+ projectPath: options.projectPath,
1084
+ includePreCompact: options.includePreCompact,
1085
+ includePostWrite: options.includePostWrite
1086
+ });
1087
+ results.push(result);
1088
+ } catch (error) {
1089
+ console.error(`Failed to install hooks for ${editor}:`, error);
1090
+ }
1091
+ }
1092
+ return results;
1093
+ }
1094
+ function generateAllHooksDocumentation() {
1095
+ return `
1096
+ ## Editor Hooks Support (ContextStream)
815
1097
 
816
- **Note:** PreToolUse hooks block these tools when ContextStream is available.
817
- **Claude Code users:** Your tool names are \`mcp__contextstream__search\`, \`mcp__contextstream__init\`, etc.
818
-
819
- \u274C **NEVER DO THIS:**
820
- - \`Glob("**/*.ts")\` \u2192 Use \`search(mode="pattern", query="*.ts")\` instead
821
- - \`Grep("functionName")\` \u2192 Use \`search(mode="keyword", query="functionName")\` instead
822
- - \`Read(file)\` for discovery \u2192 Use \`search(mode="hybrid", query="...")\` instead
823
- - \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="hybrid")\` instead
824
-
825
- \u2705 **ALWAYS DO THIS:**
826
- 1. \`search(mode="hybrid", query="what you're looking for")\`
827
- 2. Only use local tools (Glob/Grep/Read) if ContextStream returns **0 results**
828
- 3. Use Read ONLY for exact file edits after you know the file path
829
-
830
- This applies to **EVERY search** throughout the **ENTIRE conversation**, not just the first message.
831
-
832
- ---
833
-
834
- ## \u{1F6A8} CRITICAL RULE #2 - AUTO-INDEXING \u{1F6A8}
835
-
836
- **ContextStream auto-indexes your project on \`init\`.** You do NOT need to:
837
- - Ask the user to index
838
- - Manually trigger ingestion
839
- - Check index_status before every search
840
-
841
- **When \`init\` returns \`indexing_status: "started"\` or \`"refreshing"\`:**
842
- - Background indexing is running automatically
843
- - Search results will be available within seconds to minutes
844
- - **DO NOT fall back to local tools** - wait for ContextStream search to work
845
- - If search returns 0 results initially, try again after a moment
846
-
847
- **Only manually trigger indexing if:**
848
- - \`init\` returned \`ingest_recommendation.recommended: true\` (rare edge case)
849
- - User explicitly asks to re-index
850
-
851
- ---
852
-
853
- ## \u{1F6A8} CRITICAL RULE #3 - LESSONS (PAST MISTAKES) \u{1F6A8}
854
-
855
- **Lessons are past mistakes that MUST inform your work.** Ignoring lessons leads to repeated failures.
856
-
857
- ### On \`init\`:
858
- - Check for \`lessons\` and \`lessons_warning\` in the response
859
- - If present, **READ THEM IMMEDIATELY** before doing any work
860
- - These are high-priority lessons (critical/high severity) relevant to your context
861
- - **Apply the prevention steps** from each lesson to avoid repeating mistakes
862
-
863
- ### On \`context\`:
864
- - Check for \`[LESSONS_WARNING]\` tag in the response
865
- - If present, you **MUST** tell the user about the lessons before proceeding
866
- - Lessons are proactively fetched when risky actions are detected (refactor, migrate, deploy, etc.)
867
- - **Do not skip or bury this warning** - lessons represent real past mistakes
868
-
869
- ### Before ANY Non-Trivial Work:
870
- **ALWAYS call \`session(action="get_lessons", query="<topic>")\`** where \`<topic>\` matches what you're about to do:
871
- - Before refactoring \u2192 \`session(action="get_lessons", query="refactoring")\`
872
- - Before API changes \u2192 \`session(action="get_lessons", query="API changes")\`
873
- - Before database work \u2192 \`session(action="get_lessons", query="database migrations")\`
874
- - Before deployments \u2192 \`session(action="get_lessons", query="deployment")\`
875
-
876
- ### When Lessons Are Found:
877
- 1. **Summarize the lessons** to the user before proceeding
878
- 2. **Explicitly state how you will avoid the past mistakes**
879
- 3. If a lesson conflicts with the current approach, **warn the user**
880
-
881
- **Failing to check lessons before risky work is a critical error.**
882
-
883
- ---
884
-
885
- ## ContextStream v0.4.x Integration (Enhanced)
886
-
887
- You have access to ContextStream MCP tools for persistent memory and context.
888
- v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
889
- Rules Version: ${RULES_VERSION}
890
-
891
- ## TL;DR - WHEN TO USE CONTEXT
892
-
893
- | Request Type | What to Do |
894
- |--------------|------------|
895
- | **\u{1F680} Simple utility** (list workspaces, show version) | **Just execute directly** - skip init, context, capture |
896
- | **\u{1F4BB} Coding task** (edit, create, refactor) | Full context: init \u2192 context \u2192 work \u2192 capture |
897
- | **\u{1F50D} Code search/discovery** | init \u2192 context \u2192 search() |
898
- | **\u26A0\uFE0F Risky work** (deploy, migrate, refactor) | Check lessons first: \`session(action="get_lessons")\` |
899
- | **User frustration/correction** | Capture lesson: \`session(action="capture_lesson", ...)\` |
900
-
901
- ### Simple Utility Operations - FAST PATH
902
-
903
- **For simple queries, just execute and respond:**
904
- - "list workspaces" \u2192 \`workspace(action="list")\` \u2192 done
905
- - "list projects" \u2192 \`project(action="list")\` \u2192 done
906
- - "show version" \u2192 \`help(action="version")\` \u2192 done
907
- - "what reminders do I have" \u2192 \`reminder(action="list")\` \u2192 done
908
-
909
- **No init. No context. No capture.** These add noise, not value.
910
-
911
- ### Coding Tasks - FULL CONTEXT
912
-
913
- | Step | What to Call |
914
- |------|--------------|
915
- | **1st message** | \`init(folder_path="...", context_hint="<msg>")\`, then \`context(...)\` |
916
- | **2nd+ messages** | \`context(user_message="<msg>", format="minified", max_tokens=400)\` |
917
- | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
918
- | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
919
- | **User correction** | \`session(action="capture_lesson", ...)\` |
920
- | **\u26A0\uFE0F When warnings received** | **STOP**, acknowledge, explain mitigation, then proceed |
921
-
922
- **How to detect simple utility operations:**
923
- - Single-word commands: "list", "show", "version", "help"
924
- - Data retrieval with no context dependency: "list my workspaces", "what projects do I have"
925
- - Status checks: "am I authenticated?", "what's the server version?"
926
-
927
- **First message rule (for coding tasks):** After \`init\`:
928
- 1. Check for \`lessons\` in response - if present, READ and SUMMARIZE them to user
929
- 2. Then call \`context\` before any other tool or response
930
-
931
- **Context Pack (Pro+):** If enabled, use \`context(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context\` (the API will fall back).
932
-
933
- **Tool naming:** Use the exact tool names exposed by your MCP client. Claude Code typically uses \`mcp__<server>__<tool>\` where \`<server>\` matches your MCP config (often \`contextstream\`). If a tool call fails with "No such tool available", refresh rules and match the tool list.
934
-
935
- ---
936
-
937
- ## Consolidated Domain Tools Architecture
938
-
939
- v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode dispatch:
940
-
941
- ### Standalone Tools
942
- - **\`init\`** - Initialize session with workspace detection + context (skip for simple utility operations)
943
- - **\`context\`** - Semantic search for relevant context (skip for simple utility operations)
944
-
945
- ### Domain Tools (Use action/mode parameter)
946
-
947
- | Domain | Actions/Modes | Example |
948
- |--------|---------------|---------|
949
- | **\`search\`** | mode: semantic, hybrid, keyword, pattern | \`search(mode="hybrid", query="auth implementation", limit=3)\` |
950
- | **\`session\`** | action: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace | \`session(action="capture", event_type="decision", title="Use JWT", content="...")\` |
951
- | **\`memory\`** | action: create_event, get_event, update_event, delete_event, list_events, distill_event, create_node, get_node, update_node, delete_node, list_nodes, supersede_node, search, decisions, timeline, summary | \`memory(action="list_events", limit=10)\` |
952
- | **\`graph\`** | action: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions | \`graph(action="impact", symbol_name="AuthService")\` |
953
- | **\`project\`** | action: list, get, create, update, index, overview, statistics, files, index_status, ingest_local | \`project(action="statistics")\` |
954
- | **\`workspace\`** | action: list, get, associate, bootstrap | \`workspace(action="list")\` |
955
- | **\`reminder\`** | action: list, active, create, snooze, complete, dismiss | \`reminder(action="active")\` |
956
- | **\`integration\`** | provider: slack/github/all; action: status, search, stats, activity, contributors, knowledge, summary, channels, discussions, sync_users, repos, issues | \`integration(provider="github", action="search", query="...")\` |
957
- | **\`help\`** | action: tools, auth, version, editor_rules, enable_bundle | \`help(action="tools")\` |
958
-
959
- ---
960
-
961
- ### Why context is Required (Even After init)
962
-
963
- **Common mistake:** "init already gave me context, I don't need context"
964
-
965
- **This is WRONG. Here's why:**
966
- - \`init\` returns the last ~10 items **BY TIME** (chronological)
967
- - \`context\` **SEARCHES** for items **RELEVANT to THIS message** (semantic)
968
-
969
- **Example failure:**
970
- - User asks: "how should I implement authentication?"
971
- - Auth decisions were made 20 conversations ago
972
- - \`init\` won't have it (too old, not in recent 10)
973
- - \`context\` FINDS it via semantic search
974
-
975
- **Without context, you WILL miss relevant older context.**
976
-
977
- ---
978
-
979
- ### Recommended Token Budgets
980
-
981
- - For trivial/local edits: \`context(..., max_tokens=200)\`
982
- - Default: \`context(..., max_tokens=400)\`
983
- - Deep debugging/architecture: \`context(..., max_tokens=800)\`
984
- - Keep \`format="minified"\` (default) unless debugging
985
-
986
- If context still feels missing, use \`session(action="recall", query="...")\` for focused deep lookup.
987
-
988
- ---
989
-
990
- ### Rules, Version & Lessons Notices
991
-
992
- **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
993
-
994
- **[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
995
- 1. A new MCP server version is available
996
- 2. The exact update command to run
997
- 3. That they need to restart their AI tool after updating
998
- **Do not skip this** - users often miss stderr warnings.
999
-
1000
- **[LESSONS_WARNING]** - You **MUST** before proceeding:
1001
- 1. Read all lessons listed
1002
- 2. Tell the user about relevant lessons
1003
- 3. Explain how you will avoid each past mistake
1004
- **This is critical** - ignoring lessons leads to repeated failures.
1005
-
1006
- ---
1007
-
1008
- ### Preferences & Lessons (Use Early)
1009
-
1010
- - If preferences/style matter: \`session(action="user_context")\`
1011
- - Before risky changes: \`session(action="get_lessons", query="<topic>")\`
1012
- - On frustration/corrections: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
1013
-
1014
- ---
1015
-
1016
- ### Context Pressure & Compaction Awareness
1017
-
1018
- ContextStream tracks context pressure to help you stay ahead of conversation compaction:
1019
-
1020
- **Automatic tracking:** Token usage is tracked automatically. \`context\` returns \`context_pressure\` when usage is high.
1021
-
1022
- **When \`context\` returns \`context_pressure\` with high/critical level:**
1023
- 1. Review the \`suggested_action\` field:
1024
- - \`prepare_save\`: Start thinking about saving important state
1025
- - \`save_now\`: Immediately call \`session(action="capture", event_type="session_snapshot")\` to preserve state
1026
-
1027
- **PreCompact Hook (Optional):** If enabled, Claude Code will inject a reminder to save state before compaction.
1028
- Enable with: \`generate_rules(install_hooks=true, include_pre_compact=true)\`
1029
-
1030
- **Before compaction happens (when warned):**
1031
- \`\`\`
1032
- session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="{
1033
- \\"conversation_summary\\": \\"<summarize what we've been doing>\\",
1034
- \\"current_goal\\": \\"<the main task>\\",
1035
- \\"active_files\\": [\\"file1.ts\\", \\"file2.ts\\"],
1036
- \\"recent_decisions\\": [{title: \\"...\\", rationale: \\"...\\"}],
1037
- \\"unfinished_work\\": [{task: \\"...\\", status: \\"...\\", next_steps: \\"...\\"}]
1038
- }")
1039
- \`\`\`
1040
-
1041
- **After compaction (when context seems lost):**
1042
- 1. Call \`init(folder_path="...", is_post_compact=true)\` - this auto-restores the most recent snapshot
1043
- 2. Or call \`session_restore_context()\` directly to get the saved state
1044
- 3. Review the \`restored_context\` to understand prior work
1045
- 4. Acknowledge to the user what was restored and continue
1046
-
1047
- ---
1048
-
1049
- ### Index Status (Auto-Managed)
1050
-
1051
- **Indexing is automatic.** After \`init\`, the project is auto-indexed in the background.
1052
-
1053
- **You do NOT need to manually check index_status before every search.** Just use \`search()\`.
1054
-
1055
- **If search returns 0 results and you expected matches:**
1056
- 1. Check if \`init\` returned \`indexing_status: "started"\` - indexing may still be in progress
1057
- 2. Wait a moment and retry \`search()\`
1058
- 3. Only as a last resort: \`project(action="index_status")\` to check
1059
-
1060
- **Graph data:** If graph queries (\`dependencies\`, \`impact\`) return empty, run \`graph(action="ingest")\` once.
1061
-
1062
- **NEVER fall back to local tools (Glob/Grep/Read) just because search returned 0 results on first try.** Retry first.
1063
-
1064
- ### Enhanced Context (Server-Side Warnings)
1065
-
1066
- \`context\` now includes **intelligent server-side filtering** that proactively surfaces relevant warnings:
1067
-
1068
- **Response fields:**
1069
- - \`warnings\`: Array of warning strings (displayed with \u26A0\uFE0F prefix)
1070
-
1071
- **What triggers warnings:**
1072
- - **Lessons**: Past mistakes relevant to the current query (via semantic matching)
1073
- - **Risky actions**: Detected high-risk operations (deployments, migrations, destructive commands)
1074
- - **Breaking changes**: When modifications may impact other parts of the codebase
1075
-
1076
- **When you receive warnings:**
1077
- 1. **STOP** and read each warning carefully
1078
- 2. **Acknowledge** the warning to the user
1079
- 3. **Explain** how you will avoid the issue
1080
- 4. Only proceed after addressing the warnings
1081
-
1082
- ### Search & Code Intelligence (ContextStream-first)
1083
-
1084
- \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
1085
-
1086
- **\u274C WRONG workflow (wastes tokens, slow):**
1087
- \`\`\`
1088
- Grep "function" \u2192 Read file1.ts \u2192 Read file2.ts \u2192 Read file3.ts \u2192 finally understand
1089
- \`\`\`
1090
-
1091
- **\u2705 CORRECT workflow (fast, complete):**
1092
- \`\`\`
1093
- search(mode="hybrid", query="function implementation") \u2192 done (results include context)
1094
- \`\`\`
1095
-
1096
- **Why?** ContextStream search returns semantic matches + context + file locations in ONE call. Local tools require multiple round-trips.
1097
-
1098
- **Search order:**
1099
- 1. \`session(action="smart_search", query="...")\` - context-enriched
1100
- 2. \`search(mode="hybrid", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
1101
- 3. \`project(action="files")\` - file tree/list (only when needed)
1102
- 4. \`graph(action="dependencies", ...)\` - code structure
1103
- 5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
1104
-
1105
- **Search Mode Selection:**
1106
-
1107
- | Need | Mode | Example |
1108
- |------|------|---------|
1109
- | Find code by meaning | \`hybrid\` | "authentication logic", "error handling" |
1110
- | Exact string/symbol | \`keyword\` | "UserAuthService", "API_KEY" |
1111
- | File patterns | \`pattern\` | "*.sql", "test_*.py" |
1112
- | ALL matches (grep-like) | \`exhaustive\` | "TODO", "FIXME" (find all occurrences) |
1113
- | Symbol renaming | \`refactor\` | "oldFunctionName" (word-boundary matching) |
1114
- | Conceptual search | \`semantic\` | "how does caching work" |
1115
-
1116
- **Token Efficiency:** Use \`output_format\` to reduce response size:
1117
- - \`full\` (default): Full content for understanding code
1118
- - \`paths\`: File paths only (80% token savings) - use for file listings
1119
- - \`minimal\`: Compact format (60% savings) - use for refactoring
1120
- - \`count\`: Match counts only (90% savings) - use for quick checks
1121
-
1122
- **When to use \`output_format=count\`:**
1123
- - User asks "how many X" or "count of X" \u2192 \`search(..., output_format="count")\`
1124
- - Checking if something exists \u2192 count > 0 is sufficient
1125
- - Large exhaustive searches \u2192 get count first, then fetch if needed
1126
-
1127
- **Auto-suggested formats:** Search responses include \`query_interpretation.suggested_output_format\` when the API detects an optimal format:
1128
- - Symbol queries (e.g., "authOptions") \u2192 suggests \`minimal\` (path + line + snippet)
1129
- - Count queries (e.g., "how many") \u2192 suggests \`count\`
1130
- **USE the suggested format** on subsequent searches for best token efficiency.
1131
-
1132
- **Search defaults:** \`search\` returns the top 3 results with compact snippets. Use \`limit\` + \`offset\` for pagination, and \`content_max_chars\` to expand snippets when needed.
1133
-
1134
- If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits or ContextStream returned 0 results.
1135
-
1136
- **Code Analysis:**
1137
- - Dependencies: \`graph(action="dependencies", file_path="...")\`
1138
- - Change impact: \`graph(action="impact", symbol_name="...")\`
1139
- - Call path: \`graph(action="call_path", from_symbol="...", to_symbol="...")\`
1140
- - Build graph: \`graph(action="ingest")\` - async, can take a few minutes
1141
-
1142
- ---
1143
-
1144
- ### Distillation & Memory Hygiene
1145
-
1146
- - Quick context: \`session(action="summary")\`
1147
- - Long chat: \`session(action="compress", content="...")\`
1148
- - Memory summary: \`memory(action="summary")\`
1149
- - Condense noisy entries: \`memory(action="distill_event", event_id="...")\`
1150
-
1151
- ---
1152
-
1153
- ### When to Capture
1154
-
1155
- | When | Call | Example |
1156
- |------|------|---------|
1157
- | User makes decision | \`session(action="capture", event_type="decision", ...)\` | "Let's use PostgreSQL" |
1158
- | User states preference | \`session(action="capture", event_type="preference", ...)\` | "I prefer TypeScript" |
1159
- | Complete significant task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
1160
- | Need past context | \`session(action="recall", query="...")\` | "What did we decide about X?" |
1161
-
1162
- **DO NOT capture utility operations:**
1163
- - \u274C "Listed workspaces" - not meaningful context
1164
- - \u274C "Showed version" - not a decision
1165
- - \u274C "Listed projects" - just data retrieval
1166
-
1167
- **DO capture meaningful work:**
1168
- - \u2705 Decisions, preferences, completed features
1169
- - \u2705 Lessons from mistakes
1170
- - \u2705 Insights about architecture or patterns
1171
-
1172
- ---
1173
-
1174
- ### \u{1F6A8} Plans & Tasks - USE CONTEXTSTREAM, NOT FILE-BASED PLANS \u{1F6A8}
1098
+ ContextStream can install hooks for multiple AI code editors to enforce ContextStream-first behavior.
1175
1099
 
1176
- **CRITICAL: When the user requests planning, implementation plans, roadmaps, task breakdowns, or step-by-step approaches:**
1100
+ ### Supported Editors
1177
1101
 
1178
- \u274C **DO NOT** use built-in plan mode (EnterPlanMode tool)
1179
- \u274C **DO NOT** write plans to markdown files or plan documents
1180
- \u274C **DO NOT** ask "should I create a plan file?"
1102
+ | Editor | Hooks Location | Hook Types |
1103
+ |--------|---------------|------------|
1104
+ | **Claude Code** | \`~/.claude/hooks/\` | PreToolUse, UserPromptSubmit, PreCompact |
1105
+ | **Cursor** | \`~/.cursor/hooks/\` | preToolUse, beforeSubmit |
1106
+ | **Cline** | \`~/Documents/Cline/Rules/Hooks/\` | PreToolUse, UserPromptSubmit |
1107
+ | **Roo Code** | \`~/.roo/hooks/\` | PreToolUse, UserPromptSubmit |
1108
+ | **Kilo Code** | \`~/.kilocode/hooks/\` | PreToolUse, UserPromptSubmit |
1181
1109
 
1182
- \u2705 **ALWAYS** use ContextStream's plan/task system instead
1110
+ ### Claude Code Hooks
1183
1111
 
1184
- **Trigger phrases to detect (use ContextStream immediately):**
1185
- - "create a plan", "make a plan", "plan this", "plan for"
1186
- - "implementation plan", "roadmap", "milestones"
1187
- - "break down", "breakdown", "break this into steps"
1188
- - "what are the steps", "step by step", "outline the approach"
1189
- - "task list", "todo list", "action items"
1190
- - "how should we approach", "implementation strategy"
1112
+ ${generateHooksDocumentation()}
1191
1113
 
1192
- **When detected, immediately:**
1114
+ ### Cursor Hooks
1193
1115
 
1194
- 1. **Create the plan in ContextStream:**
1195
- \`\`\`
1196
- session(action="capture_plan", title="<descriptive title>", description="<what this plan accomplishes>", goals=["goal1", "goal2"], steps=[{id: "1", title: "Step 1", order: 1, description: "..."}, ...])
1197
- \`\`\`
1116
+ Cursor uses a \`hooks.json\` configuration file:
1117
+ - **preToolUse**: Blocks discovery tools before execution
1118
+ - **beforeSubmitPrompt**: Injects ContextStream rules reminder
1198
1119
 
1199
- 2. **Create tasks for each step:**
1120
+ #### Output Format
1121
+ \`\`\`json
1122
+ { "decision": "allow" }
1200
1123
  \`\`\`
1201
- memory(action="create_task", title="<task title>", plan_id="<plan_id from step 1>", priority="high|medium|low", description="<detailed task description>")
1124
+ or
1125
+ \`\`\`json
1126
+ { "decision": "deny", "reason": "Use ContextStream search instead" }
1202
1127
  \`\`\`
1203
1128
 
1204
- **Why ContextStream plans are better:**
1205
- - Plans persist across sessions and are searchable
1206
- - Tasks track status (pending/in_progress/completed/blocked)
1207
- - Context is preserved with workspace/project association
1208
- - Can be retrieved with \`session(action="get_plan", plan_id="...", include_tasks=true)\`
1209
- - Future sessions can continue from where you left off
1129
+ ### Cline/Roo/Kilo Code Hooks
1210
1130
 
1211
- **Managing plans/tasks:**
1212
- - List plans: \`session(action="list_plans")\`
1213
- - Get plan with tasks: \`session(action="get_plan", plan_id="<uuid>", include_tasks=true)\`
1214
- - List tasks: \`memory(action="list_tasks", plan_id="<uuid>")\` or \`memory(action="list_tasks")\` for all
1215
- - Update task status: \`memory(action="update_task", task_id="<uuid>", task_status="pending|in_progress|completed|blocked")\`
1216
- - Link task to plan: \`memory(action="update_task", task_id="<uuid>", plan_id="<plan_uuid>")\`
1217
- - Unlink task from plan: \`memory(action="update_task", task_id="<uuid>", plan_id=null)\`
1218
- - Delete: \`memory(action="delete_task", task_id="<uuid>")\` or \`memory(action="delete_event", event_id="<plan_uuid>")\`
1131
+ These editors use the same hook format (JSON output):
1132
+ - **PreToolUse**: Blocks discovery tools, redirects to ContextStream search
1133
+ - **UserPromptSubmit**: Injects ContextStream rules reminder
1219
1134
 
1220
- ---
1135
+ Hooks are executable scripts named after the hook type (no extension).
1221
1136
 
1222
- ### Complete Action Reference
1137
+ #### Output Format
1138
+ \`\`\`json
1139
+ {
1140
+ "cancel": true,
1141
+ "errorMessage": "Use ContextStream search instead",
1142
+ "contextModification": "[CONTEXTSTREAM] Use search tool first"
1143
+ }
1144
+ \`\`\`
1223
1145
 
1224
- **session actions:**
1225
- - \`capture\` - Save decision/insight/task (requires: event_type, title, content)
1226
- - \`capture_lesson\` - Save lesson from mistake (requires: title, category, trigger, impact, prevention)
1227
- - \`get_lessons\` - Retrieve relevant lessons (optional: query, category, severity)
1228
- - \`recall\` - Natural language memory recall (requires: query)
1229
- - \`remember\` - Quick save to memory (requires: content)
1230
- - \`user_context\` - Get user preferences/style
1231
- - \`summary\` - Workspace summary
1232
- - \`compress\` - Compress long conversation
1233
- - \`delta\` - Changes since timestamp
1234
- - \`smart_search\` - Context-enriched search
1235
- - \`decision_trace\` - Trace decision provenance
1146
+ ### Installation
1236
1147
 
1237
- **memory actions:**
1238
- - Event CRUD: \`create_event\`, \`get_event\`, \`update_event\`, \`delete_event\`, \`list_events\`, \`distill_event\`
1239
- - Node CRUD: \`create_node\`, \`get_node\`, \`update_node\`, \`delete_node\`, \`list_nodes\`, \`supersede_node\`
1240
- - Query: \`search\`, \`decisions\`, \`timeline\`, \`summary\`
1148
+ Use \`generate_rules(install_hooks=true, editors=["claude", "cursor", "cline", "roo", "kilo"])\` to install hooks for specific editors, or omit \`editors\` to install for all.
1241
1149
 
1242
- **graph actions:**
1243
- - Analysis: \`dependencies\`, \`impact\`, \`call_path\`, \`related\`, \`path\`
1244
- - Quality: \`circular_dependencies\`, \`unused_code\`, \`contradictions\`
1245
- - Management: \`ingest\`, \`decisions\`
1150
+ ### Disabling Hooks
1246
1151
 
1247
- See full documentation: https://contextstream.io/docs/mcp/tools
1152
+ Set environment variables:
1153
+ - \`CONTEXTSTREAM_HOOK_ENABLED=false\` - Disable PreToolUse blocking
1154
+ - \`CONTEXTSTREAM_REMINDER_ENABLED=false\` - Disable UserPromptSubmit reminders
1248
1155
  `.trim();
1249
- CONTEXTSTREAM_RULES_MINIMAL = `
1250
- ## ContextStream Rules
1251
-
1252
- <contextstream_rules>
1253
- 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
1254
- 2. **CODING TASK?** \u2192 init \u2192 context \u2192 work \u2192 capture
1255
- 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
1256
- </contextstream_rules>
1257
-
1258
- **Display this block at the start of responses to keep rules in context.**
1259
-
1260
- ---
1261
-
1262
- ## ContextStream v0.4.x (Hooks Enforced)
1263
-
1264
- Rules Version: ${RULES_VERSION}
1265
- **Note:** PreToolUse hooks block Glob/Grep/Search when ContextStream is available.
1266
-
1267
- ### For Coding Tasks
1268
-
1269
- | Action | Tool Call |
1270
- |--------|-----------|
1271
- | **1st message** | \`init(folder_path="<cwd>", context_hint="<msg>")\` then \`context(...)\` |
1272
- | **2nd+ messages** | \`context(user_message="<msg>", format="minified", max_tokens=400)\` |
1273
- | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE any local tools |
1274
- | **Save decisions** | \`session(action="capture", event_type="decision", ...)\` |
1275
-
1276
- ### Search Modes
1277
-
1278
- | Mode | Use Case |
1279
- |------|----------|
1280
- | \`hybrid\` | General code search (default) |
1281
- | \`keyword\` | Exact symbol/string match |
1282
- | \`exhaustive\` | Find ALL matches (grep-like) |
1283
- | \`semantic\` | Conceptual questions |
1284
-
1285
- ### Why ContextStream First?
1286
-
1287
- \u274C **WRONG:** \`Grep \u2192 Read \u2192 Read \u2192 Read\` (4+ tool calls, slow)
1288
- \u2705 **CORRECT:** \`search(mode="hybrid")\` (1 call, returns context)
1289
-
1290
- ContextStream search is **indexed** and returns semantic matches + context in ONE call.
1291
-
1292
- ### Quick Reference
1293
-
1294
- | Tool | Example |
1295
- |------|---------|
1296
- | \`search\` | \`search(mode="hybrid", query="auth", limit=3)\` |
1297
- | \`session\` | \`session(action="capture", event_type="decision", title="...", content="...")\` |
1298
- | \`memory\` | \`memory(action="list_events", limit=10)\` |
1299
- | \`graph\` | \`graph(action="dependencies", file_path="...")\` |
1300
-
1301
- ### \u{1F680} FAST PATH: Simple Utility Operations
1302
-
1303
- **For simple utility commands, SKIP the ceremony and just execute directly:**
1304
-
1305
- | Command Type | Just Call | Skip |
1306
- |--------------|-----------|------|
1307
- | List workspaces | \`workspace(action="list")\` | init, context, capture |
1308
- | List projects | \`project(action="list")\` | init, context, capture |
1309
- | Show version | \`help(action="version")\` | init, context, capture |
1310
- | List reminders | \`reminder(action="list")\` | init, context, capture |
1311
- | Check auth | \`help(action="auth")\` | init, context, capture |
1312
-
1313
- **Detect simple operations by these patterns:**
1314
- - "list ...", "show ...", "what are my ...", "get ..."
1315
- - Single-action queries with no context dependency
1316
- - User just wants data, not analysis or coding help
1317
-
1318
- **DO NOT add overhead for utility operations:**
1319
- - \u274C Don't call init just to list workspaces
1320
- - \u274C Don't call context for simple queries
1321
- - \u274C Don't capture "listed workspaces" as an event (that's noise)
1322
-
1323
- **Use full context ceremony ONLY for:**
1324
- - Coding tasks (edit, create, refactor, debug)
1325
- - Search/discovery (finding code, understanding architecture)
1326
- - Tasks where past decisions or lessons matter
1327
-
1328
- ### Lessons (Past Mistakes)
1156
+ }
1157
+ var PRETOOLUSE_HOOK_SCRIPT, USER_PROMPT_HOOK_SCRIPT, MEDIA_AWARE_HOOK_SCRIPT, PRECOMPACT_HOOK_SCRIPT, CLINE_PRETOOLUSE_HOOK_SCRIPT, CLINE_USER_PROMPT_HOOK_SCRIPT, CLINE_POSTTOOLUSE_HOOK_SCRIPT, CLINE_HOOK_WRAPPER, CURSOR_PRETOOLUSE_HOOK_SCRIPT, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT;
1158
+ var init_hooks_config = __esm({
1159
+ "src/hooks-config.ts"() {
1160
+ "use strict";
1161
+ PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
1162
+ """
1163
+ ContextStream PreToolUse Hook for Claude Code
1164
+ Blocks Grep/Glob/Search/Task(Explore)/EnterPlanMode and redirects to ContextStream.
1165
+
1166
+ Only blocks if the current project is indexed in ContextStream.
1167
+ If not indexed, allows local tools through with a suggestion to index.
1168
+ """
1169
+
1170
+ import json
1171
+ import sys
1172
+ import os
1173
+ from pathlib import Path
1174
+ from datetime import datetime, timedelta
1175
+
1176
+ ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
1177
+ INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
1178
+ # Consider index stale after 7 days
1179
+ STALE_THRESHOLD_DAYS = 7
1180
+
1181
+ DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
1182
+
1183
+ def is_discovery_glob(pattern):
1184
+ pattern_lower = pattern.lower()
1185
+ for p in DISCOVERY_PATTERNS:
1186
+ if p in pattern_lower:
1187
+ return True
1188
+ if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
1189
+ return True
1190
+ if "**" in pattern or "*/" in pattern:
1191
+ return True
1192
+ return False
1193
+
1194
+ def is_discovery_grep(file_path):
1195
+ if not file_path or file_path in [".", "./", "*", "**"]:
1196
+ return True
1197
+ if "*" in file_path or "**" in file_path:
1198
+ return True
1199
+ return False
1200
+
1201
+ def is_project_indexed(cwd: str) -> tuple[bool, bool]:
1202
+ """
1203
+ Check if the current directory is in an indexed project.
1204
+ Returns (is_indexed, is_stale).
1205
+ """
1206
+ if not INDEX_STATUS_FILE.exists():
1207
+ return False, False
1208
+
1209
+ try:
1210
+ with open(INDEX_STATUS_FILE, "r") as f:
1211
+ data = json.load(f)
1212
+ except:
1213
+ return False, False
1214
+
1215
+ projects = data.get("projects", {})
1216
+ cwd_path = Path(cwd).resolve()
1217
+
1218
+ # Check if cwd is within any indexed project
1219
+ for project_path, info in projects.items():
1220
+ try:
1221
+ indexed_path = Path(project_path).resolve()
1222
+ # Check if cwd is the project or a subdirectory
1223
+ if cwd_path == indexed_path or indexed_path in cwd_path.parents:
1224
+ # Check if stale
1225
+ indexed_at = info.get("indexed_at")
1226
+ if indexed_at:
1227
+ try:
1228
+ indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
1229
+ if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
1230
+ return True, True # Indexed but stale
1231
+ except:
1232
+ pass
1233
+ return True, False # Indexed and fresh
1234
+ except:
1235
+ continue
1236
+
1237
+ return False, False
1238
+
1239
+ def main():
1240
+ if not ENABLED:
1241
+ sys.exit(0)
1242
+
1243
+ try:
1244
+ data = json.load(sys.stdin)
1245
+ except:
1246
+ sys.exit(0)
1247
+
1248
+ tool = data.get("tool_name", "")
1249
+ inp = data.get("tool_input", {})
1250
+ cwd = data.get("cwd", os.getcwd())
1251
+
1252
+ # Check if project is indexed
1253
+ is_indexed, is_stale = is_project_indexed(cwd)
1254
+
1255
+ if not is_indexed:
1256
+ # Project not indexed - allow local tools but suggest indexing
1257
+ # Don't block, just exit successfully
1258
+ sys.exit(0)
1259
+
1260
+ if is_stale:
1261
+ # Index is stale - allow with warning (printed but not blocking)
1262
+ # Still allow the tool but remind about re-indexing
1263
+ pass # Continue to blocking logic but could add warning
1264
+
1265
+ if tool == "Glob":
1266
+ pattern = inp.get("pattern", "")
1267
+ if is_discovery_glob(pattern):
1268
+ print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of Glob.", file=sys.stderr)
1269
+ sys.exit(2)
1270
+
1271
+ elif tool == "Grep" or tool == "Search":
1272
+ # Block ALL Grep/Search operations - use ContextStream search or Read for specific files
1273
+ pattern = inp.get("pattern", "")
1274
+ path = inp.get("path", "")
1275
+ if pattern:
1276
+ if path and not is_discovery_grep(path):
1277
+ # Specific file - suggest Read instead
1278
+ print(f"STOP: Use Read(\\"{path}\\") to view file content, or mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") for codebase search.", file=sys.stderr)
1279
+ else:
1280
+ print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}.", file=sys.stderr)
1281
+ sys.exit(2)
1282
+
1283
+ elif tool == "Task":
1284
+ if inp.get("subagent_type", "").lower() == "explore":
1285
+ print("STOP: Use mcp__contextstream__search(mode=\\"hybrid\\") instead of Task(Explore).", file=sys.stderr)
1286
+ sys.exit(2)
1287
+ if inp.get("subagent_type", "").lower() == "plan":
1288
+ print("STOP: Use mcp__contextstream__session(action=\\"capture_plan\\") for planning. ContextStream plans persist across sessions.", file=sys.stderr)
1289
+ sys.exit(2)
1290
+
1291
+ elif tool == "EnterPlanMode":
1292
+ print("STOP: Use mcp__contextstream__session(action=\\"capture_plan\\", title=\\"...\\", steps=[...]) instead of EnterPlanMode. ContextStream plans persist across sessions and are searchable.", file=sys.stderr)
1293
+ sys.exit(2)
1294
+
1295
+ sys.exit(0)
1296
+
1297
+ if __name__ == "__main__":
1298
+ main()
1299
+ `;
1300
+ USER_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env python3
1301
+ """
1302
+ ContextStream UserPromptSubmit Hook - Injects reminder on every message.
1303
+ """
1329
1304
 
1330
- - After \`init\`: Check for \`lessons\` field and apply before work
1331
- - Before risky work: \`session(action="get_lessons", query="<topic>")\`
1332
- - On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
1305
+ import json
1306
+ import sys
1307
+ import os
1333
1308
 
1334
- ### Context Pressure & Compaction
1309
+ ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
1335
1310
 
1336
- - If \`context\` returns high/critical \`context_pressure\`: call \`session(action="capture", ...)\` to save state
1337
- - PreCompact hooks automatically save snapshots before compaction (if installed)
1311
+ REMINDER = """[CONTEXTSTREAM RULES]
1312
+ 1. BEFORE Glob/Grep/Read/Search: mcp__contextstream__search(mode="hybrid") FIRST
1313
+ 2. Call context_smart at start of EVERY response
1314
+ 3. Local tools ONLY if ContextStream returns 0 results
1315
+ [END RULES]"""
1338
1316
 
1339
- ### Enhanced Context (Warnings)
1317
+ def main():
1318
+ if not ENABLED:
1319
+ sys.exit(0)
1340
1320
 
1341
- \`context\` returns server-side \`warnings\` for lessons, risky actions, and breaking changes.
1342
- When warnings are present: **STOP**, acknowledge them, explain mitigation, then proceed.
1321
+ try:
1322
+ json.load(sys.stdin)
1323
+ except:
1324
+ sys.exit(0)
1343
1325
 
1344
- ### Automatic Context Restoration
1326
+ print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": REMINDER}}))
1327
+ sys.exit(0)
1345
1328
 
1346
- **Context restoration is now enabled by default.** Every \`init\` call automatically:
1347
- - Restores context from recent snapshots (if available)
1348
- - Returns \`restored_context\` field with snapshot data
1349
- - Sets \`is_post_compact=true\` in response when restoration occurs
1329
+ if __name__ == "__main__":
1330
+ main()
1331
+ `;
1332
+ MEDIA_AWARE_HOOK_SCRIPT = `#!/usr/bin/env python3
1333
+ """
1334
+ ContextStream Media-Aware Hook for Claude Code
1350
1335
 
1351
- **No special handling needed after compaction** - just call \`init\` normally.
1336
+ Detects media-related prompts and injects context about the media tool.
1337
+ """
1352
1338
 
1353
- To disable automatic restoration:
1354
- - Pass \`is_post_compact=false\` in the API call
1355
- - Or set \`CONTEXTSTREAM_RESTORE_CONTEXT=false\` environment variable
1339
+ import json
1340
+ import sys
1341
+ import os
1342
+ import re
1356
1343
 
1357
- ### Notices - MUST HANDLE IMMEDIATELY
1344
+ ENABLED = os.environ.get("CONTEXTSTREAM_MEDIA_HOOK_ENABLED", "true").lower() == "true"
1358
1345
 
1359
- - **[VERSION_NOTICE]**: Tell the user about the update and command to run
1360
- - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
1361
- - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
1346
+ # Media patterns (case-insensitive)
1347
+ PATTERNS = [
1348
+ r"\\b(video|videos|clip|clips|footage|keyframe)s?\\b",
1349
+ r"\\b(remotion|timeline|video\\s*edit)\\b",
1350
+ r"\\b(image|images|photo|photos|picture|thumbnail)s?\\b",
1351
+ r"\\b(audio|podcast|transcript|transcription|voice)\\b",
1352
+ r"\\b(media|asset|assets|creative|b-roll)\\b",
1353
+ r"\\b(find|search|show).*(clip|video|image|audio|footage|media)\\b",
1354
+ ]
1362
1355
 
1363
- ### Plans & Tasks
1356
+ COMPILED = [re.compile(p, re.IGNORECASE) for p in PATTERNS]
1364
1357
 
1365
- When user asks for a plan, use ContextStream (not EnterPlanMode):
1366
- 1. \`session(action="capture_plan", title="...", steps=[...])\`
1367
- 2. \`memory(action="create_task", title="...", plan_id="<id>")\`
1358
+ MEDIA_CONTEXT = """[MEDIA TOOLS AVAILABLE]
1359
+ Your workspace may have indexed media. Use ContextStream media tools:
1368
1360
 
1369
- ### Workspace-Only Mode (Multi-Project Folders)
1361
+ - **Search**: \`mcp__contextstream__media(action="search", query="description")\`
1362
+ - **Get clip**: \`mcp__contextstream__media(action="get_clip", content_id="...", start="1:34", end="2:15", output_format="remotion|ffmpeg|raw")\`
1363
+ - **List assets**: \`mcp__contextstream__media(action="list")\`
1364
+ - **Index**: \`mcp__contextstream__media(action="index", file_path="...", content_type="video|audio|image|document")\`
1370
1365
 
1371
- If working in a parent folder containing multiple projects:
1372
- \`\`\`
1373
- init(folder_path="...", skip_project_creation=true)
1374
- \`\`\`
1366
+ For Remotion: use \`output_format="remotion"\` to get frame-based props.
1367
+ [END MEDIA TOOLS]"""
1368
+
1369
+ def matches(text):
1370
+ return any(p.search(text) for p in COMPILED)
1371
+
1372
+ def main():
1373
+ if not ENABLED:
1374
+ sys.exit(0)
1375
+
1376
+ try:
1377
+ data = json.load(sys.stdin)
1378
+ except:
1379
+ sys.exit(0)
1380
+
1381
+ prompt = data.get("prompt", "")
1382
+ if not prompt:
1383
+ session = data.get("session", {})
1384
+ for msg in reversed(session.get("messages", [])):
1385
+ if msg.get("role") == "user":
1386
+ content = msg.get("content", "")
1387
+ prompt = content if isinstance(content, str) else ""
1388
+ if isinstance(content, list):
1389
+ for b in content:
1390
+ if isinstance(b, dict) and b.get("type") == "text":
1391
+ prompt = b.get("text", "")
1392
+ break
1393
+ break
1394
+
1395
+ if not prompt or not matches(prompt):
1396
+ sys.exit(0)
1397
+
1398
+ print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": MEDIA_CONTEXT}}))
1399
+ sys.exit(0)
1400
+
1401
+ if __name__ == "__main__":
1402
+ main()
1403
+ `;
1404
+ PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
1405
+ """
1406
+ ContextStream PreCompact Hook for Claude Code
1407
+
1408
+ Runs BEFORE conversation context is compacted (manual via /compact or automatic).
1409
+ Automatically saves conversation state to ContextStream by parsing the transcript.
1410
+
1411
+ Input (via stdin):
1412
+ {
1413
+ "session_id": "...",
1414
+ "transcript_path": "/path/to/transcript.jsonl",
1415
+ "permission_mode": "default",
1416
+ "hook_event_name": "PreCompact",
1417
+ "trigger": "manual" | "auto",
1418
+ "custom_instructions": "..."
1419
+ }
1420
+
1421
+ Output (to stdout):
1422
+ {
1423
+ "hookSpecificOutput": {
1424
+ "hookEventName": "PreCompact",
1425
+ "additionalContext": "... status message ..."
1426
+ }
1427
+ }
1428
+ """
1429
+
1430
+ import json
1431
+ import sys
1432
+ import os
1433
+ import re
1434
+ import urllib.request
1435
+ import urllib.error
1436
+
1437
+ ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
1438
+ AUTO_SAVE = os.environ.get("CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE", "true").lower() == "true"
1439
+ API_URL = os.environ.get("CONTEXTSTREAM_API_URL", "https://api.contextstream.io")
1440
+ API_KEY = os.environ.get("CONTEXTSTREAM_API_KEY", "")
1441
+
1442
+ WORKSPACE_ID = None
1443
+
1444
+ def load_config_from_mcp_json(cwd):
1445
+ """Load API config from .mcp.json if env vars not set."""
1446
+ global API_URL, API_KEY, WORKSPACE_ID
1447
+
1448
+ # Try to find .mcp.json and .contextstream/config.json in cwd or parent directories
1449
+ search_dir = cwd
1450
+ for _ in range(5): # Search up to 5 levels
1451
+ # Load API config from .mcp.json
1452
+ if not API_KEY:
1453
+ mcp_path = os.path.join(search_dir, ".mcp.json")
1454
+ if os.path.exists(mcp_path):
1455
+ try:
1456
+ with open(mcp_path, 'r') as f:
1457
+ config = json.load(f)
1458
+ servers = config.get("mcpServers", {})
1459
+ cs_config = servers.get("contextstream", {})
1460
+ env = cs_config.get("env", {})
1461
+ if env.get("CONTEXTSTREAM_API_KEY"):
1462
+ API_KEY = env["CONTEXTSTREAM_API_KEY"]
1463
+ if env.get("CONTEXTSTREAM_API_URL"):
1464
+ API_URL = env["CONTEXTSTREAM_API_URL"]
1465
+ except:
1466
+ pass
1467
+
1468
+ # Load workspace_id from .contextstream/config.json
1469
+ if not WORKSPACE_ID:
1470
+ cs_config_path = os.path.join(search_dir, ".contextstream", "config.json")
1471
+ if os.path.exists(cs_config_path):
1472
+ try:
1473
+ with open(cs_config_path, 'r') as f:
1474
+ cs_config = json.load(f)
1475
+ if cs_config.get("workspace_id"):
1476
+ WORKSPACE_ID = cs_config["workspace_id"]
1477
+ except:
1478
+ pass
1479
+
1480
+ parent = os.path.dirname(search_dir)
1481
+ if parent == search_dir:
1482
+ break
1483
+ search_dir = parent
1484
+
1485
+ def parse_transcript(transcript_path):
1486
+ """Parse transcript to extract active files, decisions, and context."""
1487
+ active_files = set()
1488
+ recent_messages = []
1489
+ tool_calls = []
1490
+
1491
+ try:
1492
+ with open(transcript_path, 'r') as f:
1493
+ for line in f:
1494
+ try:
1495
+ entry = json.loads(line.strip())
1496
+ msg_type = entry.get("type", "")
1497
+
1498
+ # Extract files from tool calls
1499
+ if msg_type == "tool_use":
1500
+ tool_name = entry.get("name", "")
1501
+ tool_input = entry.get("input", {})
1502
+ tool_calls.append({"name": tool_name, "input": tool_input})
1503
+
1504
+ # Extract file paths from common tools
1505
+ if tool_name in ["Read", "Write", "Edit", "NotebookEdit"]:
1506
+ file_path = tool_input.get("file_path") or tool_input.get("notebook_path")
1507
+ if file_path:
1508
+ active_files.add(file_path)
1509
+ elif tool_name == "Glob":
1510
+ pattern = tool_input.get("pattern", "")
1511
+ if pattern:
1512
+ active_files.add(f"[glob:{pattern}]")
1513
+
1514
+ # Collect recent assistant messages for summary
1515
+ if msg_type == "assistant" and entry.get("content"):
1516
+ content = entry.get("content", "")
1517
+ if isinstance(content, str) and len(content) > 50:
1518
+ recent_messages.append(content[:500])
1519
+
1520
+ except json.JSONDecodeError:
1521
+ continue
1522
+ except Exception as e:
1523
+ pass
1375
1524
 
1376
- This enables workspace-level memory and context without project-specific indexing.
1377
- Use for monorepos or folders with multiple independent projects.
1525
+ return {
1526
+ "active_files": list(active_files)[-20:], # Last 20 files
1527
+ "tool_call_count": len(tool_calls),
1528
+ "message_count": len(recent_messages),
1529
+ "last_tools": [t["name"] for t in tool_calls[-10:]], # Last 10 tool names
1530
+ }
1531
+
1532
+ def save_snapshot(session_id, transcript_data, trigger):
1533
+ """Save snapshot to ContextStream API."""
1534
+ if not API_KEY:
1535
+ return False, "No API key configured"
1536
+
1537
+ snapshot_content = {
1538
+ "session_id": session_id,
1539
+ "trigger": trigger,
1540
+ "captured_at": None, # API will set timestamp
1541
+ "active_files": transcript_data.get("active_files", []),
1542
+ "tool_call_count": transcript_data.get("tool_call_count", 0),
1543
+ "last_tools": transcript_data.get("last_tools", []),
1544
+ "auto_captured": True,
1545
+ }
1546
+
1547
+ payload = {
1548
+ "event_type": "session_snapshot",
1549
+ "title": f"Auto Pre-compaction Snapshot ({trigger})",
1550
+ "content": json.dumps(snapshot_content),
1551
+ "importance": "high",
1552
+ "tags": ["session_snapshot", "pre_compaction", "auto_captured"],
1553
+ "source_type": "hook",
1554
+ }
1555
+
1556
+ # Add workspace_id if available
1557
+ if WORKSPACE_ID:
1558
+ payload["workspace_id"] = WORKSPACE_ID
1559
+
1560
+ try:
1561
+ req = urllib.request.Request(
1562
+ f"{API_URL}/api/v1/memory/events",
1563
+ data=json.dumps(payload).encode('utf-8'),
1564
+ headers={
1565
+ "Content-Type": "application/json",
1566
+ "X-API-Key": API_KEY,
1567
+ },
1568
+ method="POST"
1569
+ )
1570
+ with urllib.request.urlopen(req, timeout=5) as resp:
1571
+ return True, "Snapshot saved"
1572
+ except urllib.error.URLError as e:
1573
+ return False, str(e)
1574
+ except Exception as e:
1575
+ return False, str(e)
1576
+
1577
+ def main():
1578
+ if not ENABLED:
1579
+ sys.exit(0)
1580
+
1581
+ try:
1582
+ data = json.load(sys.stdin)
1583
+ except:
1584
+ sys.exit(0)
1585
+
1586
+ # Load config from .mcp.json if env vars not set
1587
+ cwd = data.get("cwd", os.getcwd())
1588
+ load_config_from_mcp_json(cwd)
1589
+
1590
+ session_id = data.get("session_id", "unknown")
1591
+ transcript_path = data.get("transcript_path", "")
1592
+ trigger = data.get("trigger", "unknown")
1593
+ custom_instructions = data.get("custom_instructions", "")
1594
+
1595
+ # Parse transcript for context
1596
+ transcript_data = {}
1597
+ if transcript_path and os.path.exists(transcript_path):
1598
+ transcript_data = parse_transcript(transcript_path)
1599
+
1600
+ # Auto-save snapshot if enabled
1601
+ auto_save_status = ""
1602
+ if AUTO_SAVE and API_KEY:
1603
+ success, msg = save_snapshot(session_id, transcript_data, trigger)
1604
+ if success:
1605
+ auto_save_status = f"\\n[ContextStream: Auto-saved snapshot with {len(transcript_data.get('active_files', []))} active files]"
1606
+ else:
1607
+ auto_save_status = f"\\n[ContextStream: Auto-save failed - {msg}]"
1608
+
1609
+ # Build context injection for the AI (backup in case auto-save fails)
1610
+ files_list = ", ".join(transcript_data.get("active_files", [])[:5]) or "none detected"
1611
+ context = f"""[CONTEXT COMPACTION - {trigger.upper()}]{auto_save_status}
1612
+
1613
+ Active files detected: {files_list}
1614
+ Tool calls in session: {transcript_data.get('tool_call_count', 0)}
1615
+
1616
+ After compaction, call session_init(is_post_compact=true) to restore context.
1617
+ {f"User instructions: {custom_instructions}" if custom_instructions else ""}"""
1618
+
1619
+ output = {
1620
+ "hookSpecificOutput": {
1621
+ "hookEventName": "PreCompact",
1622
+ "additionalContext": context
1623
+ }
1624
+ }
1625
+
1626
+ print(json.dumps(output))
1627
+ sys.exit(0)
1628
+
1629
+ if __name__ == "__main__":
1630
+ main()
1631
+ `;
1632
+ CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
1633
+ """
1634
+ ContextStream PreToolUse Hook for Cline
1635
+ Blocks discovery tools and redirects to ContextStream search.
1636
+
1637
+ Cline hooks use JSON output format:
1638
+ {
1639
+ "cancel": true/false,
1640
+ "errorMessage": "optional error description",
1641
+ "contextModification": "optional text to inject"
1642
+ }
1643
+ """
1644
+
1645
+ import json
1646
+ import sys
1647
+ import os
1648
+ from pathlib import Path
1649
+ from datetime import datetime, timedelta
1650
+
1651
+ ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
1652
+ INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
1653
+ STALE_THRESHOLD_DAYS = 7
1654
+
1655
+ DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
1656
+
1657
+ def is_discovery_glob(pattern):
1658
+ pattern_lower = pattern.lower()
1659
+ for p in DISCOVERY_PATTERNS:
1660
+ if p in pattern_lower:
1661
+ return True
1662
+ if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
1663
+ return True
1664
+ if "**" in pattern or "*/" in pattern:
1665
+ return True
1666
+ return False
1667
+
1668
+ def is_discovery_grep(file_path):
1669
+ if not file_path or file_path in [".", "./", "*", "**"]:
1670
+ return True
1671
+ if "*" in file_path or "**" in file_path:
1672
+ return True
1673
+ return False
1674
+
1675
+ def is_project_indexed(workspace_roots):
1676
+ """Check if any workspace root is in an indexed project."""
1677
+ if not INDEX_STATUS_FILE.exists():
1678
+ return False, False
1679
+
1680
+ try:
1681
+ with open(INDEX_STATUS_FILE, "r") as f:
1682
+ data = json.load(f)
1683
+ except:
1684
+ return False, False
1685
+
1686
+ projects = data.get("projects", {})
1687
+
1688
+ for workspace in workspace_roots:
1689
+ cwd_path = Path(workspace).resolve()
1690
+ for project_path, info in projects.items():
1691
+ try:
1692
+ indexed_path = Path(project_path).resolve()
1693
+ if cwd_path == indexed_path or indexed_path in cwd_path.parents:
1694
+ indexed_at = info.get("indexed_at")
1695
+ if indexed_at:
1696
+ try:
1697
+ indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
1698
+ if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
1699
+ return True, True
1700
+ except:
1701
+ pass
1702
+ return True, False
1703
+ except:
1704
+ continue
1705
+ return False, False
1706
+
1707
+ def output_allow(context_mod=None):
1708
+ result = {"cancel": False}
1709
+ if context_mod:
1710
+ result["contextModification"] = context_mod
1711
+ print(json.dumps(result))
1712
+ sys.exit(0)
1713
+
1714
+ def output_block(error_msg, context_mod=None):
1715
+ result = {"cancel": True, "errorMessage": error_msg}
1716
+ if context_mod:
1717
+ result["contextModification"] = context_mod
1718
+ print(json.dumps(result))
1719
+ sys.exit(0)
1720
+
1721
+ def main():
1722
+ if not ENABLED:
1723
+ output_allow()
1724
+
1725
+ try:
1726
+ data = json.load(sys.stdin)
1727
+ except:
1728
+ output_allow()
1729
+
1730
+ hook_name = data.get("hookName", "")
1731
+ if hook_name != "PreToolUse":
1732
+ output_allow()
1733
+
1734
+ tool = data.get("toolName", "")
1735
+ params = data.get("toolParameters", {})
1736
+ workspace_roots = data.get("workspaceRoots", [])
1737
+
1738
+ # Check if project is indexed
1739
+ is_indexed, is_stale = is_project_indexed(workspace_roots)
1740
+ if not is_indexed:
1741
+ output_allow()
1742
+
1743
+ # Check for discovery patterns
1744
+ if tool == "list_files" or tool == "search_files":
1745
+ pattern = params.get("path", "") or params.get("regex", "")
1746
+ if is_discovery_glob(pattern) or is_discovery_grep(pattern):
1747
+ output_block(
1748
+ f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
1749
+ "ContextStream search is indexed and faster. Only use local tools if ContextStream returns 0 results.",
1750
+ "[CONTEXTSTREAM] Use ContextStream search for code discovery."
1751
+ )
1752
+
1753
+ elif tool == "read_file":
1754
+ # Allow read_file by default - blocking discovery at search level is enough
1755
+ pass
1756
+
1757
+ output_allow()
1758
+
1759
+ if __name__ == "__main__":
1760
+ main()
1761
+ `;
1762
+ CLINE_USER_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env python3
1763
+ """
1764
+ ContextStream UserPromptSubmit Hook for Cline
1765
+ Injects reminder about ContextStream rules on every message.
1766
+ """
1378
1767
 
1379
- Full docs: https://contextstream.io/docs/mcp/tools
1380
- `.trim();
1381
- TEMPLATES = {
1382
- codex: {
1383
- filename: "AGENTS.md",
1384
- description: "Codex CLI agent instructions",
1385
- build: (rules) => `# Codex CLI Instructions
1386
- ${rules}
1387
- `
1388
- },
1389
- cursor: {
1390
- filename: ".cursorrules",
1391
- description: "Cursor AI rules",
1392
- build: (rules) => `# Cursor Rules
1393
- ${rules}
1394
- `
1395
- },
1396
- cline: {
1397
- filename: ".clinerules",
1398
- description: "Cline AI rules",
1399
- build: (rules) => `# Cline Rules
1400
- ${rules}
1401
- `
1402
- },
1403
- kilo: {
1404
- filename: ".kilocode/rules/contextstream.md",
1405
- description: "Kilo Code AI rules",
1406
- build: (rules) => `# Kilo Code Rules
1407
- ${rules}
1408
- `
1409
- },
1410
- roo: {
1411
- filename: ".roo/rules/contextstream.md",
1412
- description: "Roo Code AI rules",
1413
- build: (rules) => `# Roo Code Rules
1414
- ${rules}
1415
- `
1416
- },
1417
- claude: {
1418
- filename: "CLAUDE.md",
1419
- description: "Claude Code instructions",
1420
- build: (rules) => `# Claude Code Instructions
1421
- ${rules}
1422
- `
1423
- },
1424
- aider: {
1425
- filename: ".aider.conf.yml",
1426
- description: "Aider configuration with system prompt",
1427
- build: (rules) => `# Aider Configuration
1428
- # Note: Aider uses different config format - this adds to the system prompt
1768
+ import json
1769
+ import sys
1770
+ import os
1429
1771
 
1430
- # Add ContextStream guidance to conventions
1431
- conventions: |
1432
- ${rules.split("\n").map((line) => " " + line).join("\n")}
1433
- `
1434
- },
1435
- antigravity: {
1436
- filename: "GEMINI.md",
1437
- description: "Google Antigravity AI rules",
1438
- build: (rules) => `# Antigravity Agent Rules
1439
- ${rules}
1440
- `
1441
- }
1442
- };
1772
+ ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
1773
+
1774
+ REMINDER = """[CONTEXTSTREAM RULES]
1775
+ 1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="hybrid") FIRST
1776
+ 2. Call context_smart at start of EVERY response
1777
+ 3. Local tools ONLY if ContextStream returns 0 results
1778
+ [END RULES]"""
1779
+
1780
+ def main():
1781
+ if not ENABLED:
1782
+ print(json.dumps({"cancel": False}))
1783
+ sys.exit(0)
1784
+
1785
+ try:
1786
+ json.load(sys.stdin)
1787
+ except:
1788
+ print(json.dumps({"cancel": False}))
1789
+ sys.exit(0)
1790
+
1791
+ print(json.dumps({
1792
+ "cancel": False,
1793
+ "contextModification": REMINDER
1794
+ }))
1795
+ sys.exit(0)
1796
+
1797
+ if __name__ == "__main__":
1798
+ main()
1799
+ `;
1800
+ CLINE_POSTTOOLUSE_HOOK_SCRIPT = `#!/bin/bash
1801
+ # ContextStream PostToolUse Hook for Cline/Roo/Kilo Code
1802
+ # Indexes files after Edit/Write/NotebookEdit operations for real-time search updates.
1803
+ #
1804
+ # The hook receives JSON on stdin with tool_name and toolParameters.
1805
+ # Only runs for write operations (write_to_file, edit_file).
1806
+
1807
+ TOOL_NAME=$(cat | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('toolName', d.get('tool_name', '')))" 2>/dev/null)
1808
+
1809
+ case "$TOOL_NAME" in
1810
+ write_to_file|edit_file|Write|Edit|NotebookEdit)
1811
+ npx @contextstream/mcp-server hook post-write
1812
+ ;;
1813
+ esac
1814
+
1815
+ exit 0
1816
+ `;
1817
+ CLINE_HOOK_WRAPPER = (hookName) => `#!/bin/bash
1818
+ # ContextStream ${hookName} Hook Wrapper for Cline/Roo/Kilo Code
1819
+ # Calls the Node.js hook via npx
1820
+ exec npx @contextstream/mcp-server hook ${hookName}
1821
+ `;
1822
+ CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
1823
+ """
1824
+ ContextStream PreToolUse Hook for Cursor
1825
+ Blocks discovery tools and redirects to ContextStream search.
1826
+
1827
+ Cursor hooks use JSON output format:
1828
+ {
1829
+ "decision": "allow" | "deny",
1830
+ "reason": "optional error description"
1831
+ }
1832
+ """
1833
+
1834
+ import json
1835
+ import sys
1836
+ import os
1837
+ from pathlib import Path
1838
+ from datetime import datetime, timedelta
1839
+
1840
+ ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
1841
+ INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
1842
+ STALE_THRESHOLD_DAYS = 7
1843
+
1844
+ DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
1845
+
1846
+ def is_discovery_glob(pattern):
1847
+ pattern_lower = pattern.lower()
1848
+ for p in DISCOVERY_PATTERNS:
1849
+ if p in pattern_lower:
1850
+ return True
1851
+ if pattern_lower.startswith("**/*.") or pattern_lower.startswith("**/"):
1852
+ return True
1853
+ if "**" in pattern or "*/" in pattern:
1854
+ return True
1855
+ return False
1856
+
1857
+ def is_discovery_grep(file_path):
1858
+ if not file_path or file_path in [".", "./", "*", "**"]:
1859
+ return True
1860
+ if "*" in file_path or "**" in file_path:
1861
+ return True
1862
+ return False
1863
+
1864
+ def is_project_indexed(workspace_roots):
1865
+ """Check if any workspace root is in an indexed project."""
1866
+ if not INDEX_STATUS_FILE.exists():
1867
+ return False, False
1868
+
1869
+ try:
1870
+ with open(INDEX_STATUS_FILE, "r") as f:
1871
+ data = json.load(f)
1872
+ except:
1873
+ return False, False
1874
+
1875
+ projects = data.get("projects", {})
1876
+
1877
+ for workspace in workspace_roots:
1878
+ cwd_path = Path(workspace).resolve()
1879
+ for project_path, info in projects.items():
1880
+ try:
1881
+ indexed_path = Path(project_path).resolve()
1882
+ if cwd_path == indexed_path or indexed_path in cwd_path.parents:
1883
+ indexed_at = info.get("indexed_at")
1884
+ if indexed_at:
1885
+ try:
1886
+ indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
1887
+ if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
1888
+ return True, True
1889
+ except:
1890
+ pass
1891
+ return True, False
1892
+ except:
1893
+ continue
1894
+ return False, False
1895
+
1896
+ def output_allow():
1897
+ print(json.dumps({"decision": "allow"}))
1898
+ sys.exit(0)
1899
+
1900
+ def output_deny(reason):
1901
+ print(json.dumps({"decision": "deny", "reason": reason}))
1902
+ sys.exit(0)
1903
+
1904
+ def main():
1905
+ if not ENABLED:
1906
+ output_allow()
1907
+
1908
+ try:
1909
+ data = json.load(sys.stdin)
1910
+ except:
1911
+ output_allow()
1912
+
1913
+ hook_name = data.get("hook_event_name", "")
1914
+ if hook_name != "preToolUse":
1915
+ output_allow()
1916
+
1917
+ tool = data.get("tool_name", "")
1918
+ params = data.get("tool_input", {}) or data.get("parameters", {})
1919
+ workspace_roots = data.get("workspace_roots", [])
1920
+
1921
+ # Check if project is indexed
1922
+ is_indexed, _ = is_project_indexed(workspace_roots)
1923
+ if not is_indexed:
1924
+ output_allow()
1925
+
1926
+ # Check for Cursor tools
1927
+ if tool in ["Glob", "glob", "list_files"]:
1928
+ pattern = params.get("pattern", "") or params.get("path", "")
1929
+ if is_discovery_glob(pattern):
1930
+ output_deny(
1931
+ f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
1932
+ "ContextStream search is indexed and faster."
1933
+ )
1934
+
1935
+ elif tool in ["Grep", "grep", "search_files", "ripgrep"]:
1936
+ pattern = params.get("pattern", "") or params.get("regex", "")
1937
+ file_path = params.get("path", "")
1938
+ if is_discovery_grep(file_path):
1939
+ output_deny(
1940
+ f"Use mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") instead of {tool}. "
1941
+ "ContextStream search is indexed and faster."
1942
+ )
1943
+
1944
+ output_allow()
1945
+
1946
+ if __name__ == "__main__":
1947
+ main()
1948
+ `;
1949
+ CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT = `#!/usr/bin/env python3
1950
+ """
1951
+ ContextStream BeforeSubmitPrompt Hook for Cursor
1952
+ Injects reminder about ContextStream rules.
1953
+ """
1954
+
1955
+ import json
1956
+ import sys
1957
+ import os
1958
+
1959
+ ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
1960
+
1961
+ def main():
1962
+ if not ENABLED:
1963
+ print(json.dumps({"continue": True}))
1964
+ sys.exit(0)
1965
+
1966
+ try:
1967
+ json.load(sys.stdin)
1968
+ except:
1969
+ print(json.dumps({"continue": True}))
1970
+ sys.exit(0)
1971
+
1972
+ print(json.dumps({
1973
+ "continue": True,
1974
+ "user_message": "[CONTEXTSTREAM] Search with mcp__contextstream__search before using Glob/Grep/Read"
1975
+ }))
1976
+ sys.exit(0)
1977
+
1978
+ if __name__ == "__main__":
1979
+ main()
1980
+ `;
1443
1981
  }
1444
1982
  });
1445
1983
 
@@ -2502,14 +3040,49 @@ function extractCwd3(input) {
2502
3040
  if (input.cwd) return input.cwd;
2503
3041
  return process.cwd();
2504
3042
  }
2505
- async function generateRulesForFolder(folderPath) {
2506
- const { generateAllRuleFiles: generateAllRuleFiles2 } = await Promise.resolve().then(() => (init_rules_templates(), rules_templates_exports));
2507
- await generateAllRuleFiles2({
2508
- folderPath,
2509
- editors: ["cursor", "cline", "kilo", "roo", "claude", "aider", "codex"],
2510
- overwriteExisting: true,
2511
- mode: "minimal"
2512
- // Use minimal mode for auto-updates
3043
+ function hasPythonHooks(settingsPath) {
3044
+ try {
3045
+ if (!fs11.existsSync(settingsPath)) return false;
3046
+ const content = fs11.readFileSync(settingsPath, "utf-8");
3047
+ const settings = JSON.parse(content);
3048
+ const hooks = settings.hooks;
3049
+ if (!hooks) return false;
3050
+ for (const hookType of Object.keys(hooks)) {
3051
+ const matchers = hooks[hookType];
3052
+ if (!Array.isArray(matchers)) continue;
3053
+ for (const matcher of matchers) {
3054
+ const hookList = matcher.hooks;
3055
+ if (!Array.isArray(hookList)) continue;
3056
+ for (const hook of hookList) {
3057
+ const cmd = hook.command || "";
3058
+ if (cmd.includes("python3") && cmd.includes("contextstream")) {
3059
+ return true;
3060
+ }
3061
+ }
3062
+ }
3063
+ }
3064
+ return false;
3065
+ } catch {
3066
+ return false;
3067
+ }
3068
+ }
3069
+ function detectPythonHooks(cwd) {
3070
+ const globalSettingsPath = path12.join(homedir9(), ".claude", "settings.json");
3071
+ const projectSettingsPath = path12.join(cwd, ".claude", "settings.json");
3072
+ return {
3073
+ global: hasPythonHooks(globalSettingsPath),
3074
+ project: hasPythonHooks(projectSettingsPath)
3075
+ };
3076
+ }
3077
+ async function upgradeHooksForFolder(folderPath) {
3078
+ const { installClaudeCodeHooks: installClaudeCodeHooks3 } = await Promise.resolve().then(() => (init_hooks_config(), hooks_config_exports));
3079
+ await installClaudeCodeHooks3({
3080
+ scope: "both",
3081
+ projectPath: folderPath,
3082
+ includePreCompact: true,
3083
+ includeMediaAware: true,
3084
+ includePostWrite: true,
3085
+ includeAutoRules: true
2513
3086
  });
2514
3087
  }
2515
3088
  async function runAutoRulesHook() {
@@ -2537,14 +3110,17 @@ async function runAutoRulesHook() {
2537
3110
  if (!isContextTool) {
2538
3111
  process.exit(0);
2539
3112
  }
3113
+ const cwd = extractCwd3(input);
3114
+ const pythonHooks = detectPythonHooks(cwd);
3115
+ const hasPythonHooksToUpgrade = pythonHooks.global || pythonHooks.project;
2540
3116
  const rulesNotice = extractRulesNotice(input);
2541
- if (!rulesNotice || rulesNotice.status === "current") {
3117
+ const rulesNeedUpdate = rulesNotice && rulesNotice.status !== "current";
3118
+ if (!hasPythonHooksToUpgrade && !rulesNeedUpdate) {
2542
3119
  process.exit(0);
2543
3120
  }
2544
- const cwd = extractCwd3(input);
2545
- const folderPath = rulesNotice.update_args?.folder_path || cwd;
3121
+ const folderPath = rulesNotice?.update_args?.folder_path || cwd;
2546
3122
  try {
2547
- await generateRulesForFolder(folderPath);
3123
+ await upgradeHooksForFolder(folderPath);
2548
3124
  markAsRan();
2549
3125
  } catch {
2550
3126
  }
@@ -6611,8 +7187,176 @@ var coerce = {
6611
7187
  };
6612
7188
  var NEVER = INVALID;
6613
7189
 
7190
+ // src/version.ts
7191
+ import { createRequire } from "module";
7192
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
7193
+ import { homedir } from "os";
7194
+ import { join } from "path";
7195
+ var UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
7196
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
7197
+ function getVersion() {
7198
+ try {
7199
+ const require2 = createRequire(import.meta.url);
7200
+ const pkg = require2("../package.json");
7201
+ const version = pkg?.version;
7202
+ if (typeof version === "string" && version.trim()) return version.trim();
7203
+ } catch {
7204
+ }
7205
+ return "unknown";
7206
+ }
7207
+ var VERSION = getVersion();
7208
+ function compareVersions(v1, v2) {
7209
+ const parts1 = v1.split(".").map(Number);
7210
+ const parts2 = v2.split(".").map(Number);
7211
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
7212
+ const p1 = parts1[i] ?? 0;
7213
+ const p2 = parts2[i] ?? 0;
7214
+ if (p1 < p2) return -1;
7215
+ if (p1 > p2) return 1;
7216
+ }
7217
+ return 0;
7218
+ }
7219
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7220
+ var latestVersionPromise = null;
7221
+ function getCacheFilePath() {
7222
+ return join(homedir(), ".contextstream", "version-cache.json");
7223
+ }
7224
+ function readCache() {
7225
+ try {
7226
+ const cacheFile = getCacheFilePath();
7227
+ if (!existsSync(cacheFile)) return null;
7228
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
7229
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
7230
+ return data;
7231
+ } catch {
7232
+ return null;
7233
+ }
7234
+ }
7235
+ function writeCache(latestVersion) {
7236
+ try {
7237
+ const configDir = join(homedir(), ".contextstream");
7238
+ if (!existsSync(configDir)) {
7239
+ mkdirSync(configDir, { recursive: true });
7240
+ }
7241
+ const cacheFile = getCacheFilePath();
7242
+ writeFileSync(
7243
+ cacheFile,
7244
+ JSON.stringify({
7245
+ latestVersion,
7246
+ checkedAt: Date.now()
7247
+ })
7248
+ );
7249
+ } catch {
7250
+ }
7251
+ }
7252
+ async function fetchLatestVersion() {
7253
+ try {
7254
+ const controller = new AbortController();
7255
+ const timeout = setTimeout(() => controller.abort(), 5e3);
7256
+ const response = await fetch(NPM_LATEST_URL, {
7257
+ signal: controller.signal,
7258
+ headers: { Accept: "application/json" }
7259
+ });
7260
+ clearTimeout(timeout);
7261
+ if (!response.ok) return null;
7262
+ const data = await response.json();
7263
+ return typeof data.version === "string" ? data.version : null;
7264
+ } catch {
7265
+ return null;
7266
+ }
7267
+ }
7268
+ async function resolveLatestVersion() {
7269
+ const cached = readCache();
7270
+ if (cached) return cached.latestVersion;
7271
+ if (!latestVersionPromise) {
7272
+ latestVersionPromise = fetchLatestVersion().finally(() => {
7273
+ latestVersionPromise = null;
7274
+ });
7275
+ }
7276
+ const latestVersion = await latestVersionPromise;
7277
+ if (latestVersion) {
7278
+ writeCache(latestVersion);
7279
+ }
7280
+ return latestVersion;
7281
+ }
7282
+ async function checkForUpdates() {
7283
+ const notice = await getUpdateNotice();
7284
+ if (notice?.behind) {
7285
+ showUpdateWarning(notice.current, notice.latest);
7286
+ }
7287
+ }
7288
+ function showUpdateWarning(currentVersion, latestVersion) {
7289
+ console.error("");
7290
+ console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
7291
+ console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
7292
+ console.error("");
7293
+ console.error(` Run: ${UPGRADE_COMMAND}`);
7294
+ console.error("");
7295
+ console.error(" Then restart your AI tool to use the new version.");
7296
+ console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
7297
+ console.error("");
7298
+ }
7299
+ async function getUpdateNotice() {
7300
+ const currentVersion = VERSION;
7301
+ if (currentVersion === "unknown") return null;
7302
+ try {
7303
+ const latestVersion = await resolveLatestVersion();
7304
+ if (!latestVersion) return null;
7305
+ if (compareVersions(currentVersion, latestVersion) < 0) {
7306
+ return {
7307
+ current: currentVersion,
7308
+ latest: latestVersion,
7309
+ behind: true,
7310
+ upgrade_command: UPGRADE_COMMAND
7311
+ };
7312
+ }
7313
+ } catch {
7314
+ }
7315
+ return null;
7316
+ }
7317
+ function getVersionsBehind(current, latest) {
7318
+ try {
7319
+ const currentParts = current.split(".").map(Number);
7320
+ const latestParts = latest.split(".").map(Number);
7321
+ if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
7322
+ return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
7323
+ }
7324
+ const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
7325
+ const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
7326
+ if (minorDiff > 0) {
7327
+ return minorDiff;
7328
+ } else if (minorDiff === 0 && patchDiff > 0) {
7329
+ return 1;
7330
+ }
7331
+ return 0;
7332
+ } catch {
7333
+ return 0;
7334
+ }
7335
+ }
7336
+ function getVersionWarning(notice) {
7337
+ if (!notice?.behind) return null;
7338
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
7339
+ if (versionsBehind >= 3) {
7340
+ return `[VERSION_CRITICAL] MCP server is ${versionsBehind} versions behind (${notice.current} \u2192 ${notice.latest}). Update immediately: ${notice.upgrade_command}`;
7341
+ } else if (versionsBehind >= 1) {
7342
+ return `[VERSION_NOTICE] MCP server update available (${notice.current} \u2192 ${notice.latest}). Run: ${notice.upgrade_command}`;
7343
+ }
7344
+ return null;
7345
+ }
7346
+ function getVersionInstructions(notice) {
7347
+ if (!notice?.behind) return null;
7348
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
7349
+ if (versionsBehind >= 5) {
7350
+ return `\u26A0\uFE0F CRITICAL: User's MCP is ${versionsBehind} versions behind. Mention update in EVERY response until updated.`;
7351
+ } else if (versionsBehind >= 3) {
7352
+ return `\u26A0\uFE0F User's MCP is ${versionsBehind} versions behind. Remind about update every 2-3 messages.`;
7353
+ } else if (versionsBehind >= 1) {
7354
+ return `Note: MCP update available (${notice.current}\u2192${notice.latest}). Mention once at session start.`;
7355
+ }
7356
+ return null;
7357
+ }
7358
+
6614
7359
  // src/config.ts
6615
- init_version();
6616
7360
  var DEFAULT_API_URL = "https://api.contextstream.io";
6617
7361
  function parseBooleanEnv(value) {
6618
7362
  if (value === void 0) return void 0;
@@ -7471,7 +8215,6 @@ var CacheKeys = {
7471
8215
  var globalCache = new MemoryCache();
7472
8216
 
7473
8217
  // src/client.ts
7474
- init_version();
7475
8218
  var uuidSchema = external_exports.string().uuid();
7476
8219
  function unwrapApiResponse(result) {
7477
8220
  if (!result || typeof result !== "object") return result;
@@ -8915,6 +9658,19 @@ var ContextStreamClient = class {
8915
9658
  }
8916
9659
  } catch {
8917
9660
  }
9661
+ try {
9662
+ const rememberItems = await this.getHighPriorityRememberItems({
9663
+ workspace_id: workspaceId,
9664
+ project_id: projectId,
9665
+ context_hint: params.context_hint,
9666
+ limit: 5
9667
+ });
9668
+ if (rememberItems.length > 0) {
9669
+ context.remember_items = rememberItems;
9670
+ context.remember_warning = `\u{1F4CC} ${rememberItems.length} user preference(s) to remember. ALWAYS check these before making changes.`;
9671
+ }
9672
+ } catch {
9673
+ }
8918
9674
  } catch (e) {
8919
9675
  console.error(
8920
9676
  "[ContextStream] Batched endpoint failed, falling back to individual calls:",
@@ -9879,18 +10635,38 @@ ${context2}`;
9879
10635
  context_hint: params.user_message,
9880
10636
  limit: 3
9881
10637
  });
9882
- for (const lesson of lessons) {
9883
- const prefix = lesson.severity === "critical" ? "\u26A0\uFE0F " : "";
10638
+ for (const lesson of lessons) {
10639
+ const prefix = lesson.severity === "critical" ? "\u26A0\uFE0F " : "";
10640
+ items.push({
10641
+ type: "L",
10642
+ key: "lesson",
10643
+ value: `${prefix}${lesson.title}: ${lesson.prevention.slice(0, 100)}`,
10644
+ relevance: lesson.severity === "critical" ? 1 : 0.9
10645
+ // Lessons are high priority
10646
+ });
10647
+ }
10648
+ } catch (e) {
10649
+ errors.push(`lessons: ${e?.message || "fetch failed"}`);
10650
+ }
10651
+ try {
10652
+ const rememberItems = await this.getHighPriorityRememberItems({
10653
+ workspace_id: withDefaults.workspace_id,
10654
+ project_id: withDefaults.project_id,
10655
+ context_hint: params.user_message,
10656
+ limit: 5
10657
+ });
10658
+ for (const item of rememberItems) {
10659
+ const prefix = item.importance === "critical" ? "\u{1F6A8} " : "\u{1F4CC} ";
9884
10660
  items.push({
9885
- type: "L",
9886
- key: "lesson",
9887
- value: `${prefix}${lesson.title}: ${lesson.prevention.slice(0, 100)}`,
9888
- relevance: lesson.severity === "critical" ? 1 : 0.9
9889
- // Lessons are high priority
10661
+ type: "R",
10662
+ key: "remember",
10663
+ value: `${prefix}${item.content.slice(0, 150)}`,
10664
+ relevance: 1
10665
+ // Remember items are ALWAYS highest priority
9890
10666
  });
9891
10667
  }
9892
10668
  } catch (e) {
9893
- errors.push(`lessons: ${e?.message || "fetch failed"}`);
10669
+ errors.push(`remember: ${e?.message || "fetch failed"}`);
9894
10670
  }
9895
10671
  if (errors.length > 0) {
9896
10672
  console.error("[ContextStream] context_smart errors:", errors.join(", "));
@@ -10044,6 +10820,38 @@ ${context}`;
10044
10820
  return [];
10045
10821
  }
10046
10822
  }
10823
+ /**
10824
+ * Get high-priority remember items that should be surfaced proactively.
10825
+ * These are user-flagged important items that should always be checked.
10826
+ */
10827
+ async getHighPriorityRememberItems(params) {
10828
+ const limit = params.limit || 5;
10829
+ try {
10830
+ const searchQuery = params.context_hint ? `${params.context_hint} user preference remember important` : "user preference remember important always_surface";
10831
+ const searchResult = await this.memorySearch({
10832
+ query: searchQuery,
10833
+ workspace_id: params.workspace_id,
10834
+ project_id: params.project_id,
10835
+ limit: limit * 2
10836
+ // Fetch more to filter
10837
+ });
10838
+ if (!searchResult?.results) return [];
10839
+ const rememberItems = searchResult.results.filter((item) => {
10840
+ const tags = item.metadata?.tags || [];
10841
+ return tags.includes("user_remember") || tags.includes("always_surface");
10842
+ }).slice(0, limit).map((item) => {
10843
+ const importance = item.metadata?.importance || "high";
10844
+ return {
10845
+ content: item.content || item.title || "",
10846
+ importance,
10847
+ created_at: item.occurred_at
10848
+ };
10849
+ });
10850
+ return rememberItems;
10851
+ } catch {
10852
+ return [];
10853
+ }
10854
+ }
10047
10855
  /**
10048
10856
  * Extract keywords from a message for relevance matching
10049
10857
  */
@@ -11254,659 +12062,951 @@ ${context}`;
11254
12062
  }
11255
12063
  };
11256
12064
 
11257
- // src/tools.ts
11258
- import * as fs5 from "node:fs";
11259
- import * as path6 from "node:path";
11260
- import { homedir as homedir3 } from "node:os";
11261
- init_rules_templates();
11262
- init_version();
12065
+ // src/tools.ts
12066
+ import * as fs5 from "node:fs";
12067
+ import * as path6 from "node:path";
12068
+ import { homedir as homedir3 } from "node:os";
12069
+
12070
+ // src/rules-templates.ts
12071
+ var DEFAULT_CLAUDE_MCP_SERVER_NAME = "contextstream";
12072
+ var RULES_VERSION = VERSION === "unknown" ? "0.0.0" : VERSION;
12073
+ var CONTEXTSTREAM_TOOL_NAMES = [
12074
+ // Standalone tools (always present)
12075
+ "init",
12076
+ // Renamed from session_init - initialize conversation session
12077
+ "context",
12078
+ // Renamed from context_smart - get relevant context every message
12079
+ "context_feedback",
12080
+ "generate_rules",
12081
+ // Consolidated domain tools (v0.4.x default)
12082
+ "search",
12083
+ // Modes: semantic, hybrid, keyword, pattern
12084
+ "session",
12085
+ // Actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace
12086
+ "memory",
12087
+ // Actions: create_event, get_event, update_event, delete_event, list_events, distill_event, create_node, get_node, update_node, delete_node, list_nodes, supersede_node, search, decisions, timeline, summary
12088
+ "graph",
12089
+ // Actions: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions
12090
+ "project",
12091
+ // Actions: list, get, create, update, index, overview, statistics, files, index_status, ingest_local
12092
+ "workspace",
12093
+ // Actions: list, get, associate, bootstrap
12094
+ "reminder",
12095
+ // Actions: list, active, create, snooze, complete, dismiss
12096
+ "integration",
12097
+ // Provider: slack, github, all; Actions: status, search, stats, activity, contributors, knowledge, summary, channels, discussions, sync_users, repos, issues
12098
+ "help"
12099
+ // Actions: tools, auth, version, editor_rules, enable_bundle
12100
+ ];
12101
+ function applyMcpToolPrefix(markdown, toolPrefix) {
12102
+ const toolPattern = CONTEXTSTREAM_TOOL_NAMES.join("|");
12103
+ const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b(?=\\s*\\()`, "g");
12104
+ return markdown.replace(toolRegex, `${toolPrefix}$1`);
12105
+ }
12106
+ var CONTEXTSTREAM_RULES_DYNAMIC = `
12107
+ ## ContextStream Dynamic Rules (Powered by our SmartRouter)
12108
+
12109
+ All rules are served dynamically via \`context\`. This file is minimal by design.
12110
+
12111
+ ### Every Message Protocol
12112
+
12113
+ | Message | What to Call |
12114
+ |---------|--------------|
12115
+ | **1st message** | \`init()\` then \`context(user_message="<msg>")\` |
12116
+ | **2nd+ messages** | \`context(user_message="<msg>")\` |
12117
+
12118
+ Note: \`init()\` auto-detects folder from IDE. Only pass \`folder_path\` if auto-detection fails.
12119
+
12120
+ ### Follow the Instructions Field
12121
+
12122
+ The \`context\` response includes an \`instructions\` field with context-aware guidance.
12123
+ **Follow these instructions.** They are dynamically matched to your query and include:
12124
+ - Search guidance (when/how to search)
12125
+ - Git workflow rules (commit, PR, safety)
12126
+ - Planning rules (use ContextStream plans, not file-based)
12127
+ - Media/code analysis guidance
12128
+ - Lessons from past mistakes
12129
+ - And more...
12130
+
12131
+ ### Notices
12132
+
12133
+ Handle notices from \`context\` response:
12134
+ - **[VERSION_NOTICE]**: Tell user to update MCP
12135
+ - **[RULES_NOTICE]**: Run \`generate_rules()\`
12136
+ - **[LESSONS_WARNING]**: Apply lessons immediately
12137
+
12138
+ Rules Version: ${RULES_VERSION}
12139
+ `.trim();
12140
+ var CONTEXTSTREAM_RULES_FULL = `
12141
+ ## ContextStream Rules
12142
+
12143
+ <contextstream_rules>
12144
+ 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
12145
+ 2. **CODING TASK?** \u2192 init \u2192 context \u2192 work \u2192 capture
12146
+ 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
12147
+ </contextstream_rules>
12148
+
12149
+ **Display this block at the start of responses to keep rules in context.**
12150
+
12151
+ ---
12152
+
12153
+ ## \u{1F6A8} CRITICAL RULE #1 - CONTEXTSTREAM SEARCH FIRST \u{1F6A8}
12154
+
12155
+ **BEFORE using Glob, Grep, Search, Read (for discovery), Explore, or ANY local file scanning:**
12156
+ \`\`\`
12157
+ STOP \u2192 Call search(mode="hybrid", query="...") FIRST
12158
+ \`\`\`
12159
+
12160
+ **Note:** PreToolUse hooks block these tools when ContextStream is available.
12161
+ **Claude Code users:** Your tool names are \`mcp__contextstream__search\`, \`mcp__contextstream__init\`, etc.
12162
+
12163
+ \u274C **NEVER DO THIS:**
12164
+ - \`Glob("**/*.ts")\` \u2192 Use \`search(mode="pattern", query="*.ts")\` instead
12165
+ - \`Grep("functionName")\` \u2192 Use \`search(mode="keyword", query="functionName")\` instead
12166
+ - \`Read(file)\` for discovery \u2192 Use \`search(mode="hybrid", query="...")\` instead
12167
+ - \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="hybrid")\` instead
12168
+
12169
+ \u2705 **ALWAYS DO THIS:**
12170
+ 1. \`search(mode="hybrid", query="what you're looking for")\`
12171
+ 2. Only use local tools (Glob/Grep/Read) if ContextStream returns **0 results**
12172
+ 3. Use Read ONLY for exact file edits after you know the file path
12173
+
12174
+ This applies to **EVERY search** throughout the **ENTIRE conversation**, not just the first message.
12175
+
12176
+ ---
12177
+
12178
+ ## \u{1F6A8} CRITICAL RULE #2 - AUTO-INDEXING \u{1F6A8}
12179
+
12180
+ **ContextStream auto-indexes your project on \`init\`.** You do NOT need to:
12181
+ - Ask the user to index
12182
+ - Manually trigger ingestion
12183
+ - Check index_status before every search
12184
+
12185
+ **When \`init\` returns \`indexing_status: "started"\` or \`"refreshing"\`:**
12186
+ - Background indexing is running automatically
12187
+ - Search results will be available within seconds to minutes
12188
+ - **DO NOT fall back to local tools** - wait for ContextStream search to work
12189
+ - If search returns 0 results initially, try again after a moment
12190
+
12191
+ **Only manually trigger indexing if:**
12192
+ - \`init\` returned \`ingest_recommendation.recommended: true\` (rare edge case)
12193
+ - User explicitly asks to re-index
12194
+
12195
+ ---
12196
+
12197
+ ## \u{1F6A8} CRITICAL RULE #3 - LESSONS (PAST MISTAKES) \u{1F6A8}
12198
+
12199
+ **Lessons are past mistakes that MUST inform your work.** Ignoring lessons leads to repeated failures.
12200
+
12201
+ ### On \`init\`:
12202
+ - Check for \`lessons\` and \`lessons_warning\` in the response
12203
+ - If present, **READ THEM IMMEDIATELY** before doing any work
12204
+ - These are high-priority lessons (critical/high severity) relevant to your context
12205
+ - **Apply the prevention steps** from each lesson to avoid repeating mistakes
12206
+
12207
+ ### On \`context\`:
12208
+ - Check for \`[LESSONS_WARNING]\` tag in the response
12209
+ - If present, you **MUST** tell the user about the lessons before proceeding
12210
+ - Lessons are proactively fetched when risky actions are detected (refactor, migrate, deploy, etc.)
12211
+ - **Do not skip or bury this warning** - lessons represent real past mistakes
12212
+
12213
+ ### Before ANY Non-Trivial Work:
12214
+ **ALWAYS call \`session(action="get_lessons", query="<topic>")\`** where \`<topic>\` matches what you're about to do:
12215
+ - Before refactoring \u2192 \`session(action="get_lessons", query="refactoring")\`
12216
+ - Before API changes \u2192 \`session(action="get_lessons", query="API changes")\`
12217
+ - Before database work \u2192 \`session(action="get_lessons", query="database migrations")\`
12218
+ - Before deployments \u2192 \`session(action="get_lessons", query="deployment")\`
12219
+
12220
+ ### When Lessons Are Found:
12221
+ 1. **Summarize the lessons** to the user before proceeding
12222
+ 2. **Explicitly state how you will avoid the past mistakes**
12223
+ 3. If a lesson conflicts with the current approach, **warn the user**
12224
+
12225
+ **Failing to check lessons before risky work is a critical error.**
12226
+
12227
+ ---
12228
+
12229
+ ## ContextStream v0.4.x Integration (Enhanced)
12230
+
12231
+ You have access to ContextStream MCP tools for persistent memory and context.
12232
+ v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
12233
+ Rules Version: ${RULES_VERSION}
12234
+
12235
+ ## TL;DR - WHEN TO USE CONTEXT
12236
+
12237
+ | Request Type | What to Do |
12238
+ |--------------|------------|
12239
+ | **\u{1F680} Simple utility** (list workspaces, show version) | **Just execute directly** - skip init, context, capture |
12240
+ | **\u{1F4BB} Coding task** (edit, create, refactor) | Full context: init \u2192 context \u2192 work \u2192 capture |
12241
+ | **\u{1F50D} Code search/discovery** | init \u2192 context \u2192 search() |
12242
+ | **\u26A0\uFE0F Risky work** (deploy, migrate, refactor) | Check lessons first: \`session(action="get_lessons")\` |
12243
+ | **User frustration/correction** | Capture lesson: \`session(action="capture_lesson", ...)\` |
12244
+
12245
+ ### Simple Utility Operations - FAST PATH
12246
+
12247
+ **For simple queries, just execute and respond:**
12248
+ - "list workspaces" \u2192 \`workspace(action="list")\` \u2192 done
12249
+ - "list projects" \u2192 \`project(action="list")\` \u2192 done
12250
+ - "show version" \u2192 \`help(action="version")\` \u2192 done
12251
+ - "what reminders do I have" \u2192 \`reminder(action="list")\` \u2192 done
12252
+
12253
+ **No init. No context. No capture.** These add noise, not value.
12254
+
12255
+ ### Coding Tasks - FULL CONTEXT
12256
+
12257
+ | Step | What to Call |
12258
+ |------|--------------|
12259
+ | **1st message** | \`init(folder_path="...", context_hint="<msg>")\`, then \`context(...)\` |
12260
+ | **2nd+ messages** | \`context(user_message="<msg>", format="minified", max_tokens=400)\` |
12261
+ | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
12262
+ | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
12263
+ | **User correction** | \`session(action="capture_lesson", ...)\` |
12264
+ | **\u26A0\uFE0F When warnings received** | **STOP**, acknowledge, explain mitigation, then proceed |
12265
+
12266
+ **How to detect simple utility operations:**
12267
+ - Single-word commands: "list", "show", "version", "help"
12268
+ - Data retrieval with no context dependency: "list my workspaces", "what projects do I have"
12269
+ - Status checks: "am I authenticated?", "what's the server version?"
12270
+
12271
+ **First message rule (for coding tasks):** After \`init\`:
12272
+ 1. Check for \`lessons\` in response - if present, READ and SUMMARIZE them to user
12273
+ 2. Then call \`context\` before any other tool or response
12274
+
12275
+ **Context Pack (Pro+):** If enabled, use \`context(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context\` (the API will fall back).
12276
+
12277
+ **Tool naming:** Use the exact tool names exposed by your MCP client. Claude Code typically uses \`mcp__<server>__<tool>\` where \`<server>\` matches your MCP config (often \`contextstream\`). If a tool call fails with "No such tool available", refresh rules and match the tool list.
12278
+
12279
+ ---
12280
+
12281
+ ## Consolidated Domain Tools Architecture
12282
+
12283
+ v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode dispatch:
12284
+
12285
+ ### Standalone Tools
12286
+ - **\`init\`** - Initialize session with workspace detection + context (skip for simple utility operations)
12287
+ - **\`context\`** - Semantic search for relevant context (skip for simple utility operations)
12288
+
12289
+ ### Domain Tools (Use action/mode parameter)
12290
+
12291
+ | Domain | Actions/Modes | Example |
12292
+ |--------|---------------|---------|
12293
+ | **\`search\`** | mode: semantic, hybrid, keyword, pattern | \`search(mode="hybrid", query="auth implementation", limit=3)\` |
12294
+ | **\`session\`** | action: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace | \`session(action="capture", event_type="decision", title="Use JWT", content="...")\` |
12295
+ | **\`memory\`** | action: create_event, get_event, update_event, delete_event, list_events, distill_event, create_node, get_node, update_node, delete_node, list_nodes, supersede_node, search, decisions, timeline, summary | \`memory(action="list_events", limit=10)\` |
12296
+ | **\`graph\`** | action: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions | \`graph(action="impact", symbol_name="AuthService")\` |
12297
+ | **\`project\`** | action: list, get, create, update, index, overview, statistics, files, index_status, ingest_local | \`project(action="statistics")\` |
12298
+ | **\`workspace\`** | action: list, get, associate, bootstrap | \`workspace(action="list")\` |
12299
+ | **\`reminder\`** | action: list, active, create, snooze, complete, dismiss | \`reminder(action="active")\` |
12300
+ | **\`integration\`** | provider: slack/github/all; action: status, search, stats, activity, contributors, knowledge, summary, channels, discussions, sync_users, repos, issues | \`integration(provider="github", action="search", query="...")\` |
12301
+ | **\`help\`** | action: tools, auth, version, editor_rules, enable_bundle | \`help(action="tools")\` |
12302
+
12303
+ ---
12304
+
12305
+ ### Why context is Required (Even After init)
12306
+
12307
+ **Common mistake:** "init already gave me context, I don't need context"
12308
+
12309
+ **This is WRONG. Here's why:**
12310
+ - \`init\` returns the last ~10 items **BY TIME** (chronological)
12311
+ - \`context\` **SEARCHES** for items **RELEVANT to THIS message** (semantic)
12312
+
12313
+ **Example failure:**
12314
+ - User asks: "how should I implement authentication?"
12315
+ - Auth decisions were made 20 conversations ago
12316
+ - \`init\` won't have it (too old, not in recent 10)
12317
+ - \`context\` FINDS it via semantic search
12318
+
12319
+ **Without context, you WILL miss relevant older context.**
12320
+
12321
+ ---
12322
+
12323
+ ### Recommended Token Budgets
12324
+
12325
+ - For trivial/local edits: \`context(..., max_tokens=200)\`
12326
+ - Default: \`context(..., max_tokens=400)\`
12327
+ - Deep debugging/architecture: \`context(..., max_tokens=800)\`
12328
+ - Keep \`format="minified"\` (default) unless debugging
12329
+
12330
+ If context still feels missing, use \`session(action="recall", query="...")\` for focused deep lookup.
12331
+
12332
+ ---
12333
+
12334
+ ### Rules, Version & Lessons Notices
12335
+
12336
+ **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
12337
+
12338
+ **[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
12339
+ 1. A new MCP server version is available
12340
+ 2. The exact update command to run
12341
+ 3. That they need to restart their AI tool after updating
12342
+ **Do not skip this** - users often miss stderr warnings.
12343
+
12344
+ **[LESSONS_WARNING]** - You **MUST** before proceeding:
12345
+ 1. Read all lessons listed
12346
+ 2. Tell the user about relevant lessons
12347
+ 3. Explain how you will avoid each past mistake
12348
+ **This is critical** - ignoring lessons leads to repeated failures.
12349
+
12350
+ ---
12351
+
12352
+ ### Preferences & Lessons (Use Early)
12353
+
12354
+ - If preferences/style matter: \`session(action="user_context")\`
12355
+ - Before risky changes: \`session(action="get_lessons", query="<topic>")\`
12356
+ - On frustration/corrections: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
12357
+
12358
+ ---
12359
+
12360
+ ### Context Pressure & Compaction Awareness
12361
+
12362
+ ContextStream tracks context pressure to help you stay ahead of conversation compaction:
12363
+
12364
+ **Automatic tracking:** Token usage is tracked automatically. \`context\` returns \`context_pressure\` when usage is high.
12365
+
12366
+ **When \`context\` returns \`context_pressure\` with high/critical level:**
12367
+ 1. Review the \`suggested_action\` field:
12368
+ - \`prepare_save\`: Start thinking about saving important state
12369
+ - \`save_now\`: Immediately call \`session(action="capture", event_type="session_snapshot")\` to preserve state
12370
+
12371
+ **PreCompact Hook (Optional):** If enabled, Claude Code will inject a reminder to save state before compaction.
12372
+ Enable with: \`generate_rules(install_hooks=true, include_pre_compact=true)\`
12373
+
12374
+ **Before compaction happens (when warned):**
12375
+ \`\`\`
12376
+ session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="{
12377
+ \\"conversation_summary\\": \\"<summarize what we've been doing>\\",
12378
+ \\"current_goal\\": \\"<the main task>\\",
12379
+ \\"active_files\\": [\\"file1.ts\\", \\"file2.ts\\"],
12380
+ \\"recent_decisions\\": [{title: \\"...\\", rationale: \\"...\\"}],
12381
+ \\"unfinished_work\\": [{task: \\"...\\", status: \\"...\\", next_steps: \\"...\\"}]
12382
+ }")
12383
+ \`\`\`
12384
+
12385
+ **After compaction (when context seems lost):**
12386
+ 1. Call \`init(folder_path="...", is_post_compact=true)\` - this auto-restores the most recent snapshot
12387
+ 2. Or call \`session_restore_context()\` directly to get the saved state
12388
+ 3. Review the \`restored_context\` to understand prior work
12389
+ 4. Acknowledge to the user what was restored and continue
12390
+
12391
+ ---
12392
+
12393
+ ### Index Status (Auto-Managed)
12394
+
12395
+ **Indexing is automatic.** After \`init\`, the project is auto-indexed in the background.
12396
+
12397
+ **You do NOT need to manually check index_status before every search.** Just use \`search()\`.
12398
+
12399
+ **If search returns 0 results and you expected matches:**
12400
+ 1. Check if \`init\` returned \`indexing_status: "started"\` - indexing may still be in progress
12401
+ 2. Wait a moment and retry \`search()\`
12402
+ 3. Only as a last resort: \`project(action="index_status")\` to check
12403
+
12404
+ **Graph data:** If graph queries (\`dependencies\`, \`impact\`) return empty, run \`graph(action="ingest")\` once.
12405
+
12406
+ **NEVER fall back to local tools (Glob/Grep/Read) just because search returned 0 results on first try.** Retry first.
12407
+
12408
+ ### Enhanced Context (Server-Side Warnings)
12409
+
12410
+ \`context\` now includes **intelligent server-side filtering** that proactively surfaces relevant warnings:
12411
+
12412
+ **Response fields:**
12413
+ - \`warnings\`: Array of warning strings (displayed with \u26A0\uFE0F prefix)
12414
+
12415
+ **What triggers warnings:**
12416
+ - **Lessons**: Past mistakes relevant to the current query (via semantic matching)
12417
+ - **Risky actions**: Detected high-risk operations (deployments, migrations, destructive commands)
12418
+ - **Breaking changes**: When modifications may impact other parts of the codebase
12419
+
12420
+ **When you receive warnings:**
12421
+ 1. **STOP** and read each warning carefully
12422
+ 2. **Acknowledge** the warning to the user
12423
+ 3. **Explain** how you will avoid the issue
12424
+ 4. Only proceed after addressing the warnings
12425
+
12426
+ ### Search & Code Intelligence (ContextStream-first)
12427
+
12428
+ \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
12429
+
12430
+ **\u274C WRONG workflow (wastes tokens, slow):**
12431
+ \`\`\`
12432
+ Grep "function" \u2192 Read file1.ts \u2192 Read file2.ts \u2192 Read file3.ts \u2192 finally understand
12433
+ \`\`\`
12434
+
12435
+ **\u2705 CORRECT workflow (fast, complete):**
12436
+ \`\`\`
12437
+ search(mode="hybrid", query="function implementation") \u2192 done (results include context)
12438
+ \`\`\`
12439
+
12440
+ **Why?** ContextStream search returns semantic matches + context + file locations in ONE call. Local tools require multiple round-trips.
12441
+
12442
+ **Search order:**
12443
+ 1. \`session(action="smart_search", query="...")\` - context-enriched
12444
+ 2. \`search(mode="hybrid", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
12445
+ 3. \`project(action="files")\` - file tree/list (only when needed)
12446
+ 4. \`graph(action="dependencies", ...)\` - code structure
12447
+ 5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
12448
+
12449
+ **Search Mode Selection:**
12450
+
12451
+ | Need | Mode | Example |
12452
+ |------|------|---------|
12453
+ | Find code by meaning | \`hybrid\` | "authentication logic", "error handling" |
12454
+ | Exact string/symbol | \`keyword\` | "UserAuthService", "API_KEY" |
12455
+ | File patterns | \`pattern\` | "*.sql", "test_*.py" |
12456
+ | ALL matches (grep-like) | \`exhaustive\` | "TODO", "FIXME" (find all occurrences) |
12457
+ | Symbol renaming | \`refactor\` | "oldFunctionName" (word-boundary matching) |
12458
+ | Conceptual search | \`semantic\` | "how does caching work" |
12459
+
12460
+ **Token Efficiency:** Use \`output_format\` to reduce response size:
12461
+ - \`full\` (default): Full content for understanding code
12462
+ - \`paths\`: File paths only (80% token savings) - use for file listings
12463
+ - \`minimal\`: Compact format (60% savings) - use for refactoring
12464
+ - \`count\`: Match counts only (90% savings) - use for quick checks
12465
+
12466
+ **When to use \`output_format=count\`:**
12467
+ - User asks "how many X" or "count of X" \u2192 \`search(..., output_format="count")\`
12468
+ - Checking if something exists \u2192 count > 0 is sufficient
12469
+ - Large exhaustive searches \u2192 get count first, then fetch if needed
12470
+
12471
+ **Auto-suggested formats:** Search responses include \`query_interpretation.suggested_output_format\` when the API detects an optimal format:
12472
+ - Symbol queries (e.g., "authOptions") \u2192 suggests \`minimal\` (path + line + snippet)
12473
+ - Count queries (e.g., "how many") \u2192 suggests \`count\`
12474
+ **USE the suggested format** on subsequent searches for best token efficiency.
12475
+
12476
+ **Search defaults:** \`search\` returns the top 3 results with compact snippets. Use \`limit\` + \`offset\` for pagination, and \`content_max_chars\` to expand snippets when needed.
12477
+
12478
+ If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits or ContextStream returned 0 results.
12479
+
12480
+ **Code Analysis:**
12481
+ - Dependencies: \`graph(action="dependencies", file_path="...")\`
12482
+ - Change impact: \`graph(action="impact", symbol_name="...")\`
12483
+ - Call path: \`graph(action="call_path", from_symbol="...", to_symbol="...")\`
12484
+ - Build graph: \`graph(action="ingest")\` - async, can take a few minutes
12485
+
12486
+ ---
12487
+
12488
+ ### Distillation & Memory Hygiene
12489
+
12490
+ - Quick context: \`session(action="summary")\`
12491
+ - Long chat: \`session(action="compress", content="...")\`
12492
+ - Memory summary: \`memory(action="summary")\`
12493
+ - Condense noisy entries: \`memory(action="distill_event", event_id="...")\`
12494
+
12495
+ ---
12496
+
12497
+ ### When to Capture
12498
+
12499
+ | When | Call | Example |
12500
+ |------|------|---------|
12501
+ | User makes decision | \`session(action="capture", event_type="decision", ...)\` | "Let's use PostgreSQL" |
12502
+ | User states preference | \`session(action="capture", event_type="preference", ...)\` | "I prefer TypeScript" |
12503
+ | Complete significant task | \`session(action="capture", event_type="task", ...)\` | Capture what was done |
12504
+ | Need past context | \`session(action="recall", query="...")\` | "What did we decide about X?" |
12505
+
12506
+ **DO NOT capture utility operations:**
12507
+ - \u274C "Listed workspaces" - not meaningful context
12508
+ - \u274C "Showed version" - not a decision
12509
+ - \u274C "Listed projects" - just data retrieval
12510
+
12511
+ **DO capture meaningful work:**
12512
+ - \u2705 Decisions, preferences, completed features
12513
+ - \u2705 Lessons from mistakes
12514
+ - \u2705 Insights about architecture or patterns
12515
+
12516
+ ---
12517
+
12518
+ ### \u{1F6A8} Plans & Tasks - USE CONTEXTSTREAM, NOT FILE-BASED PLANS \u{1F6A8}
12519
+
12520
+ **CRITICAL: When the user requests planning, implementation plans, roadmaps, task breakdowns, or step-by-step approaches:**
12521
+
12522
+ \u274C **DO NOT** use built-in plan mode (EnterPlanMode tool)
12523
+ \u274C **DO NOT** write plans to markdown files or plan documents
12524
+ \u274C **DO NOT** ask "should I create a plan file?"
12525
+
12526
+ \u2705 **ALWAYS** use ContextStream's plan/task system instead
12527
+
12528
+ **Trigger phrases to detect (use ContextStream immediately):**
12529
+ - "create a plan", "make a plan", "plan this", "plan for"
12530
+ - "implementation plan", "roadmap", "milestones"
12531
+ - "break down", "breakdown", "break this into steps"
12532
+ - "what are the steps", "step by step", "outline the approach"
12533
+ - "task list", "todo list", "action items"
12534
+ - "how should we approach", "implementation strategy"
12535
+
12536
+ **When detected, immediately:**
12537
+
12538
+ 1. **Create the plan in ContextStream:**
12539
+ \`\`\`
12540
+ session(action="capture_plan", title="<descriptive title>", description="<what this plan accomplishes>", goals=["goal1", "goal2"], steps=[{id: "1", title: "Step 1", order: 1, description: "..."}, ...])
12541
+ \`\`\`
12542
+
12543
+ 2. **Create tasks for each step:**
12544
+ \`\`\`
12545
+ memory(action="create_task", title="<task title>", plan_id="<plan_id from step 1>", priority="high|medium|low", description="<detailed task description>")
12546
+ \`\`\`
12547
+
12548
+ **Why ContextStream plans are better:**
12549
+ - Plans persist across sessions and are searchable
12550
+ - Tasks track status (pending/in_progress/completed/blocked)
12551
+ - Context is preserved with workspace/project association
12552
+ - Can be retrieved with \`session(action="get_plan", plan_id="...", include_tasks=true)\`
12553
+ - Future sessions can continue from where you left off
12554
+
12555
+ **Managing plans/tasks:**
12556
+ - List plans: \`session(action="list_plans")\`
12557
+ - Get plan with tasks: \`session(action="get_plan", plan_id="<uuid>", include_tasks=true)\`
12558
+ - List tasks: \`memory(action="list_tasks", plan_id="<uuid>")\` or \`memory(action="list_tasks")\` for all
12559
+ - Update task status: \`memory(action="update_task", task_id="<uuid>", task_status="pending|in_progress|completed|blocked")\`
12560
+ - Link task to plan: \`memory(action="update_task", task_id="<uuid>", plan_id="<plan_uuid>")\`
12561
+ - Unlink task from plan: \`memory(action="update_task", task_id="<uuid>", plan_id=null)\`
12562
+ - Delete: \`memory(action="delete_task", task_id="<uuid>")\` or \`memory(action="delete_event", event_id="<plan_uuid>")\`
12563
+
12564
+ ---
12565
+
12566
+ ### Complete Action Reference
11263
12567
 
11264
- // src/tool-catalog.ts
11265
- var TOOL_CATALOG = [
11266
- {
11267
- name: "Session",
11268
- tools: [
11269
- { name: "init", hint: "start-conv" },
11270
- { name: "smart", hint: "each-msg" },
11271
- { name: "capture", hint: "save" },
11272
- { name: "recall", hint: "find" },
11273
- { name: "remember", hint: "quick" },
11274
- { name: "compress", hint: "end" },
11275
- { name: "summary", hint: "brief" },
11276
- { name: "delta", hint: "changes" },
11277
- { name: "get_lessons", hint: "learn" },
11278
- { name: "capture_lesson", hint: "mistake" },
11279
- { name: "get_user_context", hint: "prefs" },
11280
- { name: "smart_search", hint: "deep-find" },
11281
- // Plan actions
11282
- { name: "capture_plan", hint: "save-plan" },
11283
- { name: "get_plan", hint: "get-plan" },
11284
- { name: "update_plan", hint: "edit-plan" },
11285
- { name: "list_plans", hint: "list-plans" }
11286
- ]
11287
- },
11288
- {
11289
- name: "Search",
11290
- tools: [
11291
- { name: "semantic", hint: "meaning" },
11292
- { name: "hybrid", hint: "combo" },
11293
- { name: "keyword", hint: "exact" },
11294
- { name: "pattern", hint: "code" }
11295
- ]
11296
- },
11297
- {
11298
- name: "Memory",
11299
- tools: [
11300
- { name: "create_event", hint: "new" },
11301
- { name: "list_events", hint: "list" },
11302
- { name: "get_event", hint: "get" },
11303
- { name: "update_event", hint: "edit" },
11304
- { name: "delete_event", hint: "rm" },
11305
- { name: "search", hint: "find" },
11306
- { name: "decisions", hint: "choices" },
11307
- { name: "timeline", hint: "history" },
11308
- { name: "distill_event", hint: "extract" },
11309
- // Task actions
11310
- { name: "create_task", hint: "new-task" },
11311
- { name: "get_task", hint: "get-task" },
11312
- { name: "update_task", hint: "edit-task" },
11313
- { name: "delete_task", hint: "rm-task" },
11314
- { name: "list_tasks", hint: "list-tasks" },
11315
- { name: "reorder_tasks", hint: "sort-tasks" }
11316
- ]
11317
- },
11318
- {
11319
- name: "Knowledge",
11320
- tools: [
11321
- { name: "create_node", hint: "new" },
11322
- { name: "list_nodes", hint: "list" },
11323
- { name: "get_node", hint: "get" },
11324
- { name: "update_node", hint: "edit" },
11325
- { name: "delete_node", hint: "rm" },
11326
- { name: "supersede_node", hint: "replace" }
11327
- ]
11328
- },
11329
- {
11330
- name: "Graph",
11331
- tools: [
11332
- { name: "related", hint: "links" },
11333
- { name: "path", hint: "trace" },
11334
- { name: "decisions", hint: "choices" },
11335
- { name: "dependencies", hint: "deps" },
11336
- { name: "ingest", hint: "build" },
11337
- { name: "impact", hint: "changes" },
11338
- { name: "contradictions", hint: "conflicts" }
11339
- ]
11340
- },
11341
- {
11342
- name: "Media",
11343
- tools: [
11344
- { name: "index", hint: "add-media" },
11345
- { name: "status", hint: "progress" },
11346
- { name: "search", hint: "find-clip" },
11347
- { name: "get_clip", hint: "get-segment" },
11348
- { name: "list", hint: "browse" },
11349
- { name: "delete", hint: "remove" }
11350
- ]
11351
- },
11352
- {
11353
- name: "Workspace",
11354
- tools: [
11355
- { name: "list", hint: "" },
11356
- { name: "get", hint: "" },
11357
- { name: "create", hint: "" },
11358
- { name: "associate", hint: "link-folder" },
11359
- { name: "bootstrap", hint: "new-ws" }
11360
- ]
11361
- },
11362
- {
11363
- name: "Project",
11364
- tools: [
11365
- { name: "list", hint: "" },
11366
- { name: "get", hint: "" },
11367
- { name: "create", hint: "" },
11368
- { name: "index", hint: "scan-code" },
11369
- { name: "files", hint: "list-files" },
11370
- { name: "overview", hint: "summary" }
11371
- ]
11372
- },
11373
- {
11374
- name: "AI",
11375
- tools: [
11376
- { name: "context", hint: "smart-ctx" },
11377
- { name: "plan", hint: "generate" },
11378
- { name: "tasks", hint: "breakdown" },
11379
- { name: "embeddings", hint: "vectors" }
11380
- ]
11381
- },
11382
- {
11383
- name: "Notion",
11384
- tools: [
11385
- { name: "create_page", hint: "new-page" },
11386
- { name: "search_pages", hint: "find" },
11387
- { name: "list_databases", hint: "list-dbs" },
11388
- { name: "get_page", hint: "get" },
11389
- { name: "query_database", hint: "query-db" },
11390
- { name: "update_page", hint: "edit" },
11391
- { name: "stats", hint: "overview" },
11392
- { name: "activity", hint: "recent" },
11393
- { name: "knowledge", hint: "insights" },
11394
- { name: "summary", hint: "brief" }
11395
- ]
11396
- }
11397
- ];
11398
- function generateToolCatalog(format = "grouped", category) {
11399
- let categories = TOOL_CATALOG;
11400
- if (category) {
11401
- const filtered = TOOL_CATALOG.filter((c) => c.name.toLowerCase() === category.toLowerCase());
11402
- if (filtered.length > 0) {
11403
- categories = filtered;
11404
- }
11405
- }
11406
- switch (format) {
11407
- case "minimal":
11408
- return generateMinimal(categories);
11409
- case "full":
11410
- return generateFull(categories);
11411
- case "grouped":
11412
- default:
11413
- return generateGrouped(categories);
11414
- }
11415
- }
11416
- function generateGrouped(categories) {
11417
- return categories.map((cat) => {
11418
- const tools = cat.tools.map((t) => t.hint ? `${t.name}(${t.hint})` : t.name).join(" ");
11419
- return `${cat.name}: ${tools}`;
11420
- }).join("\n");
11421
- }
11422
- function generateMinimal(categories) {
11423
- return categories.map((cat) => {
11424
- const tools = cat.tools.map((t) => t.name).join("|");
11425
- return `${cat.name}:${tools}`;
11426
- }).join("\n");
11427
- }
11428
- function generateFull(categories) {
11429
- const lines = [];
11430
- for (const cat of categories) {
11431
- lines.push(`## ${cat.name}`);
11432
- for (const tool of cat.tools) {
11433
- const prefix = cat.name.toLowerCase().replace(/\s+/g, "_");
11434
- const fullName = `${prefix}_${tool.name}`;
11435
- lines.push(`- ${fullName}: ${tool.hint || "standard CRUD"}`);
11436
- }
11437
- }
11438
- return lines.join("\n");
11439
- }
11440
- function getCoreToolsHint() {
11441
- return `Session: init(start) smart(each-msg) capture(save) recall(find) remember(quick)`;
11442
- }
12568
+ **session actions:**
12569
+ - \`capture\` - Save decision/insight/task (requires: event_type, title, content)
12570
+ - \`capture_lesson\` - Save lesson from mistake (requires: title, category, trigger, impact, prevention)
12571
+ - \`get_lessons\` - Retrieve relevant lessons (optional: query, category, severity)
12572
+ - \`recall\` - Natural language memory recall (requires: query)
12573
+ - \`remember\` - Quick save to memory (requires: content)
12574
+ - \`user_context\` - Get user preferences/style
12575
+ - \`summary\` - Workspace summary
12576
+ - \`compress\` - Compress long conversation
12577
+ - \`delta\` - Changes since timestamp
12578
+ - \`smart_search\` - Context-enriched search
12579
+ - \`decision_trace\` - Trace decision provenance
12580
+
12581
+ **memory actions:**
12582
+ - Event CRUD: \`create_event\`, \`get_event\`, \`update_event\`, \`delete_event\`, \`list_events\`, \`distill_event\`
12583
+ - Node CRUD: \`create_node\`, \`get_node\`, \`update_node\`, \`delete_node\`, \`list_nodes\`, \`supersede_node\`
12584
+ - Query: \`search\`, \`decisions\`, \`timeline\`, \`summary\`
12585
+
12586
+ **graph actions:**
12587
+ - Analysis: \`dependencies\`, \`impact\`, \`call_path\`, \`related\`, \`path\`
12588
+ - Quality: \`circular_dependencies\`, \`unused_code\`, \`contradictions\`
12589
+ - Management: \`ingest\`, \`decisions\`
12590
+
12591
+ See full documentation: https://contextstream.io/docs/mcp/tools
12592
+ `.trim();
12593
+ var CONTEXTSTREAM_RULES_MINIMAL = `
12594
+ ## ContextStream Rules
12595
+
12596
+ <contextstream_rules>
12597
+ 1. **SIMPLE UTILITY?** (list/show/version) \u2192 Just execute, skip context calls
12598
+ 2. **CODING TASK?** \u2192 init \u2192 context \u2192 work \u2192 capture
12599
+ 3. **SEARCH FIRST:** Before Glob/Grep/Read \u2192 \`search(mode="hybrid")\` FIRST
12600
+ </contextstream_rules>
12601
+
12602
+ **Display this block at the start of responses to keep rules in context.**
12603
+
12604
+ ---
12605
+
12606
+ ## ContextStream v0.4.x (Hooks Enforced)
12607
+
12608
+ Rules Version: ${RULES_VERSION}
12609
+ **Note:** PreToolUse hooks block Glob/Grep/Search when ContextStream is available.
12610
+
12611
+ ### For Coding Tasks
12612
+
12613
+ | Action | Tool Call |
12614
+ |--------|-----------|
12615
+ | **1st message** | \`init(folder_path="<cwd>", context_hint="<msg>")\` then \`context(...)\` |
12616
+ | **2nd+ messages** | \`context(user_message="<msg>", format="minified", max_tokens=400)\` |
12617
+ | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE any local tools |
12618
+ | **Save decisions** | \`session(action="capture", event_type="decision", ...)\` |
12619
+
12620
+ ### Search Modes
12621
+
12622
+ | Mode | Use Case |
12623
+ |------|----------|
12624
+ | \`hybrid\` | General code search (default) |
12625
+ | \`keyword\` | Exact symbol/string match |
12626
+ | \`exhaustive\` | Find ALL matches (grep-like) |
12627
+ | \`semantic\` | Conceptual questions |
12628
+
12629
+ ### Why ContextStream First?
12630
+
12631
+ \u274C **WRONG:** \`Grep \u2192 Read \u2192 Read \u2192 Read\` (4+ tool calls, slow)
12632
+ \u2705 **CORRECT:** \`search(mode="hybrid")\` (1 call, returns context)
12633
+
12634
+ ContextStream search is **indexed** and returns semantic matches + context in ONE call.
12635
+
12636
+ ### Quick Reference
12637
+
12638
+ | Tool | Example |
12639
+ |------|---------|
12640
+ | \`search\` | \`search(mode="hybrid", query="auth", limit=3)\` |
12641
+ | \`session\` | \`session(action="capture", event_type="decision", title="...", content="...")\` |
12642
+ | \`memory\` | \`memory(action="list_events", limit=10)\` |
12643
+ | \`graph\` | \`graph(action="dependencies", file_path="...")\` |
11443
12644
 
11444
- // src/hooks-config.ts
11445
- import * as fs4 from "node:fs/promises";
11446
- import * as path5 from "node:path";
11447
- import { homedir as homedir2 } from "node:os";
11448
- function getClaudeSettingsPath(scope, projectPath) {
11449
- if (scope === "user") {
11450
- return path5.join(homedir2(), ".claude", "settings.json");
11451
- }
11452
- if (!projectPath) {
11453
- throw new Error("projectPath required for project scope");
11454
- }
11455
- return path5.join(projectPath, ".claude", "settings.json");
11456
- }
11457
- function getHooksDir() {
11458
- return path5.join(homedir2(), ".claude", "hooks");
11459
- }
11460
- function buildHooksConfig(options) {
11461
- const userPromptHooks = [
11462
- {
11463
- matcher: "*",
11464
- hooks: [
11465
- {
11466
- type: "command",
11467
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
11468
- timeout: 5
11469
- }
11470
- ]
11471
- }
11472
- ];
11473
- if (options?.includeMediaAware !== false) {
11474
- userPromptHooks.push({
11475
- matcher: "*",
11476
- hooks: [
11477
- {
11478
- type: "command",
11479
- command: "npx @contextstream/mcp-server hook media-aware",
11480
- timeout: 5
11481
- }
11482
- ]
11483
- });
11484
- }
11485
- const config = {
11486
- PreToolUse: [
11487
- {
11488
- matcher: "Glob|Grep|Search|Task|EnterPlanMode",
11489
- hooks: [
11490
- {
11491
- type: "command",
11492
- command: "npx @contextstream/mcp-server hook pre-tool-use",
11493
- timeout: 5
11494
- }
11495
- ]
11496
- }
11497
- ],
11498
- UserPromptSubmit: userPromptHooks
11499
- };
11500
- if (options?.includePreCompact) {
11501
- config.PreCompact = [
11502
- {
11503
- // Match both manual (/compact) and automatic compaction
11504
- matcher: "*",
11505
- hooks: [
11506
- {
11507
- type: "command",
11508
- command: "npx @contextstream/mcp-server hook pre-compact",
11509
- timeout: 10
11510
- }
11511
- ]
11512
- }
11513
- ];
11514
- }
11515
- const postToolUseHooks = [];
11516
- if (options?.includePostWrite !== false) {
11517
- postToolUseHooks.push({
11518
- matcher: "Edit|Write|NotebookEdit",
11519
- hooks: [
11520
- {
11521
- type: "command",
11522
- command: "npx @contextstream/mcp-server hook post-write",
11523
- timeout: 10
11524
- }
11525
- ]
11526
- });
11527
- }
11528
- if (options?.includeAutoRules !== false) {
11529
- postToolUseHooks.push({
11530
- matcher: "mcp__contextstream__init|mcp__contextstream__context",
11531
- hooks: [
11532
- {
11533
- type: "command",
11534
- command: "npx @contextstream/mcp-server hook auto-rules",
11535
- timeout: 15
11536
- }
11537
- ]
11538
- });
11539
- }
11540
- if (postToolUseHooks.length > 0) {
11541
- config.PostToolUse = postToolUseHooks;
11542
- }
11543
- return config;
11544
- }
11545
- async function installHookScripts(options) {
11546
- const hooksDir = getHooksDir();
11547
- await fs4.mkdir(hooksDir, { recursive: true });
11548
- const result = {
11549
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
11550
- userPrompt: "npx @contextstream/mcp-server hook user-prompt-submit"
11551
- };
11552
- if (options?.includePreCompact) {
11553
- result.preCompact = "npx @contextstream/mcp-server hook pre-compact";
11554
- }
11555
- if (options?.includeMediaAware !== false) {
11556
- result.mediaAware = "npx @contextstream/mcp-server hook media-aware";
11557
- }
11558
- if (options?.includeAutoRules !== false) {
11559
- result.autoRules = "npx @contextstream/mcp-server hook auto-rules";
11560
- }
11561
- return result;
11562
- }
11563
- async function readClaudeSettings(scope, projectPath) {
11564
- const settingsPath = getClaudeSettingsPath(scope, projectPath);
11565
- try {
11566
- const content = await fs4.readFile(settingsPath, "utf-8");
11567
- return JSON.parse(content);
11568
- } catch {
11569
- return {};
11570
- }
11571
- }
11572
- async function writeClaudeSettings(settings, scope, projectPath) {
11573
- const settingsPath = getClaudeSettingsPath(scope, projectPath);
11574
- const dir = path5.dirname(settingsPath);
11575
- await fs4.mkdir(dir, { recursive: true });
11576
- await fs4.writeFile(settingsPath, JSON.stringify(settings, null, 2));
11577
- }
11578
- function mergeHooksIntoSettings(existingSettings, newHooks) {
11579
- const settings = { ...existingSettings };
11580
- const existingHooks = settings.hooks || {};
11581
- for (const [hookType, matchers] of Object.entries(newHooks || {})) {
11582
- if (!matchers) continue;
11583
- const existing = existingHooks?.[hookType] || [];
11584
- const filtered = existing.filter((m) => {
11585
- return !m.hooks?.some((h) => h.command?.includes("contextstream"));
11586
- });
11587
- existingHooks[hookType] = [...filtered, ...matchers];
11588
- }
11589
- settings.hooks = existingHooks;
11590
- return settings;
11591
- }
11592
- async function installClaudeCodeHooks(options) {
11593
- const result = { scripts: [], settings: [] };
11594
- result.scripts.push(
11595
- "npx @contextstream/mcp-server hook pre-tool-use",
11596
- "npx @contextstream/mcp-server hook user-prompt-submit"
11597
- );
11598
- if (options.includePreCompact) {
11599
- result.scripts.push("npx @contextstream/mcp-server hook pre-compact");
11600
- }
11601
- if (options.includeMediaAware !== false) {
11602
- result.scripts.push("npx @contextstream/mcp-server hook media-aware");
11603
- }
11604
- if (options.includePostWrite !== false) {
11605
- result.scripts.push("npx @contextstream/mcp-server hook post-write");
11606
- }
11607
- if (options.includeAutoRules !== false) {
11608
- result.scripts.push("npx @contextstream/mcp-server hook auto-rules");
11609
- }
11610
- const hooksConfig = buildHooksConfig({
11611
- includePreCompact: options.includePreCompact,
11612
- includeMediaAware: options.includeMediaAware,
11613
- includePostWrite: options.includePostWrite,
11614
- includeAutoRules: options.includeAutoRules
11615
- });
11616
- if (options.scope === "user" || options.scope === "both") {
11617
- const settingsPath = getClaudeSettingsPath("user");
11618
- if (!options.dryRun) {
11619
- const existing = await readClaudeSettings("user");
11620
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
11621
- await writeClaudeSettings(merged, "user");
11622
- }
11623
- result.settings.push(settingsPath);
11624
- }
11625
- if ((options.scope === "project" || options.scope === "both") && options.projectPath) {
11626
- const settingsPath = getClaudeSettingsPath("project", options.projectPath);
11627
- if (!options.dryRun) {
11628
- const existing = await readClaudeSettings("project", options.projectPath);
11629
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
11630
- await writeClaudeSettings(merged, "project", options.projectPath);
11631
- }
11632
- result.settings.push(settingsPath);
11633
- }
11634
- return result;
11635
- }
11636
- function getIndexStatusPath() {
11637
- return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
11638
- }
11639
- async function readIndexStatus() {
11640
- const statusPath = getIndexStatusPath();
11641
- try {
11642
- const content = await fs4.readFile(statusPath, "utf-8");
11643
- return JSON.parse(content);
11644
- } catch {
11645
- return { version: 1, projects: {} };
12645
+ ### \u{1F680} FAST PATH: Simple Utility Operations
12646
+
12647
+ **For simple utility commands, SKIP the ceremony and just execute directly:**
12648
+
12649
+ | Command Type | Just Call | Skip |
12650
+ |--------------|-----------|------|
12651
+ | List workspaces | \`workspace(action="list")\` | init, context, capture |
12652
+ | List projects | \`project(action="list")\` | init, context, capture |
12653
+ | Show version | \`help(action="version")\` | init, context, capture |
12654
+ | List reminders | \`reminder(action="list")\` | init, context, capture |
12655
+ | Check auth | \`help(action="auth")\` | init, context, capture |
12656
+
12657
+ **Detect simple operations by these patterns:**
12658
+ - "list ...", "show ...", "what are my ...", "get ..."
12659
+ - Single-action queries with no context dependency
12660
+ - User just wants data, not analysis or coding help
12661
+
12662
+ **DO NOT add overhead for utility operations:**
12663
+ - \u274C Don't call init just to list workspaces
12664
+ - \u274C Don't call context for simple queries
12665
+ - \u274C Don't capture "listed workspaces" as an event (that's noise)
12666
+
12667
+ **Use full context ceremony ONLY for:**
12668
+ - Coding tasks (edit, create, refactor, debug)
12669
+ - Search/discovery (finding code, understanding architecture)
12670
+ - Tasks where past decisions or lessons matter
12671
+
12672
+ ### Lessons (Past Mistakes)
12673
+
12674
+ - After \`init\`: Check for \`lessons\` field and apply before work
12675
+ - Before risky work: \`session(action="get_lessons", query="<topic>")\`
12676
+ - On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
12677
+
12678
+ ### Context Pressure & Compaction
12679
+
12680
+ - If \`context\` returns high/critical \`context_pressure\`: call \`session(action="capture", ...)\` to save state
12681
+ - PreCompact hooks automatically save snapshots before compaction (if installed)
12682
+
12683
+ ### Enhanced Context (Warnings)
12684
+
12685
+ \`context\` returns server-side \`warnings\` for lessons, risky actions, and breaking changes.
12686
+ When warnings are present: **STOP**, acknowledge them, explain mitigation, then proceed.
12687
+
12688
+ ### Automatic Context Restoration
12689
+
12690
+ **Context restoration is now enabled by default.** Every \`init\` call automatically:
12691
+ - Restores context from recent snapshots (if available)
12692
+ - Returns \`restored_context\` field with snapshot data
12693
+ - Sets \`is_post_compact=true\` in response when restoration occurs
12694
+
12695
+ **No special handling needed after compaction** - just call \`init\` normally.
12696
+
12697
+ To disable automatic restoration:
12698
+ - Pass \`is_post_compact=false\` in the API call
12699
+ - Or set \`CONTEXTSTREAM_RESTORE_CONTEXT=false\` environment variable
12700
+
12701
+ ### Notices - MUST HANDLE IMMEDIATELY
12702
+
12703
+ - **[VERSION_NOTICE]**: Tell the user about the update and command to run
12704
+ - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
12705
+ - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
12706
+
12707
+ ### Plans & Tasks
12708
+
12709
+ When user asks for a plan, use ContextStream (not EnterPlanMode):
12710
+ 1. \`session(action="capture_plan", title="...", steps=[...])\`
12711
+ 2. \`memory(action="create_task", title="...", plan_id="<id>")\`
12712
+
12713
+ ### Workspace-Only Mode (Multi-Project Folders)
12714
+
12715
+ If working in a parent folder containing multiple projects:
12716
+ \`\`\`
12717
+ init(folder_path="...", skip_project_creation=true)
12718
+ \`\`\`
12719
+
12720
+ This enables workspace-level memory and context without project-specific indexing.
12721
+ Use for monorepos or folders with multiple independent projects.
12722
+
12723
+ Full docs: https://contextstream.io/docs/mcp/tools
12724
+ `.trim();
12725
+ var TEMPLATES = {
12726
+ codex: {
12727
+ filename: "AGENTS.md",
12728
+ description: "Codex CLI agent instructions",
12729
+ build: (rules) => `# Codex CLI Instructions
12730
+ ${rules}
12731
+ `
12732
+ },
12733
+ cursor: {
12734
+ filename: ".cursorrules",
12735
+ description: "Cursor AI rules",
12736
+ build: (rules) => `# Cursor Rules
12737
+ ${rules}
12738
+ `
12739
+ },
12740
+ cline: {
12741
+ filename: ".clinerules",
12742
+ description: "Cline AI rules",
12743
+ build: (rules) => `# Cline Rules
12744
+ ${rules}
12745
+ `
12746
+ },
12747
+ kilo: {
12748
+ filename: ".kilocode/rules/contextstream.md",
12749
+ description: "Kilo Code AI rules",
12750
+ build: (rules) => `# Kilo Code Rules
12751
+ ${rules}
12752
+ `
12753
+ },
12754
+ roo: {
12755
+ filename: ".roo/rules/contextstream.md",
12756
+ description: "Roo Code AI rules",
12757
+ build: (rules) => `# Roo Code Rules
12758
+ ${rules}
12759
+ `
12760
+ },
12761
+ claude: {
12762
+ filename: "CLAUDE.md",
12763
+ description: "Claude Code instructions",
12764
+ build: (rules) => `# Claude Code Instructions
12765
+ ${rules}
12766
+ `
12767
+ },
12768
+ aider: {
12769
+ filename: ".aider.conf.yml",
12770
+ description: "Aider configuration with system prompt",
12771
+ build: (rules) => `# Aider Configuration
12772
+ # Note: Aider uses different config format - this adds to the system prompt
12773
+
12774
+ # Add ContextStream guidance to conventions
12775
+ conventions: |
12776
+ ${rules.split("\n").map((line) => " " + line).join("\n")}
12777
+ `
12778
+ },
12779
+ antigravity: {
12780
+ filename: "GEMINI.md",
12781
+ description: "Google Antigravity AI rules",
12782
+ build: (rules) => `# Antigravity Agent Rules
12783
+ ${rules}
12784
+ `
11646
12785
  }
12786
+ };
12787
+ function getAvailableEditors() {
12788
+ return Object.keys(TEMPLATES);
11647
12789
  }
11648
- async function writeIndexStatus(status) {
11649
- const statusPath = getIndexStatusPath();
11650
- const dir = path5.dirname(statusPath);
11651
- await fs4.mkdir(dir, { recursive: true });
11652
- await fs4.writeFile(statusPath, JSON.stringify(status, null, 2));
11653
- }
11654
- async function markProjectIndexed(projectPath, options) {
11655
- const status = await readIndexStatus();
11656
- const resolvedPath = path5.resolve(projectPath);
11657
- status.projects[resolvedPath] = {
11658
- indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
11659
- project_id: options?.project_id,
11660
- project_name: options?.project_name
11661
- };
11662
- await writeIndexStatus(status);
11663
- }
11664
- function getClineHooksDir(scope, projectPath) {
11665
- if (scope === "global") {
11666
- return path5.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
11667
- }
11668
- if (!projectPath) {
11669
- throw new Error("projectPath required for project scope");
11670
- }
11671
- return path5.join(projectPath, ".clinerules", "hooks");
12790
+ function getTemplate(editor) {
12791
+ return TEMPLATES[editor.toLowerCase()] || null;
11672
12792
  }
11673
- var CLINE_HOOK_WRAPPER = (hookName) => `#!/bin/bash
11674
- # ContextStream ${hookName} Hook Wrapper for Cline/Roo/Kilo Code
11675
- # Calls the Node.js hook via npx
11676
- exec npx @contextstream/mcp-server hook ${hookName}
12793
+ function generateRuleContent(editor, options) {
12794
+ const template = getTemplate(editor);
12795
+ if (!template) return null;
12796
+ const mode = options?.mode || "dynamic";
12797
+ const rules = mode === "full" ? CONTEXTSTREAM_RULES_FULL : mode === "minimal" ? CONTEXTSTREAM_RULES_MINIMAL : CONTEXTSTREAM_RULES_DYNAMIC;
12798
+ let content = template.build(rules);
12799
+ if (options?.workspaceName || options?.projectName) {
12800
+ const header = `
12801
+ # Workspace: ${options.workspaceName || "Unknown"}
12802
+ ${options.projectName ? `# Project: ${options.projectName}` : ""}
12803
+ ${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
12804
+
11677
12805
  `;
11678
- async function installClineHookScripts(options) {
11679
- const hooksDir = getClineHooksDir(options.scope, options.projectPath);
11680
- await fs4.mkdir(hooksDir, { recursive: true });
11681
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
11682
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
11683
- const postToolUsePath = path5.join(hooksDir, "PostToolUse");
11684
- await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
11685
- await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
11686
- const result = {
11687
- preToolUse: preToolUsePath,
11688
- userPromptSubmit: userPromptPath
11689
- };
11690
- if (options.includePostWrite !== false) {
11691
- await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
11692
- result.postToolUse = postToolUsePath;
11693
- }
11694
- return result;
11695
- }
11696
- function getRooCodeHooksDir(scope, projectPath) {
11697
- if (scope === "global") {
11698
- return path5.join(homedir2(), ".roo", "hooks");
11699
- }
11700
- if (!projectPath) {
11701
- throw new Error("projectPath required for project scope");
11702
- }
11703
- return path5.join(projectPath, ".roo", "hooks");
11704
- }
11705
- async function installRooCodeHookScripts(options) {
11706
- const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
11707
- await fs4.mkdir(hooksDir, { recursive: true });
11708
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
11709
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
11710
- const postToolUsePath = path5.join(hooksDir, "PostToolUse");
11711
- await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
11712
- await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
11713
- const result = {
11714
- preToolUse: preToolUsePath,
11715
- userPromptSubmit: userPromptPath
11716
- };
11717
- if (options.includePostWrite !== false) {
11718
- await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
11719
- result.postToolUse = postToolUsePath;
12806
+ content = header + content;
11720
12807
  }
11721
- return result;
11722
- }
11723
- function getKiloCodeHooksDir(scope, projectPath) {
11724
- if (scope === "global") {
11725
- return path5.join(homedir2(), ".kilocode", "hooks");
12808
+ if (options?.additionalRules) {
12809
+ content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
11726
12810
  }
11727
- if (!projectPath) {
11728
- throw new Error("projectPath required for project scope");
12811
+ if (editor.toLowerCase() === "claude") {
12812
+ content = applyMcpToolPrefix(content, `mcp__${DEFAULT_CLAUDE_MCP_SERVER_NAME}__`);
11729
12813
  }
11730
- return path5.join(projectPath, ".kilocode", "hooks");
11731
- }
11732
- async function installKiloCodeHookScripts(options) {
11733
- const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
11734
- await fs4.mkdir(hooksDir, { recursive: true });
11735
- const preToolUsePath = path5.join(hooksDir, "PreToolUse");
11736
- const userPromptPath = path5.join(hooksDir, "UserPromptSubmit");
11737
- const postToolUsePath = path5.join(hooksDir, "PostToolUse");
11738
- await fs4.writeFile(preToolUsePath, CLINE_HOOK_WRAPPER("pre-tool-use"), { mode: 493 });
11739
- await fs4.writeFile(userPromptPath, CLINE_HOOK_WRAPPER("user-prompt-submit"), { mode: 493 });
11740
- const result = {
11741
- preToolUse: preToolUsePath,
11742
- userPromptSubmit: userPromptPath
12814
+ return {
12815
+ filename: template.filename,
12816
+ content: content.trim() + "\n"
11743
12817
  };
11744
- if (options.includePostWrite !== false) {
11745
- await fs4.writeFile(postToolUsePath, CLINE_HOOK_WRAPPER("post-write"), { mode: 493 });
11746
- result.postToolUse = postToolUsePath;
11747
- }
11748
- return result;
11749
12818
  }
11750
- function getCursorHooksConfigPath(scope, projectPath) {
11751
- if (scope === "global") {
11752
- return path5.join(homedir2(), ".cursor", "hooks.json");
11753
- }
11754
- if (!projectPath) {
11755
- throw new Error("projectPath required for project scope");
11756
- }
11757
- return path5.join(projectPath, ".cursor", "hooks.json");
12819
+ function generateAllRuleFiles(options) {
12820
+ return getAvailableEditors().map((editor) => {
12821
+ const result = generateRuleContent(editor, options);
12822
+ if (!result) return null;
12823
+ return { editor, ...result };
12824
+ }).filter((r) => r !== null);
11758
12825
  }
11759
- function getCursorHooksDir(scope, projectPath) {
11760
- if (scope === "global") {
11761
- return path5.join(homedir2(), ".cursor", "hooks");
12826
+
12827
+ // src/tool-catalog.ts
12828
+ var TOOL_CATALOG = [
12829
+ {
12830
+ name: "Session",
12831
+ tools: [
12832
+ { name: "init", hint: "start-conv" },
12833
+ { name: "smart", hint: "each-msg" },
12834
+ { name: "capture", hint: "save" },
12835
+ { name: "recall", hint: "find" },
12836
+ { name: "remember", hint: "quick" },
12837
+ { name: "compress", hint: "end" },
12838
+ { name: "summary", hint: "brief" },
12839
+ { name: "delta", hint: "changes" },
12840
+ { name: "get_lessons", hint: "learn" },
12841
+ { name: "capture_lesson", hint: "mistake" },
12842
+ { name: "get_user_context", hint: "prefs" },
12843
+ { name: "smart_search", hint: "deep-find" },
12844
+ // Plan actions
12845
+ { name: "capture_plan", hint: "save-plan" },
12846
+ { name: "get_plan", hint: "get-plan" },
12847
+ { name: "update_plan", hint: "edit-plan" },
12848
+ { name: "list_plans", hint: "list-plans" }
12849
+ ]
12850
+ },
12851
+ {
12852
+ name: "Search",
12853
+ tools: [
12854
+ { name: "semantic", hint: "meaning" },
12855
+ { name: "hybrid", hint: "combo" },
12856
+ { name: "keyword", hint: "exact" },
12857
+ { name: "pattern", hint: "code" }
12858
+ ]
12859
+ },
12860
+ {
12861
+ name: "Memory",
12862
+ tools: [
12863
+ { name: "create_event", hint: "new" },
12864
+ { name: "list_events", hint: "list" },
12865
+ { name: "get_event", hint: "get" },
12866
+ { name: "update_event", hint: "edit" },
12867
+ { name: "delete_event", hint: "rm" },
12868
+ { name: "search", hint: "find" },
12869
+ { name: "decisions", hint: "choices" },
12870
+ { name: "timeline", hint: "history" },
12871
+ { name: "distill_event", hint: "extract" },
12872
+ // Task actions
12873
+ { name: "create_task", hint: "new-task" },
12874
+ { name: "get_task", hint: "get-task" },
12875
+ { name: "update_task", hint: "edit-task" },
12876
+ { name: "delete_task", hint: "rm-task" },
12877
+ { name: "list_tasks", hint: "list-tasks" },
12878
+ { name: "reorder_tasks", hint: "sort-tasks" }
12879
+ ]
12880
+ },
12881
+ {
12882
+ name: "Knowledge",
12883
+ tools: [
12884
+ { name: "create_node", hint: "new" },
12885
+ { name: "list_nodes", hint: "list" },
12886
+ { name: "get_node", hint: "get" },
12887
+ { name: "update_node", hint: "edit" },
12888
+ { name: "delete_node", hint: "rm" },
12889
+ { name: "supersede_node", hint: "replace" }
12890
+ ]
12891
+ },
12892
+ {
12893
+ name: "Graph",
12894
+ tools: [
12895
+ { name: "related", hint: "links" },
12896
+ { name: "path", hint: "trace" },
12897
+ { name: "decisions", hint: "choices" },
12898
+ { name: "dependencies", hint: "deps" },
12899
+ { name: "ingest", hint: "build" },
12900
+ { name: "impact", hint: "changes" },
12901
+ { name: "contradictions", hint: "conflicts" }
12902
+ ]
12903
+ },
12904
+ {
12905
+ name: "Media",
12906
+ tools: [
12907
+ { name: "index", hint: "add-media" },
12908
+ { name: "status", hint: "progress" },
12909
+ { name: "search", hint: "find-clip" },
12910
+ { name: "get_clip", hint: "get-segment" },
12911
+ { name: "list", hint: "browse" },
12912
+ { name: "delete", hint: "remove" }
12913
+ ]
12914
+ },
12915
+ {
12916
+ name: "Workspace",
12917
+ tools: [
12918
+ { name: "list", hint: "" },
12919
+ { name: "get", hint: "" },
12920
+ { name: "create", hint: "" },
12921
+ { name: "associate", hint: "link-folder" },
12922
+ { name: "bootstrap", hint: "new-ws" }
12923
+ ]
12924
+ },
12925
+ {
12926
+ name: "Project",
12927
+ tools: [
12928
+ { name: "list", hint: "" },
12929
+ { name: "get", hint: "" },
12930
+ { name: "create", hint: "" },
12931
+ { name: "index", hint: "scan-code" },
12932
+ { name: "files", hint: "list-files" },
12933
+ { name: "overview", hint: "summary" }
12934
+ ]
12935
+ },
12936
+ {
12937
+ name: "AI",
12938
+ tools: [
12939
+ { name: "context", hint: "smart-ctx" },
12940
+ { name: "plan", hint: "generate" },
12941
+ { name: "tasks", hint: "breakdown" },
12942
+ { name: "embeddings", hint: "vectors" }
12943
+ ]
12944
+ },
12945
+ {
12946
+ name: "Notion",
12947
+ tools: [
12948
+ { name: "create_page", hint: "new-page" },
12949
+ { name: "search_pages", hint: "find" },
12950
+ { name: "list_databases", hint: "list-dbs" },
12951
+ { name: "get_page", hint: "get" },
12952
+ { name: "query_database", hint: "query-db" },
12953
+ { name: "update_page", hint: "edit" },
12954
+ { name: "stats", hint: "overview" },
12955
+ { name: "activity", hint: "recent" },
12956
+ { name: "knowledge", hint: "insights" },
12957
+ { name: "summary", hint: "brief" }
12958
+ ]
11762
12959
  }
11763
- if (!projectPath) {
11764
- throw new Error("projectPath required for project scope");
12960
+ ];
12961
+ function generateToolCatalog(format = "grouped", category) {
12962
+ let categories = TOOL_CATALOG;
12963
+ if (category) {
12964
+ const filtered = TOOL_CATALOG.filter((c) => c.name.toLowerCase() === category.toLowerCase());
12965
+ if (filtered.length > 0) {
12966
+ categories = filtered;
12967
+ }
11765
12968
  }
11766
- return path5.join(projectPath, ".cursor", "hooks");
11767
- }
11768
- async function readCursorHooksConfig(scope, projectPath) {
11769
- const configPath = getCursorHooksConfigPath(scope, projectPath);
11770
- try {
11771
- const content = await fs4.readFile(configPath, "utf-8");
11772
- return JSON.parse(content);
11773
- } catch {
11774
- return { version: 1, hooks: {} };
12969
+ switch (format) {
12970
+ case "minimal":
12971
+ return generateMinimal(categories);
12972
+ case "full":
12973
+ return generateFull(categories);
12974
+ case "grouped":
12975
+ default:
12976
+ return generateGrouped(categories);
11775
12977
  }
11776
12978
  }
11777
- async function writeCursorHooksConfig(config, scope, projectPath) {
11778
- const configPath = getCursorHooksConfigPath(scope, projectPath);
11779
- const dir = path5.dirname(configPath);
11780
- await fs4.mkdir(dir, { recursive: true });
11781
- await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
12979
+ function generateGrouped(categories) {
12980
+ return categories.map((cat) => {
12981
+ const tools = cat.tools.map((t) => t.hint ? `${t.name}(${t.hint})` : t.name).join(" ");
12982
+ return `${cat.name}: ${tools}`;
12983
+ }).join("\n");
11782
12984
  }
11783
- async function installCursorHookScripts(options) {
11784
- const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
11785
- await fs4.mkdir(hooksDir, { recursive: true });
11786
- const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
11787
- const filterContextStreamHooks = (hooks) => {
11788
- if (!hooks) return [];
11789
- return hooks.filter((h) => {
11790
- const hook = h;
11791
- return !hook.command?.includes("contextstream");
11792
- });
11793
- };
11794
- const filteredPreToolUse = filterContextStreamHooks(existingConfig.hooks.preToolUse);
11795
- const filteredBeforeSubmit = filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt);
11796
- const config = {
11797
- version: 1,
11798
- hooks: {
11799
- ...existingConfig.hooks,
11800
- preToolUse: [
11801
- ...filteredPreToolUse,
11802
- {
11803
- command: "npx @contextstream/mcp-server hook pre-tool-use",
11804
- type: "command",
11805
- timeout: 5,
11806
- matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
11807
- }
11808
- ],
11809
- beforeSubmitPrompt: [
11810
- ...filteredBeforeSubmit,
11811
- {
11812
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
11813
- type: "command",
11814
- timeout: 5
11815
- }
11816
- ]
11817
- }
11818
- };
11819
- await writeCursorHooksConfig(config, options.scope, options.projectPath);
11820
- const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
11821
- return {
11822
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
11823
- beforeSubmitPrompt: "npx @contextstream/mcp-server hook user-prompt-submit",
11824
- config: configPath
11825
- };
12985
+ function generateMinimal(categories) {
12986
+ return categories.map((cat) => {
12987
+ const tools = cat.tools.map((t) => t.name).join("|");
12988
+ return `${cat.name}:${tools}`;
12989
+ }).join("\n");
11826
12990
  }
11827
- async function installEditorHooks(options) {
11828
- const { editor, scope, projectPath, includePreCompact, includePostWrite } = options;
11829
- switch (editor) {
11830
- case "claude": {
11831
- if (scope === "project" && !projectPath) {
11832
- throw new Error("projectPath required for project scope");
11833
- }
11834
- const scripts = await installHookScripts({ includePreCompact });
11835
- const hooksConfig = buildHooksConfig({ includePreCompact, includePostWrite });
11836
- const settingsScope = scope === "global" ? "user" : "project";
11837
- const existing = await readClaudeSettings(settingsScope, projectPath);
11838
- const merged = mergeHooksIntoSettings(existing, hooksConfig);
11839
- await writeClaudeSettings(merged, settingsScope, projectPath);
11840
- const installed = [scripts.preToolUse, scripts.userPrompt];
11841
- if (scripts.preCompact) installed.push(scripts.preCompact);
11842
- return {
11843
- editor: "claude",
11844
- installed,
11845
- hooksDir: getHooksDir()
11846
- };
11847
- }
11848
- case "cline": {
11849
- const scripts = await installClineHookScripts({ scope, projectPath, includePostWrite });
11850
- const installed = [scripts.preToolUse, scripts.userPromptSubmit];
11851
- if (scripts.postToolUse) installed.push(scripts.postToolUse);
11852
- return {
11853
- editor: "cline",
11854
- installed,
11855
- hooksDir: getClineHooksDir(scope, projectPath)
11856
- };
11857
- }
11858
- case "roo": {
11859
- const scripts = await installRooCodeHookScripts({ scope, projectPath, includePostWrite });
11860
- const installed = [scripts.preToolUse, scripts.userPromptSubmit];
11861
- if (scripts.postToolUse) installed.push(scripts.postToolUse);
11862
- return {
11863
- editor: "roo",
11864
- installed,
11865
- hooksDir: getRooCodeHooksDir(scope, projectPath)
11866
- };
11867
- }
11868
- case "kilo": {
11869
- const scripts = await installKiloCodeHookScripts({ scope, projectPath, includePostWrite });
11870
- const installed = [scripts.preToolUse, scripts.userPromptSubmit];
11871
- if (scripts.postToolUse) installed.push(scripts.postToolUse);
11872
- return {
11873
- editor: "kilo",
11874
- installed,
11875
- hooksDir: getKiloCodeHooksDir(scope, projectPath)
11876
- };
11877
- }
11878
- case "cursor": {
11879
- const scripts = await installCursorHookScripts({ scope, projectPath });
11880
- return {
11881
- editor: "cursor",
11882
- installed: [scripts.preToolUse, scripts.beforeSubmitPrompt],
11883
- hooksDir: getCursorHooksDir(scope, projectPath)
11884
- };
12991
+ function generateFull(categories) {
12992
+ const lines = [];
12993
+ for (const cat of categories) {
12994
+ lines.push(`## ${cat.name}`);
12995
+ for (const tool of cat.tools) {
12996
+ const prefix = cat.name.toLowerCase().replace(/\s+/g, "_");
12997
+ const fullName = `${prefix}_${tool.name}`;
12998
+ lines.push(`- ${fullName}: ${tool.hint || "standard CRUD"}`);
11885
12999
  }
11886
- default:
11887
- throw new Error(`Unsupported editor: ${editor}`);
11888
13000
  }
13001
+ return lines.join("\n");
11889
13002
  }
11890
- async function installAllEditorHooks(options) {
11891
- const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
11892
- const results = [];
11893
- for (const editor of editors) {
11894
- try {
11895
- const result = await installEditorHooks({
11896
- editor,
11897
- scope: options.scope,
11898
- projectPath: options.projectPath,
11899
- includePreCompact: options.includePreCompact,
11900
- includePostWrite: options.includePostWrite
11901
- });
11902
- results.push(result);
11903
- } catch (error) {
11904
- console.error(`Failed to install hooks for ${editor}:`, error);
11905
- }
11906
- }
11907
- return results;
13003
+ function getCoreToolsHint() {
13004
+ return `Session: init(start) smart(each-msg) capture(save) recall(find) remember(quick)`;
11908
13005
  }
11909
13006
 
13007
+ // src/tools.ts
13008
+ init_hooks_config();
13009
+
11910
13010
  // src/token-savings.ts
11911
13011
  var TOKEN_SAVINGS_FORMULA_VERSION = 1;
11912
13012
  var MAX_CHARS_PER_EVENT = 2e7;
@@ -12122,6 +13222,28 @@ ${LESSONS_REMINDER_PREFIX}
12122
13222
  ${lessonLines.join("\n")}
12123
13223
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
12124
13224
  }
13225
+ var REMEMBER_REMINDER_PREFIX = `
13226
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13227
+ \u{1F4CC} USER PREFERENCES - MUST FOLLOW
13228
+ These are user-specified preferences that MUST be checked and followed.
13229
+ \u26A0\uFE0F IMPORTANT: Always verify your actions align with these preferences.
13230
+ `;
13231
+ function generateRememberReminder(result) {
13232
+ const rememberItems = result.remember_items;
13233
+ if (!rememberItems || rememberItems.length === 0) {
13234
+ return "";
13235
+ }
13236
+ const itemLines = rememberItems.slice(0, 5).map((item, i) => {
13237
+ const importance = item.importance === "critical" ? "\u{1F6A8}" : "\u{1F4CC}";
13238
+ const content = item.content || "";
13239
+ return `${i + 1}. ${importance} ${content.slice(0, 150)}`;
13240
+ });
13241
+ return `
13242
+
13243
+ ${REMEMBER_REMINDER_PREFIX}
13244
+ ${itemLines.join("\n")}
13245
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
13246
+ }
12125
13247
  function generateRulesUpdateWarning(rulesNotice) {
12126
13248
  if (!rulesNotice || rulesNotice.status !== "behind" && rulesNotice.status !== "missing") {
12127
13249
  return "";
@@ -15925,6 +17047,10 @@ ${noticeLines.filter(Boolean).join("\n")}`;
15925
17047
  if (lessonsReminder) {
15926
17048
  text = `${text}${lessonsReminder}`;
15927
17049
  }
17050
+ const rememberReminder = generateRememberReminder(result);
17051
+ if (rememberReminder) {
17052
+ text = `${text}${rememberReminder}`;
17053
+ }
15928
17054
  if (SEARCH_RULES_REMINDER_ENABLED) {
15929
17055
  text = `${text}
15930
17056
 
@@ -23495,7 +24621,6 @@ import { createServer } from "node:http";
23495
24621
  import { randomUUID as randomUUID2 } from "node:crypto";
23496
24622
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23497
24623
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
23498
- init_version();
23499
24624
  var HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
23500
24625
  var PORT = Number.parseInt(process.env.MCP_HTTP_PORT || "8787", 10);
23501
24626
  var MCP_PATH = process.env.MCP_HTTP_PATH || "/mcp";
@@ -23774,7 +24899,6 @@ async function runHttpGateway() {
23774
24899
  }
23775
24900
 
23776
24901
  // src/index.ts
23777
- init_version();
23778
24902
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
23779
24903
  import { homedir as homedir10 } from "os";
23780
24904
  import { join as join14 } from "path";
@@ -23785,8 +24909,6 @@ import * as path8 from "node:path";
23785
24909
  import { homedir as homedir5 } from "node:os";
23786
24910
  import { stdin, stdout } from "node:process";
23787
24911
  import { createInterface } from "node:readline/promises";
23788
- init_rules_templates();
23789
- init_version();
23790
24912
 
23791
24913
  // src/credentials.ts
23792
24914
  import * as fs6 from "node:fs/promises";
@@ -23851,6 +24973,7 @@ async function writeSavedCredentials(input) {
23851
24973
  }
23852
24974
 
23853
24975
  // src/setup.ts
24976
+ init_hooks_config();
23854
24977
  var EDITOR_LABELS = {
23855
24978
  codex: "Codex CLI",
23856
24979
  claude: "Claude Code",