@contextstream/mcp-server 0.4.15 → 0.4.17

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",
@@ -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
 
@@ -7578,6 +7584,13 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
7578
7584
 
7579
7585
  ---
7580
7586
 
7587
+ ### Rules Update Notices
7588
+
7589
+ - If you see **[RULES_NOTICE]**, update rules via \`generate_editor_rules(folder_path="<cwd>")\` (or rerun setup).
7590
+ - If you see **[VERSION_NOTICE]**, tell the user to update MCP using the provided command.
7591
+
7592
+ ---
7593
+
7581
7594
  ### Preferences & Lessons (Use Early)
7582
7595
 
7583
7596
  - If preferences/style matter: \`session(action="user_context")\`
@@ -7595,6 +7608,7 @@ Before searching files or code, confirm the project is indexed and the graph is
7595
7608
  - Local repo: \`project(action="ingest_local", path="<cwd>")\`
7596
7609
  - Otherwise: \`project(action="index")\`
7597
7610
  3. If graph queries are empty/unavailable: \`graph(action="ingest")\`
7611
+ 4. If indexing is in progress, tell the user and wait; do not fall back to local scans.
7598
7612
 
7599
7613
  Only after this preflight, proceed with search/analysis below.
7600
7614
 
@@ -7602,9 +7616,12 @@ Only after this preflight, proceed with search/analysis below.
7602
7616
 
7603
7617
  **Search order:**
7604
7618
  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
7619
+ 2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
7620
+ 3. \`project(action="files")\` - file tree/list (only when needed)
7621
+ 4. \`graph(action="dependencies", ...)\` - code structure
7622
+ 5. Local repo scans (rg/ls/find) - only if ContextStream returns no results or is unavailable
7623
+
7624
+ Use ContextStream results directly; only open files if you need exact code.
7608
7625
 
7609
7626
  **Code Analysis:**
7610
7627
  - Dependencies: \`graph(action="dependencies", file_path="...")\`
@@ -7684,6 +7701,7 @@ var CONTEXTSTREAM_RULES_MINIMAL = `
7684
7701
  ## ContextStream v0.4.x (Consolidated Domain Tools)
7685
7702
 
7686
7703
  v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous versions.
7704
+ Rules Version: ${RULES_VERSION}
7687
7705
 
7688
7706
  ### Required Every Message
7689
7707
 
