@contextstream/mcp-server 0.4.19 → 0.4.20
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 +285 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7527,7 +7527,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7527
7527
|
|
|
7528
7528
|
**NO EXCEPTIONS.** Do not skip even if you think you have enough context.
|
|
7529
7529
|
|
|
7530
|
-
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode\` and
|
|
7530
|
+
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context_smart\` (the API will fall back).
|
|
7531
7531
|
|
|
7532
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
7533
|
|
|
@@ -7621,9 +7621,9 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7621
7621
|
2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
|
|
7622
7622
|
3. \`project(action="files")\` - file tree/list (only when needed)
|
|
7623
7623
|
4. \`graph(action="dependencies", ...)\` - code structure
|
|
7624
|
-
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results or
|
|
7624
|
+
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results, errors, or the user explicitly asks
|
|
7625
7625
|
|
|
7626
|
-
|
|
7626
|
+
If ContextStream returns results, stop and use them. Do NOT use local Search/Explore/Read unless you need exact code edits.
|
|
7627
7627
|
|
|
7628
7628
|
**Code Analysis:**
|
|
7629
7629
|
- Dependencies: \`graph(action="dependencies", file_path="...")\`
|
|
@@ -7715,7 +7715,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7715
7715
|
| **Before risky work** | \`session(action="get_lessons", query="<topic>")\` |
|
|
7716
7716
|
| **On user frustration** | \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\` |
|
|
7717
7717
|
|
|
7718
|
-
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode
|
|
7718
|
+
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context_smart\` (the API will fall back).
|
|
7719
7719
|
|
|
7720
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
7721
|
|
|
@@ -7739,6 +7739,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7739
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
|
|
7740
7740
|
- **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before any local repo scans
|
|
7741
7741
|
- **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
|
|
7742
|
+
- **If ContextStream returns results**: Do NOT use local Search/Explore/Read; only open specific files when needed for exact edits
|
|
7742
7743
|
- **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
|
|
7743
7744
|
- **On [RULES_NOTICE]**: Use \`generate_editor_rules(folder_path="<cwd>")\` to update rules
|
|
7744
7745
|
- **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
|
|
@@ -8054,6 +8055,7 @@ var DEFAULT_PARAM_DESCRIPTIONS = {
|
|
|
8054
8055
|
recurrence: "Recurrence pattern (daily, weekly, monthly).",
|
|
8055
8056
|
keywords: "Keywords for matching.",
|
|
8056
8057
|
overwrite: "Allow overwriting existing files on disk.",
|
|
8058
|
+
overwrite_existing: "Allow overwriting existing rule files (ContextStream block only).",
|
|
8057
8059
|
write_to_disk: "Write ingested files to disk before indexing.",
|
|
8058
8060
|
await_indexing: "Wait for indexing to finish before returning.",
|
|
8059
8061
|
auto_index: "Automatically index on creation.",
|
|
@@ -8261,6 +8263,19 @@ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
|
|
|
8261
8263
|
"plans & tasks",
|
|
8262
8264
|
"complete action reference"
|
|
8263
8265
|
];
|
|
8266
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
8267
|
+
/^#\s+workspace:/i,
|
|
8268
|
+
/^#\s+project:/i,
|
|
8269
|
+
/^#\s+workspace id:/i,
|
|
8270
|
+
/^#\s+codex cli instructions$/i,
|
|
8271
|
+
/^#\s+claude code instructions$/i,
|
|
8272
|
+
/^#\s+cursor rules$/i,
|
|
8273
|
+
/^#\s+windsurf rules$/i,
|
|
8274
|
+
/^#\s+cline rules$/i,
|
|
8275
|
+
/^#\s+kilo code rules$/i,
|
|
8276
|
+
/^#\s+roo code rules$/i,
|
|
8277
|
+
/^#\s+aider configuration$/i
|
|
8278
|
+
];
|
|
8264
8279
|
function wrapWithMarkers(content) {
|
|
8265
8280
|
return `${CONTEXTSTREAM_START_MARKER}
|
|
8266
8281
|
${content.trim()}
|
|
@@ -8281,6 +8296,96 @@ function isLegacyContextStreamRules(content) {
|
|
|
8281
8296
|
}
|
|
8282
8297
|
return hasHeading;
|
|
8283
8298
|
}
|
|
8299
|
+
function isContextStreamPreamble(line) {
|
|
8300
|
+
const trimmed = line.trim();
|
|
8301
|
+
if (!trimmed) return false;
|
|
8302
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
8303
|
+
}
|
|
8304
|
+
function findContextStreamHeading(lines) {
|
|
8305
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8306
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
8307
|
+
const match = headingRegex.exec(lines[i]);
|
|
8308
|
+
if (!match) continue;
|
|
8309
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
8310
|
+
return { index: i, level: match[1].length };
|
|
8311
|
+
}
|
|
8312
|
+
}
|
|
8313
|
+
return null;
|
|
8314
|
+
}
|
|
8315
|
+
function findSectionEnd(lines, startLine, level) {
|
|
8316
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8317
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
8318
|
+
const match = headingRegex.exec(lines[i]);
|
|
8319
|
+
if (!match) continue;
|
|
8320
|
+
if (match[1].length <= level) return i;
|
|
8321
|
+
}
|
|
8322
|
+
return lines.length;
|
|
8323
|
+
}
|
|
8324
|
+
function extractContextStreamBlock(content) {
|
|
8325
|
+
const lines = content.split(/\r?\n/);
|
|
8326
|
+
const heading = findContextStreamHeading(lines);
|
|
8327
|
+
if (!heading) return content.trim();
|
|
8328
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8329
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
8330
|
+
}
|
|
8331
|
+
function findLegacyContextStreamSection(content) {
|
|
8332
|
+
const lines = content.split(/\r?\n/);
|
|
8333
|
+
const heading = findContextStreamHeading(lines);
|
|
8334
|
+
if (!heading) return null;
|
|
8335
|
+
let startLine = heading.index;
|
|
8336
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
8337
|
+
const line = lines[i];
|
|
8338
|
+
if (!line.trim()) {
|
|
8339
|
+
startLine = i;
|
|
8340
|
+
continue;
|
|
8341
|
+
}
|
|
8342
|
+
if (isContextStreamPreamble(line)) {
|
|
8343
|
+
startLine = i;
|
|
8344
|
+
continue;
|
|
8345
|
+
}
|
|
8346
|
+
break;
|
|
8347
|
+
}
|
|
8348
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8349
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
8350
|
+
}
|
|
8351
|
+
function blockHasPreamble(block) {
|
|
8352
|
+
const lines = block.split(/\r?\n/);
|
|
8353
|
+
for (const line of lines) {
|
|
8354
|
+
const trimmed = line.trim();
|
|
8355
|
+
if (!trimmed) continue;
|
|
8356
|
+
if (isContextStreamPreamble(trimmed)) return true;
|
|
8357
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
8358
|
+
}
|
|
8359
|
+
return false;
|
|
8360
|
+
}
|
|
8361
|
+
function replaceContextStreamBlock(existing, content) {
|
|
8362
|
+
const fullWrapped = wrapWithMarkers(content);
|
|
8363
|
+
const blockWrapped = wrapWithMarkers(extractContextStreamBlock(content));
|
|
8364
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
|
|
8365
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
|
|
8366
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
8367
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER.length, endIdx);
|
|
8368
|
+
const replacement = blockHasPreamble(existingBlock) ? fullWrapped : blockWrapped;
|
|
8369
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
8370
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length).trimStart();
|
|
8371
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8372
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8373
|
+
}
|
|
8374
|
+
const legacy = findLegacyContextStreamSection(existing);
|
|
8375
|
+
if (legacy) {
|
|
8376
|
+
const lines = existing.split(/\r?\n/);
|
|
8377
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
8378
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
8379
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
8380
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8381
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8382
|
+
}
|
|
8383
|
+
if (isLegacyContextStreamRules(existing)) {
|
|
8384
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
8385
|
+
}
|
|
8386
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
8387
|
+
return { content: appended, status: "appended" };
|
|
8388
|
+
}
|
|
8284
8389
|
async function upsertRuleFile(filePath, content) {
|
|
8285
8390
|
await fs3.promises.mkdir(path4.dirname(filePath), { recursive: true });
|
|
8286
8391
|
const wrappedContent = wrapWithMarkers(content);
|
|
@@ -8293,22 +8398,13 @@ async function upsertRuleFile(filePath, content) {
|
|
|
8293
8398
|
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8294
8399
|
return "created";
|
|
8295
8400
|
}
|
|
8296
|
-
|
|
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)) {
|
|
8401
|
+
if (!existing.trim()) {
|
|
8306
8402
|
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8307
8403
|
return "updated";
|
|
8308
8404
|
}
|
|
8309
|
-
const
|
|
8310
|
-
await fs3.promises.writeFile(filePath,
|
|
8311
|
-
return
|
|
8405
|
+
const replaced = replaceContextStreamBlock(existing, content);
|
|
8406
|
+
await fs3.promises.writeFile(filePath, replaced.content, "utf8");
|
|
8407
|
+
return replaced.status;
|
|
8312
8408
|
}
|
|
8313
8409
|
async function writeEditorRules(options) {
|
|
8314
8410
|
const editors = options.editors && options.editors.length > 0 ? options.editors : getAvailableEditors();
|
|
@@ -8326,6 +8422,10 @@ async function writeEditorRules(options) {
|
|
|
8326
8422
|
continue;
|
|
8327
8423
|
}
|
|
8328
8424
|
const filePath = path4.join(options.folderPath, rule.filename);
|
|
8425
|
+
if (fs3.existsSync(filePath) && !options.overwriteExisting) {
|
|
8426
|
+
results.push({ editor, filename: rule.filename, status: "skipped (exists)" });
|
|
8427
|
+
continue;
|
|
8428
|
+
}
|
|
8329
8429
|
try {
|
|
8330
8430
|
const status = await upsertRuleFile(filePath, rule.content);
|
|
8331
8431
|
results.push({ editor, filename: rule.filename, status });
|
|
@@ -10039,7 +10139,8 @@ Access: Free`,
|
|
|
10039
10139
|
description: external_exports.string().optional().describe("Project description"),
|
|
10040
10140
|
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID (uses current session workspace if not provided)"),
|
|
10041
10141
|
folder_path: external_exports.string().optional().describe("Optional: Local folder path to associate with this project"),
|
|
10042
|
-
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in folder_path (requires folder_path)")
|
|
10142
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in folder_path (requires folder_path)"),
|
|
10143
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
10043
10144
|
})
|
|
10044
10145
|
},
|
|
10045
10146
|
async (input) => {
|
|
@@ -10054,6 +10155,7 @@ Access: Free`,
|
|
|
10054
10155
|
});
|
|
10055
10156
|
const projectData = result;
|
|
10056
10157
|
let rulesGenerated = [];
|
|
10158
|
+
let rulesSkipped = [];
|
|
10057
10159
|
if (input.folder_path && projectData.id) {
|
|
10058
10160
|
try {
|
|
10059
10161
|
const configDir = path4.join(input.folder_path, ".contextstream");
|
|
@@ -10072,9 +10174,11 @@ Access: Free`,
|
|
|
10072
10174
|
folderPath: input.folder_path,
|
|
10073
10175
|
editors: getAvailableEditors(),
|
|
10074
10176
|
workspaceId,
|
|
10075
|
-
projectName: input.name
|
|
10177
|
+
projectName: input.name,
|
|
10178
|
+
overwriteExisting: input.overwrite_existing
|
|
10076
10179
|
});
|
|
10077
10180
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
10181
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
10078
10182
|
}
|
|
10079
10183
|
} catch (err) {
|
|
10080
10184
|
console.error("[ContextStream] Failed to write project config:", err);
|
|
@@ -10084,7 +10188,8 @@ Access: Free`,
|
|
|
10084
10188
|
...result && typeof result === "object" ? result : {},
|
|
10085
10189
|
folder_path: input.folder_path,
|
|
10086
10190
|
config_written: input.folder_path ? true : void 0,
|
|
10087
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
10191
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
10192
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
10088
10193
|
};
|
|
10089
10194
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
10090
10195
|
}
|
|
@@ -11208,24 +11313,29 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
11208
11313
|
workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
|
|
11209
11314
|
workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
|
|
11210
11315
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
|
|
11211
|
-
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider")
|
|
11316
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"),
|
|
11317
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
11212
11318
|
})
|
|
11213
11319
|
},
|
|
11214
11320
|
async (input) => {
|
|
11215
11321
|
const result = await client.associateWorkspace(input);
|
|
11216
11322
|
let rulesGenerated = [];
|
|
11323
|
+
let rulesSkipped = [];
|
|
11217
11324
|
if (input.generate_editor_rules) {
|
|
11218
11325
|
const ruleResults = await writeEditorRules({
|
|
11219
11326
|
folderPath: input.folder_path,
|
|
11220
11327
|
editors: getAvailableEditors(),
|
|
11221
11328
|
workspaceName: input.workspace_name,
|
|
11222
|
-
workspaceId: input.workspace_id
|
|
11329
|
+
workspaceId: input.workspace_id,
|
|
11330
|
+
overwriteExisting: input.overwrite_existing
|
|
11223
11331
|
});
|
|
11224
11332
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11333
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11225
11334
|
}
|
|
11226
11335
|
const response = {
|
|
11227
11336
|
...result,
|
|
11228
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11337
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11338
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11229
11339
|
};
|
|
11230
11340
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
11231
11341
|
}
|
|
@@ -11248,6 +11358,7 @@ Behavior:
|
|
|
11248
11358
|
visibility: external_exports.enum(["private", "public"]).optional().describe("Workspace visibility (default: private)"),
|
|
11249
11359
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/company/* -> workspace)"),
|
|
11250
11360
|
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in the folder for automatic ContextStream usage"),
|
|
11361
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules"),
|
|
11251
11362
|
context_hint: external_exports.string().optional().describe("Optional context hint for session initialization"),
|
|
11252
11363
|
auto_index: external_exports.boolean().optional().describe("Automatically create and index project from folder (default: true)")
|
|
11253
11364
|
})
|
|
@@ -11300,14 +11411,17 @@ Behavior:
|
|
|
11300
11411
|
create_parent_mapping: input.create_parent_mapping
|
|
11301
11412
|
});
|
|
11302
11413
|
let rulesGenerated = [];
|
|
11414
|
+
let rulesSkipped = [];
|
|
11303
11415
|
if (input.generate_editor_rules) {
|
|
11304
11416
|
const ruleResults = await writeEditorRules({
|
|
11305
11417
|
folderPath,
|
|
11306
11418
|
editors: getAvailableEditors(),
|
|
11307
11419
|
workspaceName: newWorkspace.name || input.workspace_name,
|
|
11308
|
-
workspaceId: newWorkspace.id
|
|
11420
|
+
workspaceId: newWorkspace.id,
|
|
11421
|
+
overwriteExisting: input.overwrite_existing
|
|
11309
11422
|
});
|
|
11310
11423
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11424
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11311
11425
|
}
|
|
11312
11426
|
const session = await client.initSession(
|
|
11313
11427
|
{
|
|
@@ -11332,7 +11446,8 @@ Behavior:
|
|
|
11332
11446
|
name: newWorkspace.name || input.workspace_name
|
|
11333
11447
|
},
|
|
11334
11448
|
association: associateResult,
|
|
11335
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11449
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11450
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11336
11451
|
}
|
|
11337
11452
|
};
|
|
11338
11453
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
@@ -11719,6 +11834,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11719
11834
|
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
11720
11835
|
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
11721
11836
|
mode: external_exports.enum(["minimal", "full"]).optional().describe("Rule verbosity mode (default: minimal)"),
|
|
11837
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
11722
11838
|
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
11723
11839
|
})
|
|
11724
11840
|
},
|
|
@@ -11757,14 +11873,17 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11757
11873
|
workspaceId: input.workspace_id,
|
|
11758
11874
|
projectName: input.project_name,
|
|
11759
11875
|
additionalRules: input.additional_rules,
|
|
11760
|
-
mode: input.mode
|
|
11876
|
+
mode: input.mode,
|
|
11877
|
+
overwriteExisting: input.overwrite_existing
|
|
11761
11878
|
});
|
|
11762
11879
|
results.push(...writeResults);
|
|
11763
11880
|
}
|
|
11881
|
+
const createdCount = results.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").length;
|
|
11882
|
+
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
11764
11883
|
const summary = {
|
|
11765
11884
|
folder: folderPath,
|
|
11766
11885
|
results,
|
|
11767
|
-
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${
|
|
11886
|
+
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`
|
|
11768
11887
|
};
|
|
11769
11888
|
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
11770
11889
|
}
|
|
@@ -15821,6 +15940,19 @@ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2 = [
|
|
|
15821
15940
|
"plans & tasks",
|
|
15822
15941
|
"complete action reference"
|
|
15823
15942
|
];
|
|
15943
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
15944
|
+
/^#\s+workspace:/i,
|
|
15945
|
+
/^#\s+project:/i,
|
|
15946
|
+
/^#\s+workspace id:/i,
|
|
15947
|
+
/^#\s+codex cli instructions$/i,
|
|
15948
|
+
/^#\s+claude code instructions$/i,
|
|
15949
|
+
/^#\s+cursor rules$/i,
|
|
15950
|
+
/^#\s+windsurf rules$/i,
|
|
15951
|
+
/^#\s+cline rules$/i,
|
|
15952
|
+
/^#\s+kilo code rules$/i,
|
|
15953
|
+
/^#\s+roo code rules$/i,
|
|
15954
|
+
/^#\s+aider configuration$/i
|
|
15955
|
+
];
|
|
15824
15956
|
function wrapWithMarkers2(content) {
|
|
15825
15957
|
return `${CONTEXTSTREAM_START_MARKER2}
|
|
15826
15958
|
${content.trim()}
|
|
@@ -15841,6 +15973,96 @@ function isLegacyContextStreamRules2(content) {
|
|
|
15841
15973
|
}
|
|
15842
15974
|
return hasHeading;
|
|
15843
15975
|
}
|
|
15976
|
+
function isContextStreamPreamble2(line) {
|
|
15977
|
+
const trimmed = line.trim();
|
|
15978
|
+
if (!trimmed) return false;
|
|
15979
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS2.some((pattern) => pattern.test(trimmed));
|
|
15980
|
+
}
|
|
15981
|
+
function findContextStreamHeading2(lines) {
|
|
15982
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15983
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
15984
|
+
const match = headingRegex.exec(lines[i]);
|
|
15985
|
+
if (!match) continue;
|
|
15986
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
15987
|
+
return { index: i, level: match[1].length };
|
|
15988
|
+
}
|
|
15989
|
+
}
|
|
15990
|
+
return null;
|
|
15991
|
+
}
|
|
15992
|
+
function findSectionEnd2(lines, startLine, level) {
|
|
15993
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15994
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
15995
|
+
const match = headingRegex.exec(lines[i]);
|
|
15996
|
+
if (!match) continue;
|
|
15997
|
+
if (match[1].length <= level) return i;
|
|
15998
|
+
}
|
|
15999
|
+
return lines.length;
|
|
16000
|
+
}
|
|
16001
|
+
function extractContextStreamBlock2(content) {
|
|
16002
|
+
const lines = content.split(/\r?\n/);
|
|
16003
|
+
const heading = findContextStreamHeading2(lines);
|
|
16004
|
+
if (!heading) return content.trim();
|
|
16005
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16006
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
16007
|
+
}
|
|
16008
|
+
function findLegacyContextStreamSection2(content) {
|
|
16009
|
+
const lines = content.split(/\r?\n/);
|
|
16010
|
+
const heading = findContextStreamHeading2(lines);
|
|
16011
|
+
if (!heading) return null;
|
|
16012
|
+
let startLine = heading.index;
|
|
16013
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
16014
|
+
const line = lines[i];
|
|
16015
|
+
if (!line.trim()) {
|
|
16016
|
+
startLine = i;
|
|
16017
|
+
continue;
|
|
16018
|
+
}
|
|
16019
|
+
if (isContextStreamPreamble2(line)) {
|
|
16020
|
+
startLine = i;
|
|
16021
|
+
continue;
|
|
16022
|
+
}
|
|
16023
|
+
break;
|
|
16024
|
+
}
|
|
16025
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16026
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
16027
|
+
}
|
|
16028
|
+
function blockHasPreamble2(block) {
|
|
16029
|
+
const lines = block.split(/\r?\n/);
|
|
16030
|
+
for (const line of lines) {
|
|
16031
|
+
const trimmed = line.trim();
|
|
16032
|
+
if (!trimmed) continue;
|
|
16033
|
+
if (isContextStreamPreamble2(trimmed)) return true;
|
|
16034
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
16035
|
+
}
|
|
16036
|
+
return false;
|
|
16037
|
+
}
|
|
16038
|
+
function replaceContextStreamBlock2(existing, content) {
|
|
16039
|
+
const fullWrapped = wrapWithMarkers2(content);
|
|
16040
|
+
const blockWrapped = wrapWithMarkers2(extractContextStreamBlock2(content));
|
|
16041
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
|
|
16042
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
16043
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
16044
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER2.length, endIdx);
|
|
16045
|
+
const replacement = blockHasPreamble2(existingBlock) ? fullWrapped : blockWrapped;
|
|
16046
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
16047
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length).trimStart();
|
|
16048
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16049
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16050
|
+
}
|
|
16051
|
+
const legacy = findLegacyContextStreamSection2(existing);
|
|
16052
|
+
if (legacy) {
|
|
16053
|
+
const lines = existing.split(/\r?\n/);
|
|
16054
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
16055
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
16056
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
16057
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16058
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16059
|
+
}
|
|
16060
|
+
if (isLegacyContextStreamRules2(existing)) {
|
|
16061
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
16062
|
+
}
|
|
16063
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
16064
|
+
return { content: appended, status: "appended" };
|
|
16065
|
+
}
|
|
15844
16066
|
async function upsertTextFile(filePath, content, _marker) {
|
|
15845
16067
|
await fs5.mkdir(path6.dirname(filePath), { recursive: true });
|
|
15846
16068
|
const exists = await fileExists(filePath);
|
|
@@ -15850,22 +16072,13 @@ async function upsertTextFile(filePath, content, _marker) {
|
|
|
15850
16072
|
return "created";
|
|
15851
16073
|
}
|
|
15852
16074
|
const existing = await fs5.readFile(filePath, "utf8").catch(() => "");
|
|
15853
|
-
|
|
15854
|
-
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
15855
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
15856
|
-
const before = existing.substring(0, startIdx);
|
|
15857
|
-
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length);
|
|
15858
|
-
const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
|
|
15859
|
-
await fs5.writeFile(filePath, updated.trim() + "\n", "utf8");
|
|
15860
|
-
return "updated";
|
|
15861
|
-
}
|
|
15862
|
-
if (isLegacyContextStreamRules2(existing)) {
|
|
16075
|
+
if (!existing.trim()) {
|
|
15863
16076
|
await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
15864
16077
|
return "updated";
|
|
15865
16078
|
}
|
|
15866
|
-
const
|
|
15867
|
-
await fs5.writeFile(filePath,
|
|
15868
|
-
return
|
|
16079
|
+
const replaced = replaceContextStreamBlock2(existing, content);
|
|
16080
|
+
await fs5.writeFile(filePath, replaced.content, "utf8");
|
|
16081
|
+
return replaced.status;
|
|
15869
16082
|
}
|
|
15870
16083
|
function globalRulesPathForEditor(editor) {
|
|
15871
16084
|
const home = homedir4();
|
|
@@ -16250,6 +16463,27 @@ async function runSetupWizard(args) {
|
|
|
16250
16463
|
const dryRun = args.includes("--dry-run");
|
|
16251
16464
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
16252
16465
|
const writeActions = [];
|
|
16466
|
+
let overwriteAllRules = null;
|
|
16467
|
+
let skipAllRules = false;
|
|
16468
|
+
const confirmOverwriteRules = async (filePath) => {
|
|
16469
|
+
if (dryRun) return true;
|
|
16470
|
+
if (skipAllRules) return false;
|
|
16471
|
+
if (overwriteAllRules) return true;
|
|
16472
|
+
const exists = await fileExists(filePath);
|
|
16473
|
+
if (!exists) return true;
|
|
16474
|
+
const answer = normalizeInput(
|
|
16475
|
+
await rl.question(`Rules file already exists at ${filePath}. Replace ContextStream block? [y/N/a/s]: `)
|
|
16476
|
+
).toLowerCase();
|
|
16477
|
+
if (answer === "a" || answer === "all") {
|
|
16478
|
+
overwriteAllRules = true;
|
|
16479
|
+
return true;
|
|
16480
|
+
}
|
|
16481
|
+
if (answer === "s" || answer === "skip-all" || answer === "none") {
|
|
16482
|
+
skipAllRules = true;
|
|
16483
|
+
return false;
|
|
16484
|
+
}
|
|
16485
|
+
return answer === "y" || answer === "yes";
|
|
16486
|
+
};
|
|
16253
16487
|
try {
|
|
16254
16488
|
console.log(`ContextStream Setup Wizard (v${VERSION})`);
|
|
16255
16489
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
@@ -16622,6 +16856,12 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
16622
16856
|
console.log(`- ${EDITOR_LABELS[editor]}: would write ${filePath}`);
|
|
16623
16857
|
continue;
|
|
16624
16858
|
}
|
|
16859
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
16860
|
+
if (!allowOverwrite) {
|
|
16861
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
16862
|
+
console.log(`- ${EDITOR_LABELS[editor]}: skipped ${filePath}`);
|
|
16863
|
+
continue;
|
|
16864
|
+
}
|
|
16625
16865
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16626
16866
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16627
16867
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
@@ -16757,6 +16997,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
16757
16997
|
continue;
|
|
16758
16998
|
}
|
|
16759
16999
|
try {
|
|
17000
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
17001
|
+
if (!allowOverwrite) {
|
|
17002
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
17003
|
+
continue;
|
|
17004
|
+
}
|
|
16760
17005
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16761
17006
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16762
17007
|
} catch (err) {
|
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.
|
|
4
|
+
"version": "0.4.20",
|
|
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",
|