@contextstream/mcp-server 0.3.4 → 0.3.5
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 +486 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4449,6 +4449,106 @@ function resolveWorkspace(repoPath) {
|
|
|
4449
4449
|
return { config: null, source: "ambiguous" };
|
|
4450
4450
|
}
|
|
4451
4451
|
|
|
4452
|
+
// src/cache.ts
|
|
4453
|
+
var MemoryCache = class {
|
|
4454
|
+
constructor(cleanupIntervalMs = 6e4) {
|
|
4455
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
4456
|
+
this.cleanupInterval = null;
|
|
4457
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), cleanupIntervalMs);
|
|
4458
|
+
}
|
|
4459
|
+
/**
|
|
4460
|
+
* Get a cached value if it exists and hasn't expired
|
|
4461
|
+
*/
|
|
4462
|
+
get(key) {
|
|
4463
|
+
const entry = this.cache.get(key);
|
|
4464
|
+
if (!entry) return void 0;
|
|
4465
|
+
if (Date.now() > entry.expiresAt) {
|
|
4466
|
+
this.cache.delete(key);
|
|
4467
|
+
return void 0;
|
|
4468
|
+
}
|
|
4469
|
+
return entry.value;
|
|
4470
|
+
}
|
|
4471
|
+
/**
|
|
4472
|
+
* Set a value with TTL in milliseconds
|
|
4473
|
+
*/
|
|
4474
|
+
set(key, value, ttlMs) {
|
|
4475
|
+
this.cache.set(key, {
|
|
4476
|
+
value,
|
|
4477
|
+
expiresAt: Date.now() + ttlMs
|
|
4478
|
+
});
|
|
4479
|
+
}
|
|
4480
|
+
/**
|
|
4481
|
+
* Delete a specific key
|
|
4482
|
+
*/
|
|
4483
|
+
delete(key) {
|
|
4484
|
+
return this.cache.delete(key);
|
|
4485
|
+
}
|
|
4486
|
+
/**
|
|
4487
|
+
* Delete all keys matching a prefix
|
|
4488
|
+
*/
|
|
4489
|
+
deleteByPrefix(prefix) {
|
|
4490
|
+
let deleted = 0;
|
|
4491
|
+
for (const key of this.cache.keys()) {
|
|
4492
|
+
if (key.startsWith(prefix)) {
|
|
4493
|
+
this.cache.delete(key);
|
|
4494
|
+
deleted++;
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
return deleted;
|
|
4498
|
+
}
|
|
4499
|
+
/**
|
|
4500
|
+
* Clear all cached entries
|
|
4501
|
+
*/
|
|
4502
|
+
clear() {
|
|
4503
|
+
this.cache.clear();
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Remove expired entries
|
|
4507
|
+
*/
|
|
4508
|
+
cleanup() {
|
|
4509
|
+
const now = Date.now();
|
|
4510
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
4511
|
+
if (now > entry.expiresAt) {
|
|
4512
|
+
this.cache.delete(key);
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
/**
|
|
4517
|
+
* Stop the cleanup interval
|
|
4518
|
+
*/
|
|
4519
|
+
destroy() {
|
|
4520
|
+
if (this.cleanupInterval) {
|
|
4521
|
+
clearInterval(this.cleanupInterval);
|
|
4522
|
+
this.cleanupInterval = null;
|
|
4523
|
+
}
|
|
4524
|
+
this.cache.clear();
|
|
4525
|
+
}
|
|
4526
|
+
};
|
|
4527
|
+
var CacheTTL = {
|
|
4528
|
+
// Workspace info rarely changes - cache for 5 minutes
|
|
4529
|
+
WORKSPACE: 5 * 60 * 1e3,
|
|
4530
|
+
// Project info rarely changes - cache for 5 minutes
|
|
4531
|
+
PROJECT: 5 * 60 * 1e3,
|
|
4532
|
+
// Session init context - cache for 60 seconds
|
|
4533
|
+
SESSION_INIT: 60 * 1e3,
|
|
4534
|
+
// Memory events - cache for 30 seconds (they change more often)
|
|
4535
|
+
MEMORY_EVENTS: 30 * 1e3,
|
|
4536
|
+
// Search results - cache for 60 seconds
|
|
4537
|
+
SEARCH: 60 * 1e3,
|
|
4538
|
+
// User preferences - cache for 5 minutes
|
|
4539
|
+
USER_PREFS: 5 * 60 * 1e3
|
|
4540
|
+
};
|
|
4541
|
+
var CacheKeys = {
|
|
4542
|
+
workspace: (id) => `workspace:${id}`,
|
|
4543
|
+
workspaceList: (userId) => `workspaces:${userId}`,
|
|
4544
|
+
project: (id) => `project:${id}`,
|
|
4545
|
+
projectList: (workspaceId) => `projects:${workspaceId}`,
|
|
4546
|
+
sessionInit: (workspaceId, projectId) => `session_init:${workspaceId || ""}:${projectId || ""}`,
|
|
4547
|
+
memoryEvents: (workspaceId) => `memory:${workspaceId}`,
|
|
4548
|
+
search: (query, workspaceId) => `search:${workspaceId || ""}:${query}`
|
|
4549
|
+
};
|
|
4550
|
+
var globalCache = new MemoryCache();
|
|
4551
|
+
|
|
4452
4552
|
// src/client.ts
|
|
4453
4553
|
var uuidSchema = external_exports.string().uuid();
|
|
4454
4554
|
var ContextStreamClient = class {
|
|
@@ -4587,14 +4687,24 @@ var ContextStreamClient = class {
|
|
|
4587
4687
|
aiEnhancedContext(body) {
|
|
4588
4688
|
return request(this.config, "/ai/context/enhanced", { body: this.withDefaults(body) });
|
|
4589
4689
|
}
|
|
4590
|
-
// Project extended operations
|
|
4591
|
-
getProject(projectId) {
|
|
4690
|
+
// Project extended operations (with caching)
|
|
4691
|
+
async getProject(projectId) {
|
|
4592
4692
|
uuidSchema.parse(projectId);
|
|
4593
|
-
|
|
4693
|
+
const cacheKey = CacheKeys.project(projectId);
|
|
4694
|
+
const cached = globalCache.get(cacheKey);
|
|
4695
|
+
if (cached) return cached;
|
|
4696
|
+
const result = await request(this.config, `/projects/${projectId}`, { method: "GET" });
|
|
4697
|
+
globalCache.set(cacheKey, result, CacheTTL.PROJECT);
|
|
4698
|
+
return result;
|
|
4594
4699
|
}
|
|
4595
|
-
projectOverview(projectId) {
|
|
4700
|
+
async projectOverview(projectId) {
|
|
4596
4701
|
uuidSchema.parse(projectId);
|
|
4597
|
-
|
|
4702
|
+
const cacheKey = `project_overview:${projectId}`;
|
|
4703
|
+
const cached = globalCache.get(cacheKey);
|
|
4704
|
+
if (cached) return cached;
|
|
4705
|
+
const result = await request(this.config, `/projects/${projectId}/overview`, { method: "GET" });
|
|
4706
|
+
globalCache.set(cacheKey, result, CacheTTL.PROJECT);
|
|
4707
|
+
return result;
|
|
4598
4708
|
}
|
|
4599
4709
|
projectStatistics(projectId) {
|
|
4600
4710
|
uuidSchema.parse(projectId);
|
|
@@ -4618,14 +4728,24 @@ var ContextStreamClient = class {
|
|
|
4618
4728
|
body: { files }
|
|
4619
4729
|
});
|
|
4620
4730
|
}
|
|
4621
|
-
// Workspace extended operations
|
|
4622
|
-
getWorkspace(workspaceId) {
|
|
4731
|
+
// Workspace extended operations (with caching)
|
|
4732
|
+
async getWorkspace(workspaceId) {
|
|
4623
4733
|
uuidSchema.parse(workspaceId);
|
|
4624
|
-
|
|
4734
|
+
const cacheKey = CacheKeys.workspace(workspaceId);
|
|
4735
|
+
const cached = globalCache.get(cacheKey);
|
|
4736
|
+
if (cached) return cached;
|
|
4737
|
+
const result = await request(this.config, `/workspaces/${workspaceId}`, { method: "GET" });
|
|
4738
|
+
globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
|
|
4739
|
+
return result;
|
|
4625
4740
|
}
|
|
4626
|
-
workspaceOverview(workspaceId) {
|
|
4741
|
+
async workspaceOverview(workspaceId) {
|
|
4627
4742
|
uuidSchema.parse(workspaceId);
|
|
4628
|
-
|
|
4743
|
+
const cacheKey = `workspace_overview:${workspaceId}`;
|
|
4744
|
+
const cached = globalCache.get(cacheKey);
|
|
4745
|
+
if (cached) return cached;
|
|
4746
|
+
const result = await request(this.config, `/workspaces/${workspaceId}/overview`, { method: "GET" });
|
|
4747
|
+
globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
|
|
4748
|
+
return result;
|
|
4629
4749
|
}
|
|
4630
4750
|
workspaceAnalytics(workspaceId) {
|
|
4631
4751
|
uuidSchema.parse(workspaceId);
|
|
@@ -4833,9 +4953,67 @@ var ContextStreamClient = class {
|
|
|
4833
4953
|
context.ide_roots = ideRoots;
|
|
4834
4954
|
if (workspaceId) {
|
|
4835
4955
|
try {
|
|
4836
|
-
|
|
4837
|
-
|
|
4956
|
+
const batchedContext = await this._fetchSessionContextBatched({
|
|
4957
|
+
workspace_id: workspaceId,
|
|
4958
|
+
project_id: projectId,
|
|
4959
|
+
session_id: context.session_id,
|
|
4960
|
+
context_hint: params.context_hint,
|
|
4961
|
+
include_recent_memory: params.include_recent_memory !== false,
|
|
4962
|
+
include_decisions: params.include_decisions !== false
|
|
4963
|
+
});
|
|
4964
|
+
if (batchedContext.workspace) {
|
|
4965
|
+
context.workspace = batchedContext.workspace;
|
|
4966
|
+
}
|
|
4967
|
+
if (batchedContext.project) {
|
|
4968
|
+
context.project = batchedContext.project;
|
|
4969
|
+
}
|
|
4970
|
+
if (batchedContext.recent_memory) {
|
|
4971
|
+
context.recent_memory = { items: batchedContext.recent_memory };
|
|
4972
|
+
}
|
|
4973
|
+
if (batchedContext.recent_decisions) {
|
|
4974
|
+
context.recent_decisions = { items: batchedContext.recent_decisions };
|
|
4975
|
+
}
|
|
4976
|
+
if (batchedContext.relevant_context) {
|
|
4977
|
+
context.relevant_context = batchedContext.relevant_context;
|
|
4978
|
+
}
|
|
4979
|
+
} catch (e) {
|
|
4980
|
+
console.error("[ContextStream] Batched endpoint failed, falling back to individual calls:", e);
|
|
4981
|
+
await this._fetchSessionContextFallback(context, workspaceId, projectId, params);
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
return context;
|
|
4985
|
+
}
|
|
4986
|
+
/**
|
|
4987
|
+
* Fetch session context using the batched /session/init endpoint.
|
|
4988
|
+
* This is much faster than making 5-6 individual API calls.
|
|
4989
|
+
*/
|
|
4990
|
+
async _fetchSessionContextBatched(params) {
|
|
4991
|
+
const cacheKey = CacheKeys.sessionInit(params.workspace_id, params.project_id);
|
|
4992
|
+
const cached = globalCache.get(cacheKey);
|
|
4993
|
+
if (cached) {
|
|
4994
|
+
console.error("[ContextStream] Session context cache HIT");
|
|
4995
|
+
return cached;
|
|
4996
|
+
}
|
|
4997
|
+
const result = await request(this.config, "/session/init", {
|
|
4998
|
+
body: {
|
|
4999
|
+
workspace_id: params.workspace_id,
|
|
5000
|
+
project_id: params.project_id,
|
|
5001
|
+
session_id: params.session_id,
|
|
5002
|
+
include_recent_memory: params.include_recent_memory ?? true,
|
|
5003
|
+
include_decisions: params.include_decisions ?? true
|
|
4838
5004
|
}
|
|
5005
|
+
});
|
|
5006
|
+
const contextData = "data" in result && result.data ? result.data : result;
|
|
5007
|
+
globalCache.set(cacheKey, contextData, CacheTTL.SESSION_INIT);
|
|
5008
|
+
return contextData;
|
|
5009
|
+
}
|
|
5010
|
+
/**
|
|
5011
|
+
* Fallback to individual API calls if batched endpoint is unavailable.
|
|
5012
|
+
*/
|
|
5013
|
+
async _fetchSessionContextFallback(context, workspaceId, projectId, params) {
|
|
5014
|
+
try {
|
|
5015
|
+
context.workspace = await this.workspaceOverview(workspaceId);
|
|
5016
|
+
} catch {
|
|
4839
5017
|
}
|
|
4840
5018
|
if (projectId) {
|
|
4841
5019
|
try {
|
|
@@ -4843,39 +5021,34 @@ var ContextStreamClient = class {
|
|
|
4843
5021
|
} catch {
|
|
4844
5022
|
}
|
|
4845
5023
|
}
|
|
4846
|
-
if (params.include_recent_memory !== false
|
|
5024
|
+
if (params.include_recent_memory !== false) {
|
|
4847
5025
|
try {
|
|
4848
5026
|
context.recent_memory = await this.listMemoryEvents({
|
|
4849
5027
|
workspace_id: workspaceId,
|
|
4850
|
-
// NOTE: Intentionally NOT filtering by project_id
|
|
4851
|
-
// to get workspace-wide context
|
|
4852
5028
|
limit: 10
|
|
4853
5029
|
});
|
|
4854
5030
|
} catch {
|
|
4855
5031
|
}
|
|
4856
5032
|
}
|
|
4857
|
-
if (params.include_decisions !== false
|
|
5033
|
+
if (params.include_decisions !== false) {
|
|
4858
5034
|
try {
|
|
4859
5035
|
context.recent_decisions = await this.memoryDecisions({
|
|
4860
5036
|
workspace_id: workspaceId,
|
|
4861
|
-
// NOTE: Intentionally NOT filtering by project_id
|
|
4862
5037
|
limit: 5
|
|
4863
5038
|
});
|
|
4864
5039
|
} catch {
|
|
4865
5040
|
}
|
|
4866
5041
|
}
|
|
4867
|
-
if (params.context_hint
|
|
5042
|
+
if (params.context_hint) {
|
|
4868
5043
|
try {
|
|
4869
5044
|
context.relevant_context = await this.memorySearch({
|
|
4870
5045
|
query: params.context_hint,
|
|
4871
5046
|
workspace_id: workspaceId,
|
|
4872
|
-
// Search workspace-wide first
|
|
4873
5047
|
limit: 5
|
|
4874
5048
|
});
|
|
4875
5049
|
} catch {
|
|
4876
5050
|
}
|
|
4877
5051
|
}
|
|
4878
|
-
return context;
|
|
4879
5052
|
}
|
|
4880
5053
|
/**
|
|
4881
5054
|
* Associate a folder with a workspace (called after user selects from candidates).
|
|
@@ -5015,6 +5188,188 @@ var ContextStreamClient = class {
|
|
|
5015
5188
|
}
|
|
5016
5189
|
};
|
|
5017
5190
|
|
|
5191
|
+
// src/rules-templates.ts
|
|
5192
|
+
var CONTEXTSTREAM_RULES = `
|
|
5193
|
+
## ContextStream Integration
|
|
5194
|
+
|
|
5195
|
+
You have access to ContextStream MCP tools for persistent memory and context across sessions.
|
|
5196
|
+
|
|
5197
|
+
---
|
|
5198
|
+
|
|
5199
|
+
### \u{1F6A8} ESSENTIAL Tools (Always Use)
|
|
5200
|
+
|
|
5201
|
+
These tools should be called automatically in every conversation:
|
|
5202
|
+
|
|
5203
|
+
| When | Tool | Example Prompt |
|
|
5204
|
+
|------|------|----------------|
|
|
5205
|
+
| **Conversation start** | \`session_init\` | "Load my workspace context" |
|
|
5206
|
+
| **User makes a decision** | \`session_capture\` | "Remember we decided to use PostgreSQL" |
|
|
5207
|
+
| **User states a preference** | \`session_capture\` | "Remember I prefer TypeScript strict mode" |
|
|
5208
|
+
| **Before starting work** | \`session_recall\` | "What did we decide about auth?" |
|
|
5209
|
+
|
|
5210
|
+
---
|
|
5211
|
+
|
|
5212
|
+
### \u{1F4DD} Memory & Context Tools (Use Frequently)
|
|
5213
|
+
|
|
5214
|
+
| Tool | Example Prompt |
|
|
5215
|
+
|------|----------------|
|
|
5216
|
+
| \`session_remember\` | "Remember that the API uses JWT tokens" |
|
|
5217
|
+
| \`session_smart_search\` | "Search for everything about payment processing" |
|
|
5218
|
+
| \`memory_search\` | "Search my notes for caching strategy" |
|
|
5219
|
+
| \`memory_decisions\` | "Show me all past decisions" |
|
|
5220
|
+
| \`memory_timeline\` | "Show me the timeline of this workspace" |
|
|
5221
|
+
| \`memory_summary\` | "Summarize what we've worked on" |
|
|
5222
|
+
|
|
5223
|
+
---
|
|
5224
|
+
|
|
5225
|
+
### \u{1F50D} Code Search Tools (Use When Exploring Code)
|
|
5226
|
+
|
|
5227
|
+
| Tool | Example Prompt |
|
|
5228
|
+
|------|----------------|
|
|
5229
|
+
| \`search_semantic\` | "How does authentication work in this codebase?" |
|
|
5230
|
+
| \`search_hybrid\` | "Find code related to user permissions" |
|
|
5231
|
+
| \`search_keyword\` | "Search for handleSubmit function" |
|
|
5232
|
+
| \`search_pattern\` | "Find all TODO comments" |
|
|
5233
|
+
| \`ai_context\` | "Build context for implementing user roles" |
|
|
5234
|
+
| \`ai_enhanced_context\` | "Give me deep context on the payment system" |
|
|
5235
|
+
|
|
5236
|
+
---
|
|
5237
|
+
|
|
5238
|
+
### \u{1F578}\uFE0F Code Analysis Tools (Use for Architecture)
|
|
5239
|
+
|
|
5240
|
+
| Tool | Example Prompt |
|
|
5241
|
+
|------|----------------|
|
|
5242
|
+
| \`graph_dependencies\` | "What does UserService depend on?" |
|
|
5243
|
+
| \`graph_impact\` | "What would be affected if I change the User model?" |
|
|
5244
|
+
| \`graph_call_path\` | "How does login() eventually call the database?" |
|
|
5245
|
+
| \`graph_circular_dependencies\` | "Are there any circular dependencies?" |
|
|
5246
|
+
| \`graph_unused_code\` | "Find unused code in this project" |
|
|
5247
|
+
|
|
5248
|
+
---
|
|
5249
|
+
|
|
5250
|
+
### \u{1F4C1} Project & Workspace Tools
|
|
5251
|
+
|
|
5252
|
+
| Tool | Example Prompt |
|
|
5253
|
+
|------|----------------|
|
|
5254
|
+
| \`projects_overview\` | "Give me an overview of this project" |
|
|
5255
|
+
| \`projects_index\` | "Re-index this project" |
|
|
5256
|
+
| \`workspaces_overview\` | "Give me a workspace overview" |
|
|
5257
|
+
| \`workspace_associate\` | "Associate this folder with my workspace" |
|
|
5258
|
+
| \`generate_editor_rules\` | "Generate ContextStream rules for this project" |
|
|
5259
|
+
|
|
5260
|
+
---
|
|
5261
|
+
|
|
5262
|
+
### Automatic Behavior Guidelines
|
|
5263
|
+
|
|
5264
|
+
**At conversation start:**
|
|
5265
|
+
- Call \`session_init\` to load context (this happens automatically on first tool call)
|
|
5266
|
+
- Review the loaded decisions and memory before responding
|
|
5267
|
+
|
|
5268
|
+
**During the conversation:**
|
|
5269
|
+
- When the user makes a decision \u2192 \`session_capture\` with \`event_type: "decision"\`
|
|
5270
|
+
- When you learn a user preference \u2192 \`session_capture\` with \`event_type: "preference"\`
|
|
5271
|
+
- When discovering important context \u2192 \`session_remember\`
|
|
5272
|
+
- When encountering a bug \u2192 \`session_capture\` with \`event_type: "bug"\`
|
|
5273
|
+
- When a feature is discussed \u2192 \`session_capture\` with \`event_type: "feature"\`
|
|
5274
|
+
|
|
5275
|
+
**When exploring code:**
|
|
5276
|
+
- Use \`search_semantic\` or \`search_hybrid\` for code search
|
|
5277
|
+
- Use \`graph_dependencies\` to understand architecture
|
|
5278
|
+
- Use \`graph_impact\` before making changes
|
|
5279
|
+
|
|
5280
|
+
**At conversation end (if significant work done):**
|
|
5281
|
+
- Summarize and capture key decisions with \`session_capture\`
|
|
5282
|
+
|
|
5283
|
+
---
|
|
5284
|
+
|
|
5285
|
+
### Quick Examples
|
|
5286
|
+
|
|
5287
|
+
\`\`\`
|
|
5288
|
+
# Start of conversation - load context
|
|
5289
|
+
session_init()
|
|
5290
|
+
|
|
5291
|
+
# User says "Let's use PostgreSQL for the database"
|
|
5292
|
+
session_capture(event_type="decision", title="Database Choice", content="Decided to use PostgreSQL...")
|
|
5293
|
+
|
|
5294
|
+
# User says "I prefer async/await over callbacks"
|
|
5295
|
+
session_capture(event_type="preference", title="Async Style", content="User prefers async/await...")
|
|
5296
|
+
|
|
5297
|
+
# Need to find related code
|
|
5298
|
+
search_semantic(query="authentication middleware")
|
|
5299
|
+
|
|
5300
|
+
# Check what we discussed before
|
|
5301
|
+
session_recall(query="what did we decide about caching?")
|
|
5302
|
+
\`\`\`
|
|
5303
|
+
`.trim();
|
|
5304
|
+
var TEMPLATES = {
|
|
5305
|
+
windsurf: {
|
|
5306
|
+
filename: ".windsurfrules",
|
|
5307
|
+
description: "Windsurf AI rules",
|
|
5308
|
+
content: `# Windsurf Rules
|
|
5309
|
+
${CONTEXTSTREAM_RULES}
|
|
5310
|
+
`
|
|
5311
|
+
},
|
|
5312
|
+
cursor: {
|
|
5313
|
+
filename: ".cursorrules",
|
|
5314
|
+
description: "Cursor AI rules",
|
|
5315
|
+
content: `# Cursor Rules
|
|
5316
|
+
${CONTEXTSTREAM_RULES}
|
|
5317
|
+
`
|
|
5318
|
+
},
|
|
5319
|
+
cline: {
|
|
5320
|
+
filename: ".clinerules",
|
|
5321
|
+
description: "Cline AI rules",
|
|
5322
|
+
content: `# Cline Rules
|
|
5323
|
+
${CONTEXTSTREAM_RULES}
|
|
5324
|
+
`
|
|
5325
|
+
},
|
|
5326
|
+
claude: {
|
|
5327
|
+
filename: "CLAUDE.md",
|
|
5328
|
+
description: "Claude Code instructions",
|
|
5329
|
+
content: `# Claude Code Instructions
|
|
5330
|
+
${CONTEXTSTREAM_RULES}
|
|
5331
|
+
`
|
|
5332
|
+
},
|
|
5333
|
+
aider: {
|
|
5334
|
+
filename: ".aider.conf.yml",
|
|
5335
|
+
description: "Aider configuration with system prompt",
|
|
5336
|
+
content: `# Aider Configuration
|
|
5337
|
+
# Note: Aider uses different config format - this adds to the system prompt
|
|
5338
|
+
|
|
5339
|
+
# Add ContextStream guidance to conventions
|
|
5340
|
+
conventions: |
|
|
5341
|
+
${CONTEXTSTREAM_RULES.split("\n").map((line) => " " + line).join("\n")}
|
|
5342
|
+
`
|
|
5343
|
+
}
|
|
5344
|
+
};
|
|
5345
|
+
function getAvailableEditors() {
|
|
5346
|
+
return Object.keys(TEMPLATES);
|
|
5347
|
+
}
|
|
5348
|
+
function getTemplate(editor) {
|
|
5349
|
+
return TEMPLATES[editor.toLowerCase()] || null;
|
|
5350
|
+
}
|
|
5351
|
+
function generateRuleContent(editor, options) {
|
|
5352
|
+
const template = getTemplate(editor);
|
|
5353
|
+
if (!template) return null;
|
|
5354
|
+
let content = template.content;
|
|
5355
|
+
if (options?.workspaceName || options?.projectName) {
|
|
5356
|
+
const header = `
|
|
5357
|
+
# Workspace: ${options.workspaceName || "Unknown"}
|
|
5358
|
+
${options.projectName ? `# Project: ${options.projectName}` : ""}
|
|
5359
|
+
${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
|
|
5360
|
+
|
|
5361
|
+
`;
|
|
5362
|
+
content = header + content;
|
|
5363
|
+
}
|
|
5364
|
+
if (options?.additionalRules) {
|
|
5365
|
+
content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
|
|
5366
|
+
}
|
|
5367
|
+
return {
|
|
5368
|
+
filename: template.filename,
|
|
5369
|
+
content: content.trim() + "\n"
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5018
5373
|
// src/tools.ts
|
|
5019
5374
|
function formatContent(data) {
|
|
5020
5375
|
return JSON.stringify(data, null, 2);
|
|
@@ -5882,17 +6237,52 @@ Use this to understand how the user likes to work and adapt your responses accor
|
|
|
5882
6237
|
description: `Associate a folder/repo with a workspace after user selection.
|
|
5883
6238
|
Call this after session_init returns status='requires_workspace_selection' and the user has chosen a workspace.
|
|
5884
6239
|
This persists the selection to .contextstream/config.json so future sessions auto-connect.
|
|
5885
|
-
Optionally creates a parent folder mapping (e.g., all repos under /dev/company/* map to the same workspace)
|
|
6240
|
+
Optionally creates a parent folder mapping (e.g., all repos under /dev/company/* map to the same workspace).
|
|
6241
|
+
Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
5886
6242
|
inputSchema: external_exports.object({
|
|
5887
6243
|
folder_path: external_exports.string().describe("Absolute path to the folder/repo to associate"),
|
|
5888
6244
|
workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
|
|
5889
6245
|
workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
|
|
5890
|
-
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)")
|
|
6246
|
+
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
|
|
6247
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules (.windsurfrules, .cursorrules, etc.) for automatic ContextStream usage")
|
|
5891
6248
|
})
|
|
5892
6249
|
},
|
|
5893
6250
|
async (input) => {
|
|
5894
6251
|
const result = await client.associateWorkspace(input);
|
|
5895
|
-
|
|
6252
|
+
let rulesGenerated = [];
|
|
6253
|
+
if (input.generate_editor_rules) {
|
|
6254
|
+
const fs3 = await import("fs");
|
|
6255
|
+
const path3 = await import("path");
|
|
6256
|
+
for (const editor of getAvailableEditors()) {
|
|
6257
|
+
const rule = generateRuleContent(editor, {
|
|
6258
|
+
workspaceName: input.workspace_name,
|
|
6259
|
+
workspaceId: input.workspace_id
|
|
6260
|
+
});
|
|
6261
|
+
if (rule) {
|
|
6262
|
+
const filePath = path3.join(input.folder_path, rule.filename);
|
|
6263
|
+
try {
|
|
6264
|
+
let existingContent = "";
|
|
6265
|
+
try {
|
|
6266
|
+
existingContent = fs3.readFileSync(filePath, "utf-8");
|
|
6267
|
+
} catch {
|
|
6268
|
+
}
|
|
6269
|
+
if (!existingContent) {
|
|
6270
|
+
fs3.writeFileSync(filePath, rule.content);
|
|
6271
|
+
rulesGenerated.push(rule.filename);
|
|
6272
|
+
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
6273
|
+
fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
6274
|
+
rulesGenerated.push(rule.filename + " (appended)");
|
|
6275
|
+
}
|
|
6276
|
+
} catch {
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
const response = {
|
|
6282
|
+
...result,
|
|
6283
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
6284
|
+
};
|
|
6285
|
+
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
5896
6286
|
}
|
|
5897
6287
|
);
|
|
5898
6288
|
registerTool(
|
|
@@ -5992,6 +6382,79 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
|
|
|
5992
6382
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
5993
6383
|
}
|
|
5994
6384
|
);
|
|
6385
|
+
registerTool(
|
|
6386
|
+
"generate_editor_rules",
|
|
6387
|
+
{
|
|
6388
|
+
title: "Generate editor AI rules",
|
|
6389
|
+
description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Claude Code, Aider).
|
|
6390
|
+
These rules instruct the AI to automatically use ContextStream for memory and context.
|
|
6391
|
+
Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
6392
|
+
inputSchema: external_exports.object({
|
|
6393
|
+
folder_path: external_exports.string().describe("Absolute path to the project folder"),
|
|
6394
|
+
editors: external_exports.array(external_exports.enum(["windsurf", "cursor", "cline", "claude", "aider", "all"])).optional().describe("Which editors to generate rules for. Defaults to all."),
|
|
6395
|
+
workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
|
|
6396
|
+
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
|
|
6397
|
+
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
6398
|
+
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
6399
|
+
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
6400
|
+
})
|
|
6401
|
+
},
|
|
6402
|
+
async (input) => {
|
|
6403
|
+
const fs3 = await import("fs");
|
|
6404
|
+
const path3 = await import("path");
|
|
6405
|
+
const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
|
|
6406
|
+
const results = [];
|
|
6407
|
+
for (const editor of editors) {
|
|
6408
|
+
const rule = generateRuleContent(editor, {
|
|
6409
|
+
workspaceName: input.workspace_name,
|
|
6410
|
+
workspaceId: input.workspace_id,
|
|
6411
|
+
projectName: input.project_name,
|
|
6412
|
+
additionalRules: input.additional_rules
|
|
6413
|
+
});
|
|
6414
|
+
if (!rule) {
|
|
6415
|
+
results.push({ editor, filename: "", status: "unknown editor" });
|
|
6416
|
+
continue;
|
|
6417
|
+
}
|
|
6418
|
+
const filePath = path3.join(input.folder_path, rule.filename);
|
|
6419
|
+
if (input.dry_run) {
|
|
6420
|
+
results.push({
|
|
6421
|
+
editor,
|
|
6422
|
+
filename: rule.filename,
|
|
6423
|
+
status: "dry run - would create",
|
|
6424
|
+
content: rule.content
|
|
6425
|
+
});
|
|
6426
|
+
} else {
|
|
6427
|
+
try {
|
|
6428
|
+
let existingContent = "";
|
|
6429
|
+
try {
|
|
6430
|
+
existingContent = fs3.readFileSync(filePath, "utf-8");
|
|
6431
|
+
} catch {
|
|
6432
|
+
}
|
|
6433
|
+
if (existingContent && !existingContent.includes("ContextStream Integration")) {
|
|
6434
|
+
const updatedContent = existingContent + "\n\n" + rule.content;
|
|
6435
|
+
fs3.writeFileSync(filePath, updatedContent);
|
|
6436
|
+
results.push({ editor, filename: rule.filename, status: "appended to existing" });
|
|
6437
|
+
} else {
|
|
6438
|
+
fs3.writeFileSync(filePath, rule.content);
|
|
6439
|
+
results.push({ editor, filename: rule.filename, status: "created" });
|
|
6440
|
+
}
|
|
6441
|
+
} catch (err) {
|
|
6442
|
+
results.push({
|
|
6443
|
+
editor,
|
|
6444
|
+
filename: rule.filename,
|
|
6445
|
+
status: `error: ${err.message}`
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
const summary = {
|
|
6451
|
+
folder: input.folder_path,
|
|
6452
|
+
results,
|
|
6453
|
+
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.`
|
|
6454
|
+
};
|
|
6455
|
+
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
6456
|
+
}
|
|
6457
|
+
);
|
|
5995
6458
|
}
|
|
5996
6459
|
|
|
5997
6460
|
// src/resources.ts
|
package/package.json
CHANGED