@contextstream/mcp-server 0.4.16 → 0.4.19

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
@@ -4050,6 +4050,131 @@ var coerce = {
4050
4050
  };
4051
4051
  var NEVER = INVALID;
4052
4052
 
4053
+ // src/version.ts
4054
+ import { createRequire } from "module";
4055
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4056
+ import { homedir } from "os";
4057
+ import { join } from "path";
4058
+ var UPGRADE_COMMAND = "npm update -g @contextstream/mcp-server";
4059
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
4060
+ function getVersion() {
4061
+ try {
4062
+ const require2 = createRequire(import.meta.url);
4063
+ const pkg = require2("../package.json");
4064
+ const version = pkg?.version;
4065
+ if (typeof version === "string" && version.trim()) return version.trim();
4066
+ } catch {
4067
+ }
4068
+ return "unknown";
4069
+ }
4070
+ var VERSION = getVersion();
4071
+ function compareVersions(v1, v2) {
4072
+ const parts1 = v1.split(".").map(Number);
4073
+ const parts2 = v2.split(".").map(Number);
4074
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
4075
+ const p1 = parts1[i] ?? 0;
4076
+ const p2 = parts2[i] ?? 0;
4077
+ if (p1 < p2) return -1;
4078
+ if (p1 > p2) return 1;
4079
+ }
4080
+ return 0;
4081
+ }
4082
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4083
+ var latestVersionPromise = null;
4084
+ function getCacheFilePath() {
4085
+ return join(homedir(), ".contextstream", "version-cache.json");
4086
+ }
4087
+ function readCache() {
4088
+ try {
4089
+ const cacheFile = getCacheFilePath();
4090
+ if (!existsSync(cacheFile)) return null;
4091
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
4092
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
4093
+ return data;
4094
+ } catch {
4095
+ return null;
4096
+ }
4097
+ }
4098
+ function writeCache(latestVersion) {
4099
+ try {
4100
+ const configDir = join(homedir(), ".contextstream");
4101
+ if (!existsSync(configDir)) {
4102
+ mkdirSync(configDir, { recursive: true });
4103
+ }
4104
+ const cacheFile = getCacheFilePath();
4105
+ writeFileSync(cacheFile, JSON.stringify({
4106
+ latestVersion,
4107
+ checkedAt: Date.now()
4108
+ }));
4109
+ } catch {
4110
+ }
4111
+ }
4112
+ async function fetchLatestVersion() {
4113
+ try {
4114
+ const controller = new AbortController();
4115
+ const timeout = setTimeout(() => controller.abort(), 5e3);
4116
+ const response = await fetch(NPM_LATEST_URL, {
4117
+ signal: controller.signal,
4118
+ headers: { "Accept": "application/json" }
4119
+ });
4120
+ clearTimeout(timeout);
4121
+ if (!response.ok) return null;
4122
+ const data = await response.json();
4123
+ return typeof data.version === "string" ? data.version : null;
4124
+ } catch {
4125
+ return null;
4126
+ }
4127
+ }
4128
+ async function resolveLatestVersion() {
4129
+ const cached = readCache();
4130
+ if (cached) return cached.latestVersion;
4131
+ if (!latestVersionPromise) {
4132
+ latestVersionPromise = fetchLatestVersion().finally(() => {
4133
+ latestVersionPromise = null;
4134
+ });
4135
+ }
4136
+ const latestVersion = await latestVersionPromise;
4137
+ if (latestVersion) {
4138
+ writeCache(latestVersion);
4139
+ }
4140
+ return latestVersion;
4141
+ }
4142
+ async function checkForUpdates() {
4143
+ const notice = await getUpdateNotice();
4144
+ if (notice?.behind) {
4145
+ showUpdateWarning(notice.current, notice.latest);
4146
+ }
4147
+ }
4148
+ function showUpdateWarning(currentVersion, latestVersion) {
4149
+ console.error("");
4150
+ 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");
4151
+ console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
4152
+ console.error("");
4153
+ console.error(` Run: ${UPGRADE_COMMAND}`);
4154
+ console.error("");
4155
+ console.error(" Then restart your AI tool to use the new version.");
4156
+ 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");
4157
+ console.error("");
4158
+ }
4159
+ async function getUpdateNotice() {
4160
+ const currentVersion = VERSION;
4161
+ if (currentVersion === "unknown") return null;
4162
+ try {
4163
+ const latestVersion = await resolveLatestVersion();
4164
+ if (!latestVersion) return null;
4165
+ if (compareVersions(currentVersion, latestVersion) < 0) {
4166
+ return {
4167
+ current: currentVersion,
4168
+ latest: latestVersion,
4169
+ behind: true,
4170
+ upgrade_command: UPGRADE_COMMAND
4171
+ };
4172
+ }
4173
+ } catch {
4174
+ }
4175
+ return null;
4176
+ }
4177
+
4053
4178
  // src/config.ts
4054
4179
  var DEFAULT_API_URL = "https://api.contextstream.io";