@@ -7705,7 +7723,7 @@ v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous v
7705
7723
  | \`session\` | \`session(action="capture", ...)\` \u2014 actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search |
7706
7724
  | \`memory\` | \`memory(action="list_events", ...)\` \u2014 CRUD for events/nodes, search, decisions, timeline, summary |
7707
7725
  | \`graph\` | \`graph(action="dependencies", ...)\` \u2014 dependencies, impact, call_path, related, ingest |
7708
- | \`project\` | \`project(action="list", ...)\` \u2014 list, get, create, update, index, statistics |
7726
+ | \`project\` | \`project(action="list", ...)\` - list, get, create, update, index, overview, statistics, files, index_status, ingest_local |
7709
7727
  | \`workspace\` | \`workspace(action="list", ...)\` \u2014 list, get, associate, bootstrap |
7710
7728
  | \`integration\` | \`integration(provider="github", action="search", ...)\` \u2014 GitHub/Slack integration |
7711
7729
  | \`help\` | \`help(action="tools")\` \u2014 tools, auth, version, editor_rules |
@@ -7715,8 +7733,10 @@ v0.4.x uses ~11 consolidated domain tools for ~75% token reduction vs previous v
7715
7733
  - **First message**: Always call \`session_init\` with context_hint
7716
7734
  - **Every message after**: Always call \`context_smart\` BEFORE responding (semantic search for relevant context)
7717
7735
  - **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
7736
+ - **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before any local repo scans
7737
+ - **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
7719
7738
  - **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
7739
+ - **On [RULES_NOTICE]**: Use \`generate_editor_rules(folder_path="<cwd>")\` to update rules
7720
7740
  - **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
7721
7741
  - **On mistakes/corrections**: Immediately capture lessons with \`session(action="capture_lesson")\`
7722
7742
 
@@ -8042,6 +8062,235 @@ function normalizeUuid(value) {
8042
8062
  if (!value) return void 0;
8043
8063
  return uuidSchema2.safeParse(value).success ? value : void 0;
8044
8064
  }
8065
+ var RULES_NOTICE_CACHE_TTL_MS = 10 * 60 * 1e3;
8066
+ var RULES_VERSION_REGEX = /Rules Version:\s*([0-9][0-9A-Za-z.\-]*)/i;
8067
+ var RULES_PROJECT_FILES = {
8068
+ codex: "AGENTS.md",
8069
+ claude: "CLAUDE.md",
8070
+ cursor: ".cursorrules",
8071
+ windsurf: ".windsurfrules",
8072
+ cline: ".clinerules",
8073
+ kilo: path4.join(".kilocode", "rules", "contextstream.md"),
8074
+ roo: path4.join(".roo", "rules", "contextstream.md"),
8075
+ aider: ".aider.conf.yml"
8076
+ };
8077
+ var RULES_GLOBAL_FILES = {
8078
+ codex: [path4.join(homedir2(), ".codex", "AGENTS.md")],
8079
+ windsurf: [path4.join(homedir2(), ".codeium", "windsurf", "memories", "global_rules.md")],
8080
+ kilo: [path4.join(homedir2(), ".kilocode", "rules", "contextstream.md")],
8081
+ roo: [path4.join(homedir2(), ".roo", "rules", "contextstream.md")]
8082
+ };
8083
+ var rulesNoticeCache = /* @__PURE__ */ new Map();
8084
+ function compareVersions2(v1, v2) {
8085
+ const parts1 = v1.split(".").map(Number);
8086
+ const parts2 = v2.split(".").map(Number);
8087
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
8088
+ const p1 = parts1[i] ?? 0;
8089
+ const p2 = parts2[i] ?? 0;
8090
+ if (p1 < p2) return -1;
8091
+ if (p1 > p2) return 1;
8092
+ }
8093
+ return 0;
8094
+ }
8095
+ function extractRulesVersion(content) {
8096
+ const match = content.match(RULES_VERSION_REGEX);
8097
+ return match?.[1]?.trim() ?? null;
8098
+ }
8099
+ function detectEditorFromClientName(clientName) {
8100
+ if (!clientName) return null;
8101
+ const normalized = clientName.toLowerCase().trim();
8102
+ if (normalized.includes("cursor")) return "cursor";
8103
+ if (normalized.includes("windsurf") || normalized.includes("codeium")) return "windsurf";
8104
+ if (normalized.includes("claude")) return "claude";
8105
+ if (normalized.includes("cline")) return "cline";
8106
+ if (normalized.includes("kilo")) return "kilo";
8107
+ if (normalized.includes("roo")) return "roo";
8108
+ if (normalized.includes("codex")) return "codex";
8109
+ if (normalized.includes("aider")) return "aider";
8110
+ return null;
8111
+ }
8112
+ function resolveRulesCandidatePaths(folderPath, editorKey) {
8113
+ const candidates = /* @__PURE__ */ new Set();
8114
+ const addProject = (key) => {
8115
+ if (!folderPath) return;
8116
+ const rel = RULES_PROJECT_FILES[key];
8117
+ if (rel) {
8118
+ candidates.add(path4.join(folderPath, rel));
8119
+ }
8120
+ };
8121
+ const addGlobal = (key) => {
8122
+ const paths = RULES_GLOBAL_FILES[key];
8123
+ if (!paths) return;
8124
+ for (const p of paths) {
8125
+ candidates.add(p);
8126
+ }
8127
+ };
8128
+ if (editorKey) {
8129
+ addProject(editorKey);
8130
+ addGlobal(editorKey);
8131
+ } else {
8132
+ for (const key of Object.keys(RULES_PROJECT_FILES)) {
8133
+ addProject(key);
8134
+ addGlobal(key);
8135
+ }
8136
+ }
8137
+ return Array.from(candidates);
8138
+ }
8139
+ function resolveFolderPath(inputPath, sessionManager) {
8140
+ if (inputPath) return inputPath;
8141
+ const fromSession = sessionManager?.getFolderPath();
8142
+ if (fromSession) return fromSession;
8143
+ const ctxPath = sessionManager?.getContext();
8144
+ const contextFolder = ctxPath && typeof ctxPath.folder_path === "string" ? ctxPath.folder_path : null;
8145
+ if (contextFolder) return contextFolder;
8146
+ const cwd = process.cwd();
8147
+ const indicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
8148
+ const hasIndicator = indicators.some((entry) => {
8149
+ try {
8150
+ return fs3.existsSync(path4.join(cwd, entry));
8151
+ } catch {
8152
+ return false;
8153
+ }
8154
+ });
8155
+ return hasIndicator ? cwd : null;
8156
+ }
8157
+ function getRulesNotice(folderPath, clientName) {
8158
+ if (!RULES_VERSION || RULES_VERSION === "0.0.0") return null;
8159
+ const editorKey = detectEditorFromClientName(clientName);
8160
+ if (!folderPath && !editorKey) {
8161
+ return null;
8162
+ }
8163
+ const cacheKey = `${folderPath ?? "none"}|${editorKey ?? "all"}`;
8164
+ const cached = rulesNoticeCache.get(cacheKey);
8165
+ if (cached && Date.now() - cached.checkedAt < RULES_NOTICE_CACHE_TTL_MS) {
8166
+ return cached.notice;
8167
+ }
8168
+ const candidates = resolveRulesCandidatePaths(folderPath, editorKey);
8169
+ const existing = candidates.filter((filePath) => fs3.existsSync(filePath));
8170
+ if (existing.length === 0) {
8171
+ const updateCommand2 = folderPath ? `generate_editor_rules(folder_path="${folderPath}")` : 'generate_editor_rules(folder_path="<cwd>")';
8172
+ const notice2 = {
8173
+ status: "missing",
8174
+ latest: RULES_VERSION,
8175
+ files_checked: candidates,
8176
+ update_tool: "generate_editor_rules",
8177
+ update_args: {
8178
+ ...folderPath ? { folder_path: folderPath } : {},
8179
+ editors: editorKey ? [editorKey] : ["all"]
8180
+ },
8181
+ update_command: updateCommand2
8182
+ };
8183
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice: notice2 });
8184
+ return notice2;
8185
+ }
8186
+ const filesMissingVersion = [];
8187
+ const filesOutdated = [];
8188
+ const versions = [];
8189
+ for (const filePath of existing) {
8190
+ try {
8191
+ const content = fs3.readFileSync(filePath, "utf-8");
8192
+ const version = extractRulesVersion(content);
8193
+ if (!version) {
8194
+ filesMissingVersion.push(filePath);
8195
+ continue;
8196
+ }
8197
+ versions.push(version);
8198
+ if (compareVersions2(version, RULES_VERSION) < 0) {
8199
+ filesOutdated.push(filePath);
8200
+ }
8201
+ } catch {
8202
+ filesMissingVersion.push(filePath);
8203
+ }
8204
+ }
8205
+ if (filesOutdated.length === 0 && filesMissingVersion.length === 0) {
8206
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice: null });
8207
+ return null;
8208
+ }
8209
+ const current = versions.sort(compareVersions2).at(-1);
8210
+ const updateCommand = folderPath ? `generate_editor_rules(folder_path="${folderPath}")` : 'generate_editor_rules(folder_path="<cwd>")';
8211
+ const notice = {
8212
+ status: filesOutdated.length > 0 ? "behind" : "unknown",
8213
+ current,
8214
+ latest: RULES_VERSION,
8215
+ files_checked: existing,
8216
+ ...filesOutdated.length > 0 ? { files_outdated: filesOutdated } : {},
8217
+ ...filesMissingVersion.length > 0 ? { files_missing_version: filesMissingVersion } : {},
8218
+ update_tool: "generate_editor_rules",
8219
+ update_args: {
8220
+ ...folderPath ? { folder_path: folderPath } : {},
8221
+ editors: editorKey ? [editorKey] : ["all"]
8222
+ },
8223
+ update_command: updateCommand
8224
+ };
8225
+ rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice });
8226
+ return notice;
8227
+ }
8228
+ var CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
8229
+ var CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
8230
+ function wrapWithMarkers(content) {
8231
+ return `${CONTEXTSTREAM_START_MARKER}
8232
+ ${content.trim()}
8233
+ ${CONTEXTSTREAM_END_MARKER}`;
8234
+ }
8235
+ async function upsertRuleFile(filePath, content) {
8236
+ await fs3.promises.mkdir(path4.dirname(filePath), { recursive: true });
8237
+ const wrappedContent = wrapWithMarkers(content);
8238
+ let existing = "";
8239
+ try {
8240
+ existing = await fs3.promises.readFile(filePath, "utf8");
8241
+ } catch {
8242
+ }
8243
+ if (!existing) {
8244
+ await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
8245
+ return "created";
8246
+ }
8247
+ const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
8248
+ const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
8249
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
8250
+ const before = existing.substring(0, startIdx);
8251
+ const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length);
8252
+ const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
8253
+ await fs3.promises.writeFile(filePath, updated.trim() + "\n", "utf8");
8254
+ return "updated";
8255
+ }
8256
+ const joined = existing.trimEnd() + "\n\n" + wrappedContent + "\n";
8257
+ await fs3.promises.writeFile(filePath, joined, "utf8");
8258
+ return "appended";
8259
+ }
8260
+ async function writeEditorRules(options) {
8261
+ const editors = options.editors && options.editors.length > 0 ? options.editors : getAvailableEditors();
8262
+ const results = [];
8263
+ for (const editor of editors) {
8264
+ const rule = generateRuleContent(editor, {
8265
+ workspaceName: options.workspaceName,
8266
+ workspaceId: options.workspaceId,
8267
+ projectName: options.projectName,
8268
+ additionalRules: options.additionalRules,
8269
+ mode: options.mode
8270
+ });
8271
+ if (!rule) {
8272
+ results.push({ editor, filename: "", status: "unknown editor" });
8273
+ continue;
8274
+ }
8275
+ const filePath = path4.join(options.folderPath, rule.filename);
8276
+ try {
8277
+ const status = await upsertRuleFile(filePath, rule.content);
8278
+ results.push({ editor, filename: rule.filename, status });
8279
+ } catch (err) {
8280
+ results.push({
8281
+ editor,
8282
+ filename: rule.filename,
8283
+ status: `error: ${err.message}`
8284
+ });
8285
+ }
8286
+ }
8287
+ for (const key of rulesNoticeCache.keys()) {
8288
+ if (key.startsWith(`${options.folderPath}|`)) {
8289
+ rulesNoticeCache.delete(key);
8290
+ }
8291
+ }
8292
+ return results;
8293
+ }
8045
8294
  var WRITE_VERBS = /* @__PURE__ */ new Set([
8046
8295
  "create",
8047
8296
  "update",
@@ -9420,6 +9669,30 @@ Upgrade: ${upgradeUrl2}` : "";
9420
9669
  }