4055
4180
  function parseBooleanEnv(value) {
@@ -4065,7 +4190,7 @@ var configSchema = external_exports.object({
4065
4190
  jwt: external_exports.string().min(1).optional(),
4066
4191
  defaultWorkspaceId: external_exports.string().uuid().optional(),
4067
4192
  defaultProjectId: external_exports.string().uuid().optional(),
4068
- userAgent: external_exports.string().default("contextstream-mcp/0.1.0"),
4193
+ userAgent: external_exports.string().default(`contextstream-mcp/${VERSION}`),
4069
4194
  allowHeaderAuth: external_exports.boolean().optional(),
4070
4195
  contextPackEnabled: external_exports.boolean().default(true)
4071
4196
  });
@@ -4732,131 +4857,6 @@ var CacheKeys = {
4732
4857
  };
4733
4858
  var globalCache = new MemoryCache();
4734
4859
 
4735
- // src/version.ts
4736
- import { createRequire } from "module";
4737
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
4738
- import { homedir } from "os";
4739
- import { join as join3 } from "path";
4740
- var UPGRADE_COMMAND = "npm update -g @contextstream/mcp-server";
4741
- var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
4742
- function getVersion() {
4743
- try {
4744
- const require2 = createRequire(import.meta.url);
4745
- const pkg = require2("../package.json");
4746
- const version = pkg?.version;
4747
- if (typeof version === "string" && version.trim()) return version.trim();
4748
- } catch {
4749
- }
4750
- return "unknown";
4751
- }
4752
- var VERSION = getVersion();
4753
- function compareVersions(v1, v2) {
4754
- const parts1 = v1.split(".").map(Number);
4755
- const parts2 = v2.split(".").map(Number);
4756
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
4757
- const p1 = parts1[i] ?? 0;
4758
- const p2 = parts2[i] ?? 0;
4759
- if (p1 < p2) return -1;
4760
- if (p1 > p2) return 1;
4761
- }
4762
- return 0;
4763
- }
4764
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4765
- var latestVersionPromise = null;
4766
- function getCacheFilePath() {
4767
- return join3(homedir(), ".contextstream", "version-cache.json");
4768
- }
4769
- function readCache() {
4770
- try {
4771
- const cacheFile = getCacheFilePath();
4772
- if (!existsSync2(cacheFile)) return null;
4773
- const data = JSON.parse(readFileSync2(cacheFile, "utf-8"));
4774
- if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
4775
- return data;
4776
- } catch {
4777
- return null;
4778
- }
4779
- }
4780
- function writeCache(latestVersion) {
4781
- try {
4782
- const configDir = join3(homedir(), ".contextstream");
4783
- if (!existsSync2(configDir)) {
4784
- mkdirSync2(configDir, { recursive: true });
4785
- }
4786
- const cacheFile = getCacheFilePath();
4787
- writeFileSync2(cacheFile, JSON.stringify({
4788
- latestVersion,
4789
- checkedAt: Date.now()
4790
- }));
4791
- } catch {
4792
- }
4793
- }
4794
- async function fetchLatestVersion() {
4795
- try {
4796
- const controller = new AbortController();
4797
- const timeout = setTimeout(() => controller.abort(), 5e3);
4798
- const response = await fetch(NPM_LATEST_URL, {
4799
- signal: controller.signal,
4800
- headers: { "Accept": "application/json" }
4801
- });
4802
- clearTimeout(timeout);
4803
- if (!response.ok) return null;
4804
- const data = await response.json();
4805
- return typeof data.version === "string" ? data.version : null;
4806
- } catch {
4807
- return null;
4808
- }
4809
- }
4810
- async function resolveLatestVersion() {
4811
- const cached = readCache();
4812
- if (cached) return cached.latestVersion;
4813
- if (!latestVersionPromise) {
4814
- latestVersionPromise = fetchLatestVersion().finally(() => {
4815
- latestVersionPromise = null;
4816
- });
4817
- }
4818
- const latestVersion = await latestVersionPromise;
4819
- if (latestVersion) {
4820
- writeCache(latestVersion);
4821
- }
4822
- return latestVersion;
4823
- }
4824
- async function checkForUpdates() {
4825
- const notice = await getUpdateNotice();
4826
- if (notice?.behind) {
4827
- showUpdateWarning(notice.current, notice.latest);
4828
- }
4829
- }
4830
- function showUpdateWarning(currentVersion, latestVersion) {
4831
- console.error("");
4832
- 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");
4833
- console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
4834
- console.error("");
4835
- console.error(` Run: ${UPGRADE_COMMAND}`);
4836
- console.error("");
4837
- console.error(" Then restart your AI tool to use the new version.");
4838
- 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");
4839
- console.error("");
4840
- }
4841
- async function getUpdateNotice() {
4842
- const currentVersion = VERSION;
4843
- if (currentVersion === "unknown") return null;
4844
- try {
4845
- const latestVersion = await resolveLatestVersion();
4846
- if (!latestVersion) return null;
4847
- if (compareVersions(currentVersion, latestVersion) < 0) {
4848
- return {
4849
- current: currentVersion,
4850
- latest: latestVersion,
4851
- behind: true,
4852
- upgrade_command: UPGRADE_COMMAND
4853
- };
4854
- }
4855
- } catch {
4856
- }
4857
- return null;
4858
- }
4859
-
4860
4860
  // src/client.ts
4861
4861
  var uuidSchema = external_exports.string().uuid();
4862
4862
  function unwrapApiResponse(result) {
@@ -6587,7 +6587,10 @@ var ContextStreamClient = class {
6587
6587
  max_tokens: maxTokens,
6588
6588
  format,
6589
6589
  mode,
6590
- distill: params.distill
6590
+ distill: params.distill,
6591
+ client_version: VERSION,
6592
+ rules_version: VERSION,
6593
+ notice_inline: false
6591
6594
  }
6592
6595
  });
6593
6596
  const data = unwrapApiResponse(apiResult);
@@ -7469,9 +7472,11 @@ W:${wsHint}
7469
7472
  // src/tools.ts
7470
7473
  import * as fs3 from "node:fs";
7471
7474
  import * as path4 from "node:path";
7475
+ import { homedir as homedir2 } from "node:os";
7472
7476
 
7473
7477
  // src/rules-templates.ts
7474
7478
  var DEFAULT_CLAUDE_MCP_SERVER_NAME = "contextstream";
7479
+ var RULES_VERSION = VERSION === "unknown" ? "0.0.0" : VERSION;
7475
7480
  var CONTEXTSTREAM_TOOL_NAMES = [
7476
7481
  // Standalone tools (always present)
7477
7482
  "session_init",
@@ -7499,7 +7504,7 @@ var CONTEXTSTREAM_TOOL_NAMES = [
7499
7504
  ];
7500
7505
  function applyMcpToolPrefix(markdown, toolPrefix) {
7501
7506
  const toolPattern = CONTEXTSTREAM_TOOL_NAMES.join("|");
7502
- const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b`, "g");
7507
+ const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b(?=\\s*\\()`, "g");
7503
7508
  return markdown.replace(toolRegex, `${toolPrefix}$1`);
7504
7509
  }
7505
7510
  var CONTEXTSTREAM_RULES_FULL = `
@@ -7507,6 +7512,7 @@ var CONTEXTSTREAM_RULES_FULL = `
7507
7512
 
7508
7513
  You have access to ContextStream MCP tools for persistent memory and context.
7509
7514
  v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previous versions.
7515
+ Rules Version: ${RULES_VERSION}
7510
7516
 
7511
7517
  ## TL;DR - REQUIRED EVERY MESSAGE
7512
7518
 
@@ -7523,6 +7529,8 @@ v0.4.x uses **~11 consolidated domain tools** for ~75% token reduction vs previo
7523
7529
 
7524
7530
  **Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode\` and use standard \`context_smart\`.
7525
7531
 
7532
+ **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.
7533
+
7526
7534
  ---
7527
7535
 
7528
7536
  ## Consolidated Domain Tools Architecture
@@ -7578,6 +7586,13 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
7578
7586
 
7579
7587
  ---
7580
7588
 
7589
+ ### Rules Update Notices
7590
+
7591
+ - If you see **[RULES_NOTICE]**, update rules via \`generate_editor_rules(folder_path="<cwd>")\` (or rerun setup).
7592
+ - If you see **[VERSION_NOTICE]**, tell the user to update MCP using the provided command.
7593
+
7594
+ ---
7595
+
7581
7596
  ### Preferences & Lessons (Use Early)
7582
7597
 
7583
7598
  - If preferences/style matter: \`session(action="user_context")\`
@@ -7595,6 +7610,7 @@ Before searching files or code, confirm the project is indexed and the graph is
7595
7610
  - Local repo: \`project(action="ingest_local", path="<cwd>")\`
7596
7611
  - Otherwise: \`project(action="index")\`
7597
7612
  3. If graph queries are empty/unavailable: \`graph(action="ingest")\`
7613
+ 4. If indexing is in progress, tell the user and wait; do not fall back to local scans.
7598
7614
 
7599
7615
  Only after this preflight, proceed with search/analysis below.
7600
7616
 
@@ -7602,9 +7618,12 @@ Only after this preflight, proceed with search/analysis below.
7602
7618
 
7603
7619
  **Search order:**
7604
7620
  1. \`session(action="smart_search", query="...")\` - context-enriched
7605
- 2. \`search(mode="hybrid", query="...")\` - semantic + keyword
7606
- 3. \`graph(action="dependencies", ...)\` - code structure
7607
- 4. Local repo scans (rg/ls/find) - only if ContextStream returns no results
7621
+ 2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
7622
+ 3. \`project(action="files")\` - file tree/list (only when needed)
7623
+ 4. \`graph(action="dependencies", ...)\` - code structure
7624
+ 5. Local repo scans (rg/ls/find) - only if ContextStream returns no results or is unavailable
7625
+
7626
+ Use ContextStream results directly; only open files if you need exact code.
7608
7627
 
7609
7628
  **Code Analysis:**
7610
7629
  - Dependencies: \`graph(action="dependencies", file_path="...")\`
@@ -7684,6 +7703,7 @@ var CONTEXTSTREAM_RULES_MINIMAL = `
7684
7703
  ## ContextStream v0.4.x (Consolidated Domain Tools)
7685
7704
 
7686
7705
  v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous versions.
7706
+ Rules Version: ${RULES_VERSION}
7687
7707
 
7688
7708
  ### Required Every Message
7689
7709
 
@@ -7697,6 +7717,8 @@ v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous v
7697
7717
 
7698
7718
  **Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode\`.
7699
7719
 
7720
+ **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.
7721
+
7700
7722
  ### Quick Reference: Domain Tools
7701
7723
 
7702
7724
  | Tool | Common Usage |
@@ -7705,7 +7727,7 @@ v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous v
7705
7727
  | \`session\` | \`session(action="capture", ...)\` \u2014 actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search |
7706
7728
  | \`memory\` | \`memory(action="list_events", ...)\` \u2014 CRUD for events/nodes, search, decisions, timeline, summary |
7707
7729
  | \`graph\` | \`graph(action="dependencies", ...)\` \u2014 dependencies, impact, call_path, related, ingest |
7708
- | \`project\` | \`project(action="list", ...)\` \u2014 list, get, create, update, index, statistics |
7730
+ | \`project\` | \`project(action="list", ...)\` - list, get, create, update, index, overview, statistics, files, index_status, ingest_local |
7709
7731
  | \`workspace\` | \`workspace(action="list", ...)\` \u2014 list, get, associate, bootstrap |
7710
7732
  | \`integration\` | \`integration(provider="github", action="search", ...)\` \u2014 GitHub/Slack integration |
7711
7733
  | \`help\` | \`help(action="tools")\` \u2014 tools, auth, version, editor_rules |
@@ -7715,8 +7737,10 @@ v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous v
7715
7737
  - **First message**: Always call \`session_init\` with context_hint
7716
7738
  - **Every message after**: Always call \`context_smart\` BEFORE responding (semantic search for relevant context)
7717
7739
  - **Before searching files/code**: Check \`project(action="index_status")\`; if missing/stale run \`project(action="ingest_local", path="<cwd>")\` or \`project(action="index")\`, and use \`graph(action="ingest")\` if needed
7718
- - **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before local repo scans
7740
+ - **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before any local repo scans
7741
+ - **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
7719
7742
  - **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
7743
+ - **On [RULES_NOTICE]**: Use \`generate_editor_rules(folder_path="<cwd>")\` to update rules
7720
7744
  - **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
7721
7745
  - **On mistakes/corrections**: Immediately capture lessons with \`session(action="capture_lesson")\`
7722
7746
 
@@ -8042,6 +8066,284 @@ function normalizeUuid(value) {
8042
8066
  if (!value) return void 0;
8043
8067
  return uuidSchema2.safeParse(value).success ? value : void 0;
8044
8068
  }
8069
+ var RULES_NOTICE_CACHE_TTL_MS = 10 * 60 * 1e3;
8070
+ var RULES_VERSION_REGEX = /Rules Version:\s*([0-9][0-9A-Za-z.\-]*)/i;
8071
+ var RULES_PROJECT_FILES = {
8072
+ codex: "AGENTS.md",
8073
+ claude: "CLAUDE.md",
8074
+ cursor: ".cursorrules",
8075
+ windsurf: ".windsurfrules",
8076
+ cline: ".clinerules",
8077
+ kilo: path4.join(".kilocode", "rules", "contextstream.md"),
8078
+ roo: path4.join(".roo", "rules", "contextstream.md"),
8079
+ aider: ".aider.conf.yml"
8080
+ };
8081
+ var RULES_GLOBAL_FILES = {
8082
+ codex: [path4.join(homedir2(), ".codex", "AGENTS.md")],
8083
+ windsurf: [path4.join(homedir2(), ".codeium", "windsurf", "memories", "global_rules.md")],
8084
+ kilo: [path4.join(homedir2(), ".kilocode", "rules", "contextstream.md")],
8085
+ roo: [path4.join(homedir2(), ".roo", "rules", "contextstream.md")]
8086
+ };
8087
+ var rulesNoticeCache = /* @__PURE__ */ new Map();
8088
+ function compareVersions2(v1, v2) {
8089
+ const parts1 = v1.split(".").map(Number);
8090
+ const parts2 = v2.split(".").map(Number);
8091
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
8092
+ const p1 = parts1[i] ?? 0;
8093
+ const p2 = parts2[i] ?? 0;
8094
+ if (p1 < p2) return -1;
8095
+ if (p1 > p2) return 1;
8096
+ }
8097
+ return 0;
8098
+ }
8099
+ function extractRulesVersion(content) {
8100
+ const match = content.match(RULES_VERSION_REGEX);
8101
+ return match?.[1]?.trim() ?? null;
8102
+ }
8103
+ function detectEditorFromClientName(clientName) {
8104
+ if (!clientName) return null;
8105
+ const normalized = clientName.toLowerCase().trim();
8106
+ if (normalized.includes("cursor")) return "cursor";
8107
+ if (normalized.includes("windsurf") || normalized.includes("codeium")) return "windsurf";
8108
+ if (normalized.includes("claude")) return "claude";
8109
+ if (normalized.includes("cline")) return "cline";
8110
+ if (normalized.includes("kilo")) return "kilo";
8111
+ if (normalized.includes("roo")) return "roo";
8112
+ if (normalized.includes("codex")) return "codex";
8113
+ if (normalized.includes("aider")) return "aider";
8114
+ return null;
8115
+ }
8116
+ function resolveRulesCandidatePaths(folderPath, editorKey) {
8117
+ const candidates = /* @__PURE__ */ new Set();
8118
+ const addProject = (key) => {
8119
+ if (!folderPath) return;
8120
+ const rel = RULES_PROJECT_FILES[key];
8121
+ if (rel) {
8122
+ candidates.add(path4.join(folderPath, rel));
8123
+ }
8124
+ };
8125
+ const addGlobal = (key) => {
8126
+ const paths = RULES_GLOBAL_FILES[key];
8127
+ if (!paths) return;
8128
+ for (const p of paths) {
8129
+ candidates.add(p);
8130
+ }
8131
+ };
8132
+ if (editorKey) {
8133
+ addProject(editorKey);
8134
+ addGlobal(editorKey);
8135
+ } else {
8136
+ for (const key of Object.keys(RULES_PROJECT_FILES)) {
8137
+ addProject(key);
8138
+ addGlobal(key);
8139
+ }
8140
+ }
8141
+ return Array.from(candidates);
8142
+ }
8143
+ function resolveFolderPath(inputPath, sessionManager) {
8144
+ if (inputPath) return inputPath;
8145
+ const fromSession = sessionManager?.getFolderPath();
8146
+ if (fromSession) return fromSession;
8147
+ const ctxPath = sessionManager?.getContext();
8148
+ const contextFolder = ctxPath && typeof ctxPath.folder_path === "string" ? ctxPath.folder_path : null;
8149
+ if (contextFolder) return contextFolder;
8150
+ const cwd = process.cwd();
8151
+ const indicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
8152
+ const hasIndicator = indicators.some((entry) => {
8153
+ try {
8154
+ return fs3.existsSync(path4.join(cwd, entry));
8155
+ } catch {
8156
+ return false;
8157
+ }
8158
+ });
8159
+ return hasIndicator ? cwd : null;
8160
+ }
8161
+ function getRulesNotice(folderPath, clientName) {
8162
+ if (!RULES_VERSION || RULES_VERSION === "0.0.0") return null;
8163
+ const editorKey = detectEditorFromClientName(clientName);
8164
+ if (!folderPath && !editorKey) {
8165
+ return null;
8166
+ }
8167
+ const cacheKey = `${folderPath ?? "none"}|${editorKey ?? "all"}`;
8168
+ const cached = rulesNoticeCache.get(cacheKey);
8169
+ if (cached && Date.now() - cached.checkedAt < RULES_NOTICE_CACHE_TTL_MS) {
8170
+ return cached.notice;
8171
+ }
8172
+ const candidates = resolveRulesCandidatePaths(folderPath, editorKey);
8173
+ const existing = candidates.filter((filePath) => fs3.existsSync(filePath));
8174
+ if (existing.length === 0) {
8175
+ const updateCommand2 = folderPath ? `generate_editor_rules(folder_path="${folderPath}")` : 'generate_editor_rules(folder_path="<cwd>")';
8176
+ const notice2 = {
8177
+ status: "missing",
8178
+ latest: RULES_VERSION,
8179
+ files_checked: candidates,
8180
+ update_tool: "generate_editor_rules",
8181
+ update_args: {
8182
+ ...folderPath ? { folder_path: folderPath } : {},
8183
+ editors: editorKey ? [editorKey] : ["all"]
8184
+ },
8185
+ update_command: updateCommand2
8186
+ };
8187
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice: notice2 });
8188
+ return notice2;
8189
+ }
8190
+ const filesMissingVersion = [];
8191
+ const filesOutdated = [];
8192
+ const versions = [];
8193
+ for (const filePath of existing) {
8194
+ try {
8195
+ const content = fs3.readFileSync(filePath, "utf-8");
8196
+ const version = extractRulesVersion(content);
8197
+ if (!version) {
8198
+ filesMissingVersion.push(filePath);
8199
+ continue;
8200
+ }
8201
+ versions.push(version);
8202
+ if (compareVersions2(version, RULES_VERSION) < 0) {
8203
+ filesOutdated.push(filePath);
8204
+ }
8205
+ } catch {
8206
+ filesMissingVersion.push(filePath);
8207
+ }
8208
+ }
8209
+ if (filesOutdated.length === 0 && filesMissingVersion.length === 0) {
8210
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice: null });
8211
+ return null;
8212
+ }
8213
+ const current = versions.sort(compareVersions2).at(-1);
8214
+ const updateCommand = folderPath ? `generate_editor_rules(folder_path="${folderPath}")` : 'generate_editor_rules(folder_path="<cwd>")';
8215
+ const notice = {
8216
+ status: filesOutdated.length > 0 ? "behind" : "unknown",
8217
+ current,
8218
+ latest: RULES_VERSION,
8219
+ files_checked: existing,
8220
+ ...filesOutdated.length > 0 ? { files_outdated: filesOutdated } : {},
8221
+ ...filesMissingVersion.length > 0 ? { files_missing_version: filesMissingVersion } : {},
8222
+ update_tool: "generate_editor_rules",
8223
+ update_args: {
8224
+ ...folderPath ? { folder_path: folderPath } : {},
8225
+ editors: editorKey ? [editorKey] : ["all"]
8226
+ },
8227
+ update_command: updateCommand
8228
+ };
8229
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice });
8230
+ return notice;
8231
+ }
8232
+ var CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
8233
+ var CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
8234
+ var LEGACY_CONTEXTSTREAM_HINTS = [
8235
+ "contextstream integration",
8236
+ "contextstream v0.4",
8237
+ "contextstream v0.3",
8238
+ "contextstream (standard)",
8239
+ "contextstream (consolidated",
8240
+ "contextstream mcp",
8241
+ "contextstream tools"
8242
+ ];
8243
+ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
8244
+ "contextstream",
8245
+ "tl;dr",
8246
+ "required every message",
8247
+ "quick reference",
8248
+ "tool catalog",
8249
+ "consolidated domain tools",
8250
+ "standalone tools",
8251
+ "domain tools",
8252
+ "why context_smart",
8253
+ "recommended token budgets",
8254
+ "rules update notices",
8255
+ "preferences & lessons",
8256
+ "index & graph preflight",
8257
+ "search & code intelligence",
8258
+ "distillation",
8259
+ "when to capture",
8260
+ "behavior rules",
8261
+ "plans & tasks",
8262
+ "complete action reference"
8263
+ ];
8264
+ function wrapWithMarkers(content) {
8265
+ return `${CONTEXTSTREAM_START_MARKER}
8266
+ ${content.trim()}
8267
+ ${CONTEXTSTREAM_END_MARKER}`;
8268
+ }
8269
+ function isLegacyContextStreamRules(content) {
8270
+ const lower = content.toLowerCase();
8271
+ if (!lower.includes("contextstream")) return false;
8272
+ if (!LEGACY_CONTEXTSTREAM_HINTS.some((hint) => lower.includes(hint))) return false;
8273
+ const headingRegex = /^#{1,6}\s+(.+)$/gm;
8274
+ let hasHeading = false;
8275
+ let match;
8276
+ while ((match = headingRegex.exec(content)) !== null) {
8277
+ hasHeading = true;
8278
+ const heading = match[1].trim().toLowerCase();
8279
+ const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS.some((prefix) => heading.startsWith(prefix));
8280
+ if (!allowed) return false;
8281
+ }
8282
+ return hasHeading;
8283
+ }
8284
+ async function upsertRuleFile(filePath, content) {
8285
+ await fs3.promises.mkdir(path4.dirname(filePath), { recursive: true });
8286
+ const wrappedContent = wrapWithMarkers(content);
8287
+ let existing = "";
8288
+ try {
8289
+ existing = await fs3.promises.readFile(filePath, "utf8");
8290
+ } catch {
8291
+ }
8292
+ if (!existing) {
8293
+ await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
8294
+ return "created";
8295
+ }
8296
+ const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
8297
+ const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
8298
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
8299
+ const before = existing.substring(0, startIdx);
8300
+ const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length);
8301
+ const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
8302
+ await fs3.promises.writeFile(filePath, updated.trim() + "\n", "utf8");
8303
+ return "updated";
8304
+ }
8305
+ if (isLegacyContextStreamRules(existing)) {
8306
+ await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
8307
+ return "updated";
8308
+ }
8309
+ const joined = existing.trimEnd() + "\n\n" + wrappedContent + "\n";
8310
+ await fs3.promises.writeFile(filePath, joined, "utf8");
8311
+ return "appended";
8312
+ }
8313
+ async function writeEditorRules(options) {
8314
+ const editors = options.editors && options.editors.length > 0 ? options.editors : getAvailableEditors();
8315
+ const results = [];
8316
+ for (const editor of editors) {
8317
+ const rule = generateRuleContent(editor, {
8318
+ workspaceName: options.workspaceName,
8319
+ workspaceId: options.workspaceId,
8320
+ projectName: options.projectName,
8321
+ additionalRules: options.additionalRules,
8322
+ mode: options.mode
8323
+ });
8324
+ if (!rule) {
8325
+ results.push({ editor, filename: "", status: "unknown editor" });
8326
+ continue;
8327
+ }
8328
+ const filePath = path4.join(options.folderPath, rule.filename);
8329
+ try {
8330
+ const status = await upsertRuleFile(filePath, rule.content);
8331
+ results.push({ editor, filename: rule.filename, status });
8332
+ } catch (err) {
8333
+ results.push({
8334
+ editor,
8335
+ filename: rule.filename,
8336
+ status: `error: ${err.message}`
8337
+ });
8338
+ }
8339
+ }
8340
+ for (const key of rulesNoticeCache.keys()) {
8341
+ if (key.startsWith(`${options.folderPath}|`)) {
8342
+ rulesNoticeCache.delete(key);
8343
+ }
8344
+ }
8345
+ return results;
8346
+ }
8045
8347
  var WRITE_VERBS = /* @__PURE__ */ new Set([
8046
8348
  "create",
8047
8349
  "update",
@@ -9766,30 +10068,13 @@ Access: Free`,
9766
10068
  };
9767
10069
  fs3.writeFileSync(configPath, JSON.stringify(config, null, 2));
9768
10070
  if (input.generate_editor_rules) {
9769
- for (const editor of getAvailableEditors()) {
9770
- const rule = generateRuleContent(editor, {
9771
- workspaceId,
9772
- projectName: input.name
9773
- });
9774
- if (rule) {
9775
- const filePath = path4.join(input.folder_path, rule.filename);
9776
- try {
9777
- let existingContent = "";
9778
- try {
9779
- existingContent = fs3.readFileSync(filePath, "utf-8");
9780
- } catch {
9781
- }
9782
- if (!existingContent) {
9783
- fs3.writeFileSync(filePath, rule.content);
9784
- rulesGenerated.push(rule.filename);
9785
- } else if (!existingContent.includes("ContextStream")) {
9786
- fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
9787
- rulesGenerated.push(rule.filename + " (appended)");
9788
- }
9789
- } catch {
9790
- }
9791
- }
9792
- }
10071
+ const ruleResults = await writeEditorRules({
10072
+ folderPath: input.folder_path,
10073
+ editors: getAvailableEditors(),
10074
+ workspaceId,
10075
+ projectName: input.name
10076
+ });
10077
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
9793
10078
  }
9794
10079
  } catch (err) {
9795
10080
  console.error("[ContextStream] Failed to write project config:", err);
@@ -10738,6 +11023,25 @@ This does semantic search on the first message. You only need context_smart on s
10738
11023
  if (sessionManager) {
10739
11024
  sessionManager.markInitialized(result);
10740
11025
  }
11026
+ const folderPathForRules = input.folder_path || ideRoots[0] || resolveFolderPath(void 0, sessionManager);
11027
+ if (sessionManager && folderPathForRules) {
11028
+ sessionManager.setFolderPath(folderPathForRules);
11029
+ }
11030
+ let rulesNotice = null;
11031
+ if (folderPathForRules || detectedClientInfo?.name) {
11032
+ rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
11033
+ if (rulesNotice) {
11034
+ result.rules_notice = rulesNotice;
11035
+ }
11036
+ }
11037
+ let versionNotice = null;
11038
+ try {
11039
+ versionNotice = await getUpdateNotice();
11040
+ } catch {
11041
+ }
11042
+ if (versionNotice) {
11043
+ result.version_notice = versionNotice;
11044
+ }
10741
11045
  const workspaceId = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
10742
11046
  if (workspaceId && AUTO_HIDE_INTEGRATIONS) {
10743
11047
  try {
@@ -10809,6 +11113,19 @@ This does semantic search on the first message. You only need context_smart on s
10809
11113
  } else if (workspaceWarning) {
10810
11114
  text = [`Warning: ${workspaceWarning}`, "", formatContent(result)].join("\n");
10811
11115
  }
11116
+ const noticeLines = [];
11117
+ if (rulesNotice) {
11118
+ const current = rulesNotice.current ?? "unknown";
11119
+ noticeLines.push(`[RULES_NOTICE] status=${rulesNotice.status} current=${current} latest=${rulesNotice.latest} update="${rulesNotice.update_command}"`);
11120
+ }
11121
+ if (versionNotice?.behind) {
11122
+ noticeLines.push(`[VERSION_NOTICE] current=${versionNotice.current} latest=${versionNotice.latest} upgrade="${versionNotice.upgrade_command}"`);
11123
+ }
11124
+ if (noticeLines.length > 0) {
11125
+ text = `${text}
11126
+
11127
+ ${noticeLines.join("\n")}`;
11128
+ }
10812
11129
  return { content: [{ type: "text", text }], structuredContent: toStructured(result) };
10813
11130
  }
10814
11131
  );
@@ -10898,32 +11215,13 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
10898
11215
  const result = await client.associateWorkspace(input);
10899
11216
  let rulesGenerated = [];
10900
11217
  if (input.generate_editor_rules) {
10901
- const fs6 = await import("fs");
10902
- const path7 = await import("path");
10903
- for (const editor of getAvailableEditors()) {
10904
- const rule = generateRuleContent(editor, {
10905
- workspaceName: input.workspace_name,
10906
- workspaceId: input.workspace_id
10907
- });
10908
- if (rule) {
10909
- const filePath = path7.join(input.folder_path, rule.filename);
10910
- try {
10911
- let existingContent = "";
10912
- try {
10913
- existingContent = fs6.readFileSync(filePath, "utf-8");
10914
- } catch {
10915
- }
10916
- if (!existingContent) {
10917
- fs6.writeFileSync(filePath, rule.content);
10918
- rulesGenerated.push(rule.filename);
10919
- } else if (!existingContent.includes("ContextStream Integration")) {
10920
- fs6.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
10921
- rulesGenerated.push(rule.filename + " (appended)");
10922
- }
10923
- } catch {
10924
- }
10925
- }
10926
- }
11218
+ const ruleResults = await writeEditorRules({
11219
+ folderPath: input.folder_path,
11220
+ editors: getAvailableEditors(),
11221
+ workspaceName: input.workspace_name,
11222
+ workspaceId: input.workspace_id
11223
+ });
11224
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
10927
11225
  }
10928
11226
  const response = {
10929
11227
  ...result,
@@ -11003,31 +11301,13 @@ Behavior:
11003
11301
  });
11004
11302
  let rulesGenerated = [];
11005
11303
  if (input.generate_editor_rules) {
11006
- const fs6 = await import("fs");
11007
- const path7 = await import("path");
11008
- for (const editor of getAvailableEditors()) {
11009
- const rule = generateRuleContent(editor, {
11010
- workspaceName: newWorkspace.name || input.workspace_name,
11011
- workspaceId: newWorkspace.id
11012
- });
11013
- if (!rule) continue;
11014
- const filePath = path7.join(folderPath, rule.filename);
11015
- try {
11016
- let existingContent = "";
11017
- try {
11018
- existingContent = fs6.readFileSync(filePath, "utf-8");
11019
- } catch {
11020
- }
11021
- if (!existingContent) {
11022
- fs6.writeFileSync(filePath, rule.content);
11023
- rulesGenerated.push(rule.filename);
11024
- } else if (!existingContent.includes("ContextStream Integration")) {
11025
- fs6.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
11026
- rulesGenerated.push(rule.filename + " (appended)");
11027
- }
11028
- } catch {
11029
- }
11030
- }
11304
+ const ruleResults = await writeEditorRules({
11305
+ folderPath,
11306
+ editors: getAvailableEditors(),
11307
+ workspaceName: newWorkspace.name || input.workspace_name,
11308
+ workspaceId: newWorkspace.id
11309
+ });
11310
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
11031
11311
  }
11032
11312
  const session = await client.initSession(
11033
11313
  {
@@ -11432,7 +11712,7 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
11432
11712
  These rules instruct the AI to automatically use ContextStream for memory and context.
11433
11713
  Supported editors: ${getAvailableEditors().join(", ")}`,
11434
11714
  inputSchema: external_exports.object({
11435
- folder_path: external_exports.string().describe("Absolute path to the project folder"),
11715
+ folder_path: external_exports.string().optional().describe("Absolute path to the project folder (defaults to IDE root/cwd)"),
11436
11716
  editors: external_exports.array(external_exports.enum(["codex", "windsurf", "cursor", "cline", "kilo", "roo", "claude", "aider", "all"])).optional().describe("Which editors to generate rules for. Defaults to all."),
11437
11717
  workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
11438
11718
  workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
@@ -11443,58 +11723,48 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
11443
11723
  })
11444
11724
  },
11445
11725
  async (input) => {
11446
- const fs6 = await import("fs");
11447
- const path7 = await import("path");
11726
+ const folderPath = resolveFolderPath(input.folder_path, sessionManager);
11727
+ if (!folderPath) {
11728
+ return errorResult("Error: folder_path is required. Provide folder_path or run from a project directory.");
11729
+ }
11448
11730
  const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
11449
11731
  const results = [];
11450
- for (const editor of editors) {
11451
- const rule = generateRuleContent(editor, {
11452
- workspaceName: input.workspace_name,
11453
- workspaceId: input.workspace_id,
11454
- projectName: input.project_name,
11455
- additionalRules: input.additional_rules,
11456
- mode: input.mode
11457
- });
11458
- if (!rule) {
11459
- results.push({ editor, filename: "", status: "unknown editor" });
11460
- continue;
11461
- }
11462
- const filePath = path7.join(input.folder_path, rule.filename);
11463
- if (input.dry_run) {
11732
+ if (input.dry_run) {
11733
+ for (const editor of editors) {
11734
+ const rule = generateRuleContent(editor, {
11735
+ workspaceName: input.workspace_name,
11736
+ workspaceId: input.workspace_id,
11737
+ projectName: input.project_name,
11738
+ additionalRules: input.additional_rules,
11739
+ mode: input.mode
11740
+ });
11741
+ if (!rule) {
11742
+ results.push({ editor, filename: "", status: "unknown editor" });
11743
+ continue;
11744
+ }
11464
11745
  results.push({
11465
11746
  editor,
11466
11747
  filename: rule.filename,
11467
- status: "dry run - would create",
11748
+ status: "dry run - would update",
11468
11749
  content: rule.content
11469
11750
  });
11470
- } else {
11471
- try {
11472
- let existingContent = "";
11473
- try {
11474
- existingContent = fs6.readFileSync(filePath, "utf-8");
11475
- } catch {
11476
- }
11477
- if (existingContent && !existingContent.includes("ContextStream Integration")) {
11478
- const updatedContent = existingContent + "\n\n" + rule.content;
11479
- fs6.writeFileSync(filePath, updatedContent);
11480
- results.push({ editor, filename: rule.filename, status: "appended to existing" });
11481
- } else {
11482
- fs6.writeFileSync(filePath, rule.content);
11483
- results.push({ editor, filename: rule.filename, status: "created" });
11484
- }
11485
- } catch (err) {
11486
- results.push({
11487
- editor,
11488
- filename: rule.filename,
11489
- status: `error: ${err.message}`
11490
- });
11491
- }
11492
11751
  }
11752
+ } else {
11753
+ const writeResults = await writeEditorRules({
11754
+ folderPath,
11755
+ editors,
11756
+ workspaceName: input.workspace_name,
11757
+ workspaceId: input.workspace_id,
11758
+ projectName: input.project_name,
11759
+ additionalRules: input.additional_rules,
11760
+ mode: input.mode
11761
+ });
11762
+ results.push(...writeResults);
11493
11763
  }
11494
11764
  const summary = {
11495
- folder: input.folder_path,
11765
+ folder: folderPath,
11496
11766
  results,
11497
- message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${results.filter((r) => r.status === "created" || r.status.includes("appended")).length} rule files.`
11767
+ message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${results.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").length} rule files.`
11498
11768
  };
11499
11769
  return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
11500
11770
  }
@@ -11768,11 +12038,27 @@ This saves ~80% tokens compared to including full chat history.`,
11768
12038
  const footer = `
11769
12039
  ---
11770
12040
  \u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}`;
11771
- const versionNoticeLine = result.version_notice?.behind ? `
11772
- [VERSION_NOTICE] current=${result.version_notice.current} latest=${result.version_notice.latest} upgrade="${result.version_notice.upgrade_command}"` : "";
12041
+ const folderPathForRules = resolveFolderPath(void 0, sessionManager);
12042
+ const rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
12043
+ let versionNotice = result.version_notice;
12044
+ if (!versionNotice) {
12045
+ try {
12046
+ versionNotice = await getUpdateNotice();
12047
+ } catch {
12048
+ }
12049
+ }
12050
+ const rulesNoticeLine = rulesNotice ? `
12051
+ [RULES_NOTICE] status=${rulesNotice.status} current=${rulesNotice.current ?? "unknown"} latest=${rulesNotice.latest} update="${rulesNotice.update_command}"` : "";
12052
+ const versionNoticeLine = versionNotice?.behind ? `
12053
+ [VERSION_NOTICE] current=${versionNotice.current} latest=${versionNotice.latest} upgrade="${versionNotice.upgrade_command}"` : "";
12054
+ const enrichedResult = {
12055
+ ...result,
12056
+ ...rulesNotice ? { rules_notice: rulesNotice } : {},
12057
+ ...versionNotice ? { version_notice: versionNotice } : {}
12058
+ };
11773
12059
  return {
11774
- content: [{ type: "text", text: result.context + footer + versionNoticeLine }],
11775
- structuredContent: toStructured(result)
12060
+ content: [{ type: "text", text: result.context + footer + rulesNoticeLine + versionNoticeLine }],
12061
+ structuredContent: toStructured(enrichedResult)
11776
12062
  };
11777
12063
  }
11778
12064
  );
@@ -14827,6 +15113,12 @@ var SessionManager = class {
14827
15113
  getContext() {
14828
15114
  return this.context;
14829
15115
  }
15116
+ /**
15117
+ * Get the current folder path (if known)
15118
+ */
15119
+ getFolderPath() {
15120
+ return this.folderPath;
15121
+ }
14830
15122
  /**
14831
15123
  * Mark session as manually initialized (e.g., when session_init is called explicitly)
14832
15124
  */
@@ -14838,6 +15130,10 @@ var SessionManager = class {
14838
15130
  if (workspaceId || projectId) {
14839
15131
  this.client.setDefaults({ workspace_id: workspaceId, project_id: projectId });
14840
15132
  }
15133
+ const contextFolderPath = typeof context.folder_path === "string" ? context.folder_path : void 0;
15134
+ if (contextFolderPath) {
15135
+ this.folderPath = contextFolderPath;
15136
+ }
14841
15137
  }
14842
15138
  /**
14843
15139
  * Set the folder path hint (can be passed from tools that know the workspace path)
@@ -14929,6 +15225,9 @@ var SessionManager = class {
14929
15225
  if (this.ideRoots.length === 0 && this.folderPath) {
14930
15226
  this.ideRoots = [this.folderPath];
14931
15227
  }
15228
+ if (this.ideRoots.length > 0) {
15229
+ this.folderPath = this.ideRoots[0];
15230
+ }
14932
15231
  this.initializationPromise = this._doInitialize();
14933
15232
  try {
14934
15233
  const result = await this.initializationPromise;
@@ -15374,25 +15673,25 @@ async function runHttpGateway() {
15374
15673
 
15375
15674
  // src/index.ts
15376
15675
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
15377
- import { homedir as homedir4 } from "os";
15676
+ import { homedir as homedir5 } from "os";
15378
15677
  import { join as join8 } from "path";
15379
15678
 
15380
15679
  // src/setup.ts
15381
15680
  import * as fs5 from "node:fs/promises";
15382
15681
  import * as path6 from "node:path";
15383
- import { homedir as homedir3 } from "node:os";
15682
+ import { homedir as homedir4 } from "node:os";
15384
15683
  import { stdin, stdout } from "node:process";
15385
15684
  import { createInterface } from "node:readline/promises";
15386
15685
 
15387
15686
  // src/credentials.ts
15388
15687
  import * as fs4 from "node:fs/promises";
15389
15688
  import * as path5 from "node:path";
15390
- import { homedir as homedir2 } from "node:os";
15689
+ import { homedir as homedir3 } from "node:os";
15391
15690
  function normalizeApiUrl(input) {
15392
15691
  return String(input ?? "").trim().replace(/\/+$/, "");
15393
15692
  }
15394
15693
  function credentialsFilePath() {
15395
- return path5.join(homedir2(), ".contextstream", "credentials.json");
15694
+ return path5.join(homedir3(), ".contextstream", "credentials.json");
15396
15695
  }
15397
15696
  function isRecord(value) {
15398
15697
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -15490,34 +15789,78 @@ async function fileExists(filePath) {
15490
15789
  return false;
15491
15790
  }
15492
15791
  }
15493
- var CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
15494
- var CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
15495
- function wrapWithMarkers(content) {
15496
- return `${CONTEXTSTREAM_START_MARKER}
15792
+ var CONTEXTSTREAM_START_MARKER2 = "<!-- BEGIN ContextStream -->";
15793
+ var CONTEXTSTREAM_END_MARKER2 = "<!-- END ContextStream -->";
15794
+ var LEGACY_CONTEXTSTREAM_HINTS2 = [
15795
+ "contextstream integration",
15796
+ "contextstream v0.4",
15797
+ "contextstream v0.3",
15798
+ "contextstream (standard)",
15799
+ "contextstream (consolidated",
15800
+ "contextstream mcp",
15801
+ "contextstream tools"
15802
+ ];
15803
+ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2 = [
15804
+ "contextstream",
15805
+ "tl;dr",
15806
+ "required every message",
15807
+ "quick reference",
15808
+ "tool catalog",
15809
+ "consolidated domain tools",
15810
+ "standalone tools",
15811
+ "domain tools",
15812
+ "why context_smart",
15813
+ "recommended token budgets",
15814
+ "rules update notices",
15815
+ "preferences & lessons",
15816
+ "index & graph preflight",
15817
+ "search & code intelligence",
15818
+ "distillation",
15819
+ "when to capture",
15820
+ "behavior rules",
15821
+ "plans & tasks",
15822
+ "complete action reference"
15823
+ ];
15824
+ function wrapWithMarkers2(content) {
15825
+ return `${CONTEXTSTREAM_START_MARKER2}
15497
15826
  ${content.trim()}
15498
- ${CONTEXTSTREAM_END_MARKER}`;
15827
+ ${CONTEXTSTREAM_END_MARKER2}`;
15828
+ }
15829
+ function isLegacyContextStreamRules2(content) {
15830
+ const lower = content.toLowerCase();
15831
+ if (!lower.includes("contextstream")) return false;
15832
+ if (!LEGACY_CONTEXTSTREAM_HINTS2.some((hint) => lower.includes(hint))) return false;
15833
+ const headingRegex = /^#{1,6}\s+(.+)$/gm;
15834
+ let hasHeading = false;
15835
+ let match;
15836
+ while ((match = headingRegex.exec(content)) !== null) {
15837
+ hasHeading = true;
15838
+ const heading = match[1].trim().toLowerCase();
15839
+ const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2.some((prefix) => heading.startsWith(prefix));
15840
+ if (!allowed) return false;
15841
+ }
15842
+ return hasHeading;
15499
15843
  }
15500
15844
  async function upsertTextFile(filePath, content, _marker) {
15501
15845
  await fs5.mkdir(path6.dirname(filePath), { recursive: true });
15502
15846
  const exists = await fileExists(filePath);
15503
- const wrappedContent = wrapWithMarkers(content);
15847
+ const wrappedContent = wrapWithMarkers2(content);
15504
15848
  if (!exists) {
15505
15849
  await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
15506
15850
  return "created";
15507
15851
  }
15508
15852
  const existing = await fs5.readFile(filePath, "utf8").catch(() => "");
15509
- const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
15510
- const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
15853
+ const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
15854
+ const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
15511
15855
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
15512
15856
  const before = existing.substring(0, startIdx);
15513
- const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length);
15857
+ const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length);
15514
15858
  const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
15515
15859
  await fs5.writeFile(filePath, updated.trim() + "\n", "utf8");
15516
15860
  return "updated";
15517
15861
  }
15518
- if (existing.includes("ContextStream")) {
15519
- const joined2 = existing.trimEnd() + "\n\n" + wrappedContent + "\n";
15520
- await fs5.writeFile(filePath, joined2, "utf8");
15862
+ if (isLegacyContextStreamRules2(existing)) {
15863
+ await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
15521
15864
  return "updated";
15522
15865
  }
15523
15866
  const joined = existing.trimEnd() + "\n\n" + wrappedContent + "\n";
@@ -15525,7 +15868,7 @@ async function upsertTextFile(filePath, content, _marker) {
15525
15868
  return "appended";
15526
15869
  }
15527
15870
  function globalRulesPathForEditor(editor) {
15528
- const home = homedir3();
15871
+ const home = homedir4();
15529
15872
  switch (editor) {
15530
15873
  case "codex":
15531
15874
  return path6.join(home, ".codex", "AGENTS.md");
@@ -15554,7 +15897,7 @@ async function anyPathExists(paths) {
15554
15897
  return false;
15555
15898
  }
15556
15899
  async function isCodexInstalled() {
15557
- const home = homedir3();
15900
+ const home = homedir4();
15558
15901
  const envHome = process.env.CODEX_HOME;
15559
15902
  const candidates = [
15560
15903
  envHome,
@@ -15565,7 +15908,7 @@ async function isCodexInstalled() {
15565
15908
  return anyPathExists(candidates);
15566
15909
  }
15567
15910
  async function isClaudeInstalled() {
15568
- const home = homedir3();
15911
+ const home = homedir4();
15569
15912
  const candidates = [
15570
15913
  path6.join(home, ".claude"),
15571
15914
  path6.join(home, ".config", "claude")
@@ -15581,7 +15924,7 @@ async function isClaudeInstalled() {
15581
15924
  return anyPathExists(candidates);
15582
15925
  }
15583
15926
  async function isWindsurfInstalled() {
15584
- const home = homedir3();
15927
+ const home = homedir4();
15585
15928
  const candidates = [
15586
15929
  path6.join(home, ".codeium"),
15587
15930
  path6.join(home, ".codeium", "windsurf"),
@@ -15600,7 +15943,7 @@ async function isWindsurfInstalled() {
15600
15943
  return anyPathExists(candidates);
15601
15944
  }
15602
15945
  async function isClineInstalled() {
15603
- const home = homedir3();
15946
+ const home = homedir4();
15604
15947
  const candidates = [
15605
15948
  path6.join(home, "Documents", "Cline"),
15606
15949
  path6.join(home, ".cline"),
@@ -15609,7 +15952,7 @@ async function isClineInstalled() {
15609
15952
  return anyPathExists(candidates);
15610
15953
  }
15611
15954
  async function isKiloInstalled() {
15612
- const home = homedir3();
15955
+ const home = homedir4();
15613
15956
  const candidates = [
15614
15957
  path6.join(home, ".kilocode"),
15615
15958
  path6.join(home, ".config", "kilocode")
@@ -15617,7 +15960,7 @@ async function isKiloInstalled() {
15617
15960
  return anyPathExists(candidates);
15618
15961
  }
15619
15962
  async function isRooInstalled() {
15620
- const home = homedir3();
15963
+ const home = homedir4();
15621
15964
  const candidates = [
15622
15965
  path6.join(home, ".roo"),
15623
15966
  path6.join(home, ".config", "roo")
@@ -15625,7 +15968,7 @@ async function isRooInstalled() {
15625
15968
  return anyPathExists(candidates);
15626
15969
  }
15627
15970
  async function isAiderInstalled() {
15628
- const home = homedir3();
15971
+ const home = homedir4();
15629
15972
  const candidates = [
15630
15973
  path6.join(home, ".aider.conf.yml"),
15631
15974
  path6.join(home, ".config", "aider")
@@ -15633,7 +15976,7 @@ async function isAiderInstalled() {
15633
15976
  return anyPathExists(candidates);
15634
15977
  }
15635
15978
  async function isCursorInstalled() {
15636
- const home = homedir3();
15979
+ const home = homedir4();
15637
15980
  const candidates = [path6.join(home, ".cursor")];
15638
15981
  if (process.platform === "darwin") {
15639
15982
  candidates.push("/Applications/Cursor.app");
@@ -15781,7 +16124,7 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
15781
16124
  return before === after ? "skipped" : "updated";
15782
16125
  }
15783
16126
  function claudeDesktopConfigPath() {
15784
- const home = homedir3();
16127
+ const home = homedir4();
15785
16128
  if (process.platform === "darwin") {
15786
16129
  return path6.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
15787
16130
  }
@@ -16185,7 +16528,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16185
16528
  if (mcpScope === "project" && editor !== "codex") continue;
16186
16529
  try {
16187
16530
  if (editor === "codex") {
16188
- const filePath = path6.join(homedir3(), ".codex", "config.toml");
16531
+ const filePath = path6.join(homedir4(), ".codex", "config.toml");
16189
16532
  if (dryRun) {
16190
16533
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16191
16534
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16197,7 +16540,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16197
16540
  continue;
16198
16541
  }
16199
16542
  if (editor === "windsurf") {
16200
- const filePath = path6.join(homedir3(), ".codeium", "windsurf", "mcp_config.json");
16543
+ const filePath = path6.join(homedir4(), ".codeium", "windsurf", "mcp_config.json");
16201
16544
  if (dryRun) {
16202
16545
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16203
16546
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16231,7 +16574,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16231
16574
  continue;
16232
16575
  }
16233
16576
  if (editor === "cursor") {
16234
- const filePath = path6.join(homedir3(), ".cursor", "mcp.json");
16577
+ const filePath = path6.join(homedir4(), ".cursor", "mcp.json");
16235
16578
  if (dryRun) {
16236
16579
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16237
16580
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16458,7 +16801,7 @@ Applying to ${projects.length} project(s)...`);
16458
16801
  // src/index.ts
16459
16802
  var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
16460
16803
  function showFirstRunMessage() {
16461
- const configDir = join8(homedir4(), ".contextstream");
16804
+ const configDir = join8(homedir5(), ".contextstream");
16462
16805
  const starShownFile = join8(configDir, ".star-shown");
16463
16806
  if (existsSync4(starShownFile)) {
16464
16807
  return;
@@ -4050,6 +4050,21 @@ var coerce = {
4050
4050
  };
4051
4051
  var NEVER = INVALID;
4052
4052
 
4053
+ // src/version.ts
4054
+ import { createRequire } from "module";
4055
+ function getVersion() {
4056
+ try {
4057
+ const require2 = createRequire(import.meta.url);
4058
+ const pkg = require2("../package.json");
4059
+ const version = pkg?.version;
4060
+ if (typeof version === "string" && version.trim()) return version.trim();
4061
+ } catch {
4062
+ }
4063
+ return "unknown";
4064
+ }
4065
+ var VERSION = getVersion();
4066
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4067
+
4053
4068
  // src/config.ts
4054
4069
  var DEFAULT_API_URL = "https://api.contextstream.io";
4055
4070
  function parseBooleanEnv(value) {
@@ -4065,7 +4080,7 @@ var configSchema = external_exports.object({
4065
4080
  jwt: external_exports.string().min(1).optional(),
4066
4081
  defaultWorkspaceId: external_exports.string().uuid().optional(),
4067
4082
  defaultProjectId: external_exports.string().uuid().optional(),
4068
- userAgent: external_exports.string().default("contextstream-mcp/0.1.0"),
4083
+ userAgent: external_exports.string().default(`contextstream-mcp/${VERSION}`),
4069
4084
  allowHeaderAuth: external_exports.boolean().optional(),
4070
4085
  contextPackEnabled: external_exports.boolean().default(true)
4071
4086
  });
@@ -4097,21 +4112,6 @@ function loadConfig() {
4097
4112
  return parsed.data;
4098
4113
  }
4099
4114
 
4100
- // src/version.ts
4101
- import { createRequire } from "module";
4102
- function getVersion() {
4103
- try {
4104
- const require2 = createRequire(import.meta.url);
4105
- const pkg = require2("../package.json");
4106
- const version = pkg?.version;
4107
- if (typeof version === "string" && version.trim()) return version.trim();
4108
- } catch {
4109
- }
4110
- return "unknown";
4111
- }
4112
- var VERSION = getVersion();
4113
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4114
-
4115
4115
  // src/test-server.ts
4116
4116
  var PORT = parseInt(process.env.MCP_TEST_PORT || "3099", 10);
4117
4117
  var pendingRequests = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
3
  "mcpName": "io.github.contextstreamio/mcp-server",
4
- "version": "0.4.16",
4
+ "version": "0.4.19",
5
5
  "description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
6
6
  "type": "module",
7
7
  "license": "MIT",