9421
9670
  return { ok: true, resolvedPath };
9422
9671
  }
9672
+ function startBackgroundIngest(projectId, resolvedPath, ingestOptions, options = {}) {
9673
+ (async () => {
9674
+ try {
9675
+ if (options.preflight) {
9676
+ const fileCheck = await countIndexableFiles(resolvedPath, { maxFiles: 1 });
9677
+ if (fileCheck.count === 0) {
9678
+ console.error(`[ContextStream] No indexable files found in ${resolvedPath}. Skipping ingest.`);
9679
+ return;
9680
+ }
9681
+ }
9682
+ let totalIndexed = 0;
9683
+ let batchCount = 0;
9684
+ console.error(`[ContextStream] Starting background ingestion for project ${projectId} from ${resolvedPath}`);
9685
+ for await (const batch of readAllFilesInBatches(resolvedPath, { batchSize: 50 })) {
9686
+ const result = await client.ingestFiles(projectId, batch, ingestOptions);
9687
+ totalIndexed += result.data?.files_indexed ?? batch.length;
9688
+ batchCount++;
9689
+ }
9690
+ console.error(`[ContextStream] Completed background ingestion: ${totalIndexed} files in ${batchCount} batches`);
9691
+ } catch (error) {
9692
+ console.error(`[ContextStream] Ingestion failed:`, error);
9693
+ }
9694
+ })();
9695
+ }
9423
9696
  registerTool(
9424
9697
  "mcp_server_version",
9425
9698
  {
@@ -9742,30 +10015,13 @@ Access: Free`,
9742
10015
  };
9743
10016
  fs3.writeFileSync(configPath, JSON.stringify(config, null, 2));
9744
10017
  if (input.generate_editor_rules) {
9745
- for (const editor of getAvailableEditors()) {
9746
- const rule = generateRuleContent(editor, {
9747
- workspaceId,
9748
- projectName: input.name
9749
- });
9750
- if (rule) {
9751
- const filePath = path4.join(input.folder_path, rule.filename);
9752
- try {
9753
- let existingContent = "";
9754
- try {
9755
- existingContent = fs3.readFileSync(filePath, "utf-8");
9756
- } catch {
9757
- }
9758
- if (!existingContent) {
9759
- fs3.writeFileSync(filePath, rule.content);
9760
- rulesGenerated.push(rule.filename);
9761
- } else if (!existingContent.includes("ContextStream")) {
9762
- fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
9763
- rulesGenerated.push(rule.filename + " (appended)");
9764
- }
9765
- } catch {
9766
- }
9767
- }
9768
- }
10018
+ const ruleResults = await writeEditorRules({
10019
+ folderPath: input.folder_path,
10020
+ editors: getAvailableEditors(),
10021
+ workspaceId,
10022
+ projectName: input.name
10023
+ });
10024
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
9769
10025
  }
9770
10026
  } catch (err) {
9771
10027
  console.error("[ContextStream] Failed to write project config:", err);
@@ -10356,7 +10612,8 @@ ${formatContent(result)}`
10356
10612
  title: "Ingest local files",
10357
10613
  description: `Read ALL files from a local directory and ingest them for indexing.
10358
10614
  This indexes your entire project by reading files in batches.
10359
- Automatically detects code files and skips ignored directories like node_modules, target, dist, etc.`,
10615
+ Automatically detects code files and skips ignored directories like node_modules, target, dist, etc.
10616
+ Runs in the background and returns immediately; use 'projects_index_status' to monitor progress.`,
10360
10617
  inputSchema: external_exports.object({
10361
10618
  project_id: external_exports.string().uuid().optional().describe("Project to ingest files into (defaults to current session project)"),
10362
10619
  path: external_exports.string().describe("Local directory path to read files from"),
@@ -10373,31 +10630,11 @@ Automatically detects code files and skips ignored directories like node_modules
10373
10630
  if (!pathCheck.ok) {
10374
10631
  return errorResult(pathCheck.error);
10375
10632
  }
10376
- const fileCheck = await countIndexableFiles(pathCheck.resolvedPath, { maxFiles: 1 });
10377
- if (fileCheck.count === 0) {
10378
- return errorResult(
10379
- `Error: no indexable files found in directory: ${input.path}. The directory may be empty or contain only ignored files/directories. Supported file types include: .ts, .js, .py, .rs, .go, .java, .md, .json, etc.`
10380
- );
10381
- }
10382
10633
  const ingestOptions = {
10383
10634
  ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
10384
10635
  ...input.overwrite !== void 0 && { overwrite: input.overwrite }
10385
10636
  };
10386
- (async () => {
10387
- try {
10388
- let totalIndexed = 0;
10389
- let batchCount = 0;
10390
- console.error(`[ContextStream] Starting background ingestion for project ${projectId} from ${pathCheck.resolvedPath}`);
10391
- for await (const batch of readAllFilesInBatches(pathCheck.resolvedPath, { batchSize: 50 })) {
10392
- const result = await client.ingestFiles(projectId, batch, ingestOptions);
10393
- totalIndexed += result.data?.files_indexed ?? batch.length;
10394
- batchCount++;
10395
- }
10396
- console.error(`[ContextStream] Completed background ingestion: ${totalIndexed} files in ${batchCount} batches`);
10397
- } catch (error) {
10398
- console.error(`[ContextStream] Ingestion failed:`, error);
10399
- }
10400
- })();
10637
+ startBackgroundIngest(projectId, pathCheck.resolvedPath, ingestOptions, { preflight: true });
10401
10638
  const summary = {
10402
10639
  status: "started",
10403
10640
  message: "Ingestion running in background",
@@ -10733,6 +10970,25 @@ This does semantic search on the first message. You only need context_smart on s
10733
10970
  if (sessionManager) {
10734
10971
  sessionManager.markInitialized(result);
10735
10972
  }
10973
+ const folderPathForRules = input.folder_path || ideRoots[0] || resolveFolderPath(void 0, sessionManager);
10974
+ if (sessionManager && folderPathForRules) {
10975
+ sessionManager.setFolderPath(folderPathForRules);
10976
+ }
10977
+ let rulesNotice = null;
10978
+ if (folderPathForRules || detectedClientInfo?.name) {
10979
+ rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
10980
+ if (rulesNotice) {
10981
+ result.rules_notice = rulesNotice;
10982
+ }
10983
+ }
10984
+ let versionNotice = null;
10985
+ try {
10986
+ versionNotice = await getUpdateNotice();
10987
+ } catch {
10988
+ }
10989
+ if (versionNotice) {
10990
+ result.version_notice = versionNotice;
10991
+ }
10736
10992
  const workspaceId = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
10737
10993
  if (workspaceId && AUTO_HIDE_INTEGRATIONS) {
10738
10994
  try {
@@ -10804,6 +11060,19 @@ This does semantic search on the first message. You only need context_smart on s
10804
11060
  } else if (workspaceWarning) {
10805
11061
  text = [`Warning: ${workspaceWarning}`, "", formatContent(result)].join("\n");
10806
11062
  }
11063
+ const noticeLines = [];
11064
+ if (rulesNotice) {
11065
+ const current = rulesNotice.current ?? "unknown";
11066
+ noticeLines.push(`[RULES_NOTICE] status=${rulesNotice.status} current=${current} latest=${rulesNotice.latest} update="${rulesNotice.update_command}"`);
11067
+ }
11068
+ if (versionNotice?.behind) {
11069
+ noticeLines.push(`[VERSION_NOTICE] current=${versionNotice.current} latest=${versionNotice.latest} upgrade="${versionNotice.upgrade_command}"`);
11070
+ }
11071
+ if (noticeLines.length > 0) {
11072
+ text = `${text}
11073
+
11074
+ ${noticeLines.join("\n")}`;
11075
+ }
10807
11076
  return { content: [{ type: "text", text }], structuredContent: toStructured(result) };
10808
11077
  }
10809
11078
  );
@@ -10893,32 +11162,13 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
10893
11162
  const result = await client.associateWorkspace(input);
10894
11163
  let rulesGenerated = [];
10895
11164
  if (input.generate_editor_rules) {
10896
- const fs6 = await import("fs");
10897
- const path7 = await import("path");
10898
- for (const editor of getAvailableEditors()) {
10899
- const rule = generateRuleContent(editor, {
10900
- workspaceName: input.workspace_name,
10901
- workspaceId: input.workspace_id
10902
- });
10903
- if (rule) {
10904
- const filePath = path7.join(input.folder_path, rule.filename);
10905
- try {
10906
- let existingContent = "";
10907
- try {
10908
- existingContent = fs6.readFileSync(filePath, "utf-8");
10909
- } catch {
10910
- }
10911
- if (!existingContent) {
10912
- fs6.writeFileSync(filePath, rule.content);
10913
- rulesGenerated.push(rule.filename);
10914
- } else if (!existingContent.includes("ContextStream Integration")) {
10915
- fs6.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
10916
- rulesGenerated.push(rule.filename + " (appended)");
10917
- }
10918
- } catch {
10919
- }
10920
- }
10921
- }
11165
+ const ruleResults = await writeEditorRules({
11166
+ folderPath: input.folder_path,
11167
+ editors: getAvailableEditors(),
11168
+ workspaceName: input.workspace_name,
11169
+ workspaceId: input.workspace_id
11170
+ });
11171
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
10922
11172
  }
10923
11173
  const response = {
10924
11174
  ...result,
@@ -10998,31 +11248,13 @@ Behavior:
10998
11248
  });
10999
11249
  let rulesGenerated = [];
11000
11250
  if (input.generate_editor_rules) {
11001
- const fs6 = await import("fs");
11002
- const path7 = await import("path");
11003
- for (const editor of getAvailableEditors()) {
11004
- const rule = generateRuleContent(editor, {
11005
- workspaceName: newWorkspace.name || input.workspace_name,
11006
- workspaceId: newWorkspace.id
11007
- });
11008
- if (!rule) continue;
11009
- const filePath = path7.join(folderPath, rule.filename);
11010
- try {
11011
- let existingContent = "";
11012
- try {
11013
- existingContent = fs6.readFileSync(filePath, "utf-8");
11014
- } catch {
11015
- }
11016
- if (!existingContent) {
11017
- fs6.writeFileSync(filePath, rule.content);
11018
- rulesGenerated.push(rule.filename);
11019
- } else if (!existingContent.includes("ContextStream Integration")) {
11020
- fs6.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
11021
- rulesGenerated.push(rule.filename + " (appended)");
11022
- }
11023
- } catch {
11024
- }
11025
- }
11251
+ const ruleResults = await writeEditorRules({
11252
+ folderPath,
11253
+ editors: getAvailableEditors(),
11254
+ workspaceName: newWorkspace.name || input.workspace_name,
11255
+ workspaceId: newWorkspace.id
11256
+ });
11257
+ rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
11026
11258
  }
11027
11259
  const session = await client.initSession(
11028
11260
  {
@@ -11427,7 +11659,7 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
11427
11659
  These rules instruct the AI to automatically use ContextStream for memory and context.
11428
11660
  Supported editors: ${getAvailableEditors().join(", ")}`,
11429
11661
  inputSchema: external_exports.object({
11430
- folder_path: external_exports.string().describe("Absolute path to the project folder"),
11662
+ folder_path: external_exports.string().optional().describe("Absolute path to the project folder (defaults to IDE root/cwd)"),
11431
11663
  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."),
11432
11664
  workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
11433
11665
  workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
@@ -11438,58 +11670,48 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
11438
11670
  })
11439
11671
  },
11440
11672
  async (input) => {
11441
- const fs6 = await import("fs");
11442
- const path7 = await import("path");
11673
+ const folderPath = resolveFolderPath(input.folder_path, sessionManager);
11674
+ if (!folderPath) {
11675
+ return errorResult("Error: folder_path is required. Provide folder_path or run from a project directory.");
11676
+ }
11443
11677
  const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
11444
11678
  const results = [];
11445
- for (const editor of editors) {
11446
- const rule = generateRuleContent(editor, {
11447
- workspaceName: input.workspace_name,
11448
- workspaceId: input.workspace_id,
11449
- projectName: input.project_name,
11450
- additionalRules: input.additional_rules,
11451
- mode: input.mode
11452
- });
11453
- if (!rule) {
11454
- results.push({ editor, filename: "", status: "unknown editor" });
11455
- continue;
11456
- }
11457
- const filePath = path7.join(input.folder_path, rule.filename);
11458
- if (input.dry_run) {
11679
+ if (input.dry_run) {
11680
+ for (const editor of editors) {
11681
+ const rule = generateRuleContent(editor, {
11682
+ workspaceName: input.workspace_name,
11683
+ workspaceId: input.workspace_id,
11684
+ projectName: input.project_name,
11685
+ additionalRules: input.additional_rules,
11686
+ mode: input.mode
11687
+ });
11688
+ if (!rule) {
11689
+ results.push({ editor, filename: "", status: "unknown editor" });
11690
+ continue;
11691
+ }
11459
11692
  results.push({
11460
11693
  editor,
11461
11694
  filename: rule.filename,
11462
- status: "dry run - would create",
11695
+ status: "dry run - would update",
11463
11696
  content: rule.content
11464
11697
  });
11465
- } else {
11466
- try {
11467
- let existingContent = "";
11468
- try {
11469
- existingContent = fs6.readFileSync(filePath, "utf-8");
11470
- } catch {
11471
- }
11472
- if (existingContent && !existingContent.includes("ContextStream Integration")) {
11473
- const updatedContent = existingContent + "\n\n" + rule.content;
11474
- fs6.writeFileSync(filePath, updatedContent);
11475
- results.push({ editor, filename: rule.filename, status: "appended to existing" });
11476
- } else {
11477
- fs6.writeFileSync(filePath, rule.content);
11478
- results.push({ editor, filename: rule.filename, status: "created" });
11479
- }
11480
- } catch (err) {
11481
- results.push({
11482
- editor,
11483
- filename: rule.filename,
11484
- status: `error: ${err.message}`
11485
- });
11486
- }
11487
11698
  }
11699
+ } else {
11700
+ const writeResults = await writeEditorRules({
11701
+ folderPath,
11702
+ editors,
11703
+ workspaceName: input.workspace_name,
11704
+ workspaceId: input.workspace_id,
11705
+ projectName: input.project_name,
11706
+ additionalRules: input.additional_rules,
11707
+ mode: input.mode
11708
+ });
11709
+ results.push(...writeResults);
11488
11710
  }
11489
11711
  const summary = {
11490
- folder: input.folder_path,
11712
+ folder: folderPath,
11491
11713
  results,
11492
- 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.`
11714
+ 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.`
11493
11715
  };
11494
11716
  return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
11495
11717
  }
@@ -11763,11 +11985,27 @@ This saves ~80% tokens compared to including full chat history.`,
11763
11985
  const footer = `
11764
11986
  ---
11765
11987
  \u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}`;
11766
- const versionNoticeLine = result.version_notice?.behind ? `
11767
- [VERSION_NOTICE] current=${result.version_notice.current} latest=${result.version_notice.latest} upgrade="${result.version_notice.upgrade_command}"` : "";
11988
+ const folderPathForRules = resolveFolderPath(void 0, sessionManager);
11989
+ const rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
11990
+ let versionNotice = result.version_notice;
11991
+ if (!versionNotice) {
11992
+ try {
11993
+ versionNotice = await getUpdateNotice();
11994
+ } catch {
11995
+ }
11996
+ }
11997
+ const rulesNoticeLine = rulesNotice ? `
11998
+ [RULES_NOTICE] status=${rulesNotice.status} current=${rulesNotice.current ?? "unknown"} latest=${rulesNotice.latest} update="${rulesNotice.update_command}"` : "";
11999
+ const versionNoticeLine = versionNotice?.behind ? `
12000
+ [VERSION_NOTICE] current=${versionNotice.current} latest=${versionNotice.latest} upgrade="${versionNotice.upgrade_command}"` : "";
12001
+ const enrichedResult = {
12002
+ ...result,
12003
+ ...rulesNotice ? { rules_notice: rulesNotice } : {},
12004
+ ...versionNotice ? { version_notice: versionNotice } : {}
12005
+ };
11768
12006
  return {
11769
- content: [{ type: "text", text: result.context + footer + versionNoticeLine }],
11770
- structuredContent: toStructured(result)
12007
+ content: [{ type: "text", text: result.context + footer + rulesNoticeLine + versionNoticeLine }],
12008
+ structuredContent: toStructured(enrichedResult)
11771
12009
  };
11772
12010
  }
11773
12011
  );
@@ -13489,24 +13727,27 @@ Use this to remove a reminder that is no longer relevant.`,
13489
13727
  if (!validPath.ok) {
13490
13728
  return errorResult(validPath.error);
13491
13729
  }
13492
- let totalFiles = 0;
13493
- let batches = 0;
13494
- for await (const batch of readAllFilesInBatches(validPath.resolvedPath, { batchSize: 50 })) {
13495
- if (batch.length === 0) continue;
13496
- await client.ingestFiles(projectId, batch, {
13497
- overwrite: input.overwrite,
13498
- write_to_disk: input.write_to_disk
13499
- });
13500
- totalFiles += batch.length;
13501
- batches += 1;
13502
- }
13730
+ const ingestOptions = {
13731
+ ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
13732
+ ...input.overwrite !== void 0 && { overwrite: input.overwrite }
13733
+ };
13734
+ startBackgroundIngest(projectId, validPath.resolvedPath, ingestOptions);
13503
13735
  const result = {
13736
+ status: "started",
13737
+ message: "Ingestion running in background",
13504
13738
  project_id: projectId,
13505
- files_ingested: totalFiles,
13506
- batches,
13507
- path: validPath.resolvedPath
13739
+ path: validPath.resolvedPath,
13740
+ ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
13741
+ ...input.overwrite !== void 0 && { overwrite: input.overwrite },
13742
+ note: "Use 'project' with action 'index_status' to monitor progress."
13743
+ };
13744
+ return {
13745
+ content: [{
13746
+ type: "text",
13747
+ text: `Ingestion started in background for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
13748
+ }],
13749
+ structuredContent: toStructured(result)
13508
13750
  };
13509
- return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
13510
13751
  }
13511
13752
  default:
13512
13753
  return errorResult(`Unknown action: ${input.action}`);
@@ -14819,6 +15060,12 @@ var SessionManager = class {
14819
15060
  getContext() {
14820
15061
  return this.context;
14821
15062
  }
15063
+ /**
15064
+ * Get the current folder path (if known)
15065
+ */
15066
+ getFolderPath() {
15067
+ return this.folderPath;
15068
+ }
14822
15069
  /**
14823
15070
  * Mark session as manually initialized (e.g., when session_init is called explicitly)
14824
15071
  */
@@ -14830,6 +15077,10 @@ var SessionManager = class {
14830
15077
  if (workspaceId || projectId) {
14831
15078
  this.client.setDefaults({ workspace_id: workspaceId, project_id: projectId });
14832
15079
  }
15080
+ const contextFolderPath = typeof context.folder_path === "string" ? context.folder_path : void 0;
15081
+ if (contextFolderPath) {
15082
+ this.folderPath = contextFolderPath;
15083
+ }
14833
15084
  }
14834
15085
  /**
14835
15086
  * Set the folder path hint (can be passed from tools that know the workspace path)
@@ -14921,6 +15172,9 @@ var SessionManager = class {
14921
15172
  if (this.ideRoots.length === 0 && this.folderPath) {
14922
15173
  this.ideRoots = [this.folderPath];
14923
15174
  }
15175
+ if (this.ideRoots.length > 0) {
15176
+ this.folderPath = this.ideRoots[0];
15177
+ }
14924
15178
  this.initializationPromise = this._doInitialize();
14925
15179
  try {
14926
15180
  const result = await this.initializationPromise;
@@ -15366,25 +15620,25 @@ async function runHttpGateway() {
15366
15620
 
15367
15621
  // src/index.ts
15368
15622
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
15369
- import { homedir as homedir4 } from "os";
15623
+ import { homedir as homedir5 } from "os";
15370
15624
  import { join as join8 } from "path";
15371
15625
 
15372
15626
  // src/setup.ts
15373
15627
  import * as fs5 from "node:fs/promises";
15374
15628
  import * as path6 from "node:path";
15375
- import { homedir as homedir3 } from "node:os";
15629
+ import { homedir as homedir4 } from "node:os";
15376
15630
  import { stdin, stdout } from "node:process";
15377
15631
  import { createInterface } from "node:readline/promises";
15378
15632
 
15379
15633
  // src/credentials.ts
15380
15634
  import * as fs4 from "node:fs/promises";
15381
15635
  import * as path5 from "node:path";
15382
- import { homedir as homedir2 } from "node:os";
15636
+ import { homedir as homedir3 } from "node:os";
15383
15637
  function normalizeApiUrl(input) {
15384
15638
  return String(input ?? "").trim().replace(/\/+$/, "");
15385
15639
  }
15386
15640
  function credentialsFilePath() {
15387
- return path5.join(homedir2(), ".contextstream", "credentials.json");
15641
+ return path5.join(homedir3(), ".contextstream", "credentials.json");
15388
15642
  }
15389
15643
  function isRecord(value) {
15390
15644
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -15482,27 +15736,27 @@ async function fileExists(filePath) {
15482
15736
  return false;
15483
15737
  }
15484
15738
  }
15485
- var CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
15486
- var CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
15487
- function wrapWithMarkers(content) {
15488
- return `${CONTEXTSTREAM_START_MARKER}
15739
+ var CONTEXTSTREAM_START_MARKER2 = "<!-- BEGIN ContextStream -->";
15740
+ var CONTEXTSTREAM_END_MARKER2 = "<!-- END ContextStream -->";
15741
+ function wrapWithMarkers2(content) {
15742
+ return `${CONTEXTSTREAM_START_MARKER2}
15489
15743
  ${content.trim()}
15490
- ${CONTEXTSTREAM_END_MARKER}`;
15744
+ ${CONTEXTSTREAM_END_MARKER2}`;
15491
15745
  }
15492
15746
  async function upsertTextFile(filePath, content, _marker) {
15493
15747
  await fs5.mkdir(path6.dirname(filePath), { recursive: true });
15494
15748
  const exists = await fileExists(filePath);
15495
- const wrappedContent = wrapWithMarkers(content);
15749
+ const wrappedContent = wrapWithMarkers2(content);
15496
15750
  if (!exists) {
15497
15751
  await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
15498
15752
  return "created";
15499
15753
  }
15500
15754
  const existing = await fs5.readFile(filePath, "utf8").catch(() => "");
15501
- const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
15502
- const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
15755
+ const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
15756
+ const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
15503
15757
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
15504
15758
  const before = existing.substring(0, startIdx);
15505
- const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length);
15759
+ const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length);
15506
15760
  const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
15507
15761
  await fs5.writeFile(filePath, updated.trim() + "\n", "utf8");
15508
15762
  return "updated";
@@ -15517,7 +15771,7 @@ async function upsertTextFile(filePath, content, _marker) {
15517
15771
  return "appended";
15518
15772
  }
15519
15773
  function globalRulesPathForEditor(editor) {
15520
- const home = homedir3();
15774
+ const home = homedir4();
15521
15775
  switch (editor) {
15522
15776
  case "codex":
15523
15777
  return path6.join(home, ".codex", "AGENTS.md");
@@ -15546,7 +15800,7 @@ async function anyPathExists(paths) {
15546
15800
  return false;
15547
15801
  }
15548
15802
  async function isCodexInstalled() {
15549
- const home = homedir3();
15803
+ const home = homedir4();
15550
15804
  const envHome = process.env.CODEX_HOME;
15551
15805
  const candidates = [
15552
15806
  envHome,
@@ -15557,7 +15811,7 @@ async function isCodexInstalled() {
15557
15811
  return anyPathExists(candidates);
15558
15812
  }
15559
15813
  async function isClaudeInstalled() {
15560
- const home = homedir3();
15814
+ const home = homedir4();
15561
15815
  const candidates = [
15562
15816
  path6.join(home, ".claude"),
15563
15817
  path6.join(home, ".config", "claude")
@@ -15573,7 +15827,7 @@ async function isClaudeInstalled() {
15573
15827
  return anyPathExists(candidates);
15574
15828
  }
15575
15829
  async function isWindsurfInstalled() {
15576
- const home = homedir3();
15830
+ const home = homedir4();
15577
15831
  const candidates = [
15578
15832
  path6.join(home, ".codeium"),
15579
15833
  path6.join(home, ".codeium", "windsurf"),
@@ -15592,7 +15846,7 @@ async function isWindsurfInstalled() {
15592
15846
  return anyPathExists(candidates);
15593
15847
  }
15594
15848
  async function isClineInstalled() {
15595
- const home = homedir3();
15849
+ const home = homedir4();
15596
15850
  const candidates = [
15597
15851
  path6.join(home, "Documents", "Cline"),
15598
15852
  path6.join(home, ".cline"),
@@ -15601,7 +15855,7 @@ async function isClineInstalled() {
15601
15855
  return anyPathExists(candidates);
15602
15856
  }
15603
15857
  async function isKiloInstalled() {
15604
- const home = homedir3();
15858
+ const home = homedir4();
15605
15859
  const candidates = [
15606
15860
  path6.join(home, ".kilocode"),
15607
15861
  path6.join(home, ".config", "kilocode")
@@ -15609,7 +15863,7 @@ async function isKiloInstalled() {
15609
15863
  return anyPathExists(candidates);
15610
15864
  }
15611
15865
  async function isRooInstalled() {
15612
- const home = homedir3();
15866
+ const home = homedir4();
15613
15867
  const candidates = [
15614
15868
  path6.join(home, ".roo"),
15615
15869
  path6.join(home, ".config", "roo")
@@ -15617,7 +15871,7 @@ async function isRooInstalled() {
15617
15871
  return anyPathExists(candidates);
15618
15872
  }
15619
15873
  async function isAiderInstalled() {
15620
- const home = homedir3();
15874
+ const home = homedir4();
15621
15875
  const candidates = [
15622
15876
  path6.join(home, ".aider.conf.yml"),
15623
15877
  path6.join(home, ".config", "aider")
@@ -15625,7 +15879,7 @@ async function isAiderInstalled() {
15625
15879
  return anyPathExists(candidates);
15626
15880
  }
15627
15881
  async function isCursorInstalled() {
15628
- const home = homedir3();
15882
+ const home = homedir4();
15629
15883
  const candidates = [path6.join(home, ".cursor")];
15630
15884
  if (process.platform === "darwin") {
15631
15885
  candidates.push("/Applications/Cursor.app");
@@ -15773,7 +16027,7 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
15773
16027
  return before === after ? "skipped" : "updated";
15774
16028
  }
15775
16029
  function claudeDesktopConfigPath() {
15776
- const home = homedir3();
16030
+ const home = homedir4();
15777
16031
  if (process.platform === "darwin") {
15778
16032
  return path6.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
15779
16033
  }
@@ -16177,7 +16431,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16177
16431
  if (mcpScope === "project" && editor !== "codex") continue;
16178
16432
  try {
16179
16433
  if (editor === "codex") {
16180
- const filePath = path6.join(homedir3(), ".codex", "config.toml");
16434
+ const filePath = path6.join(homedir4(), ".codex", "config.toml");
16181
16435
  if (dryRun) {
16182
16436
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16183
16437
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16189,7 +16443,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16189
16443
  continue;
16190
16444
  }
16191
16445
  if (editor === "windsurf") {
16192
- const filePath = path6.join(homedir3(), ".codeium", "windsurf", "mcp_config.json");
16446
+ const filePath = path6.join(homedir4(), ".codeium", "windsurf", "mcp_config.json");
16193
16447
  if (dryRun) {
16194
16448
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16195
16449
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16223,7 +16477,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
16223
16477
  continue;
16224
16478
  }
16225
16479
  if (editor === "cursor") {
16226
- const filePath = path6.join(homedir3(), ".cursor", "mcp.json");
16480
+ const filePath = path6.join(homedir4(), ".cursor", "mcp.json");
16227
16481
  if (dryRun) {
16228
16482
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
16229
16483
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -16450,7 +16704,7 @@ Applying to ${projects.length} project(s)...`);
16450
16704
  // src/index.ts
16451
16705
  var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
16452
16706
  function showFirstRunMessage() {
16453
- const configDir = join8(homedir4(), ".contextstream");
16707
+ const configDir = join8(homedir5(), ".contextstream");
16454
16708
  const starShownFile = join8(configDir, ".star-shown");
16455
16709
  if (existsSync4(starShownFile)) {
16456
16710
  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.15",
4
+ "version": "0.4.17",
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